summaryrefslogtreecommitdiffstats
path: root/src/seastar/include/seastar/util/alloc_failure_injector.hh
blob: fc43b4dc36a092645ee03337402769ba57030e29 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
 * This file is open source software, licensed to you under the terms
 * of the Apache License, Version 2.0 (the "License").  See the NOTICE file
 * distributed with this work for additional information regarding copyright
 * ownership.  You may not use this file except in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/*
 * Copyright 2017 ScyllaDB
 */

#pragma once

#include <limits>
#include <cstdint>
#include <functional>
#include <seastar/util/noncopyable_function.hh>
#include <seastar/util/critical_alloc_section.hh>

namespace seastar {
namespace memory {

///
/// Allocation failure injection framework. Allows testing for exception safety.
///
/// To exhaustively inject failure at every allocation point:
///
///     uint64_t i = 0;
///     while (true) {
///         try {
///             local_failure_injector().fail_after(i++);
///             code_under_test();
///             local_failure_injector().cancel();
///             break;
///         } catch (const std::bad_alloc&) {
///             // expected
///         }
///     }
class alloc_failure_injector {
    uint64_t _alloc_count;
    uint64_t _fail_at = std::numeric_limits<uint64_t>::max();
    noncopyable_function<void()> _on_alloc_failure = [] { throw std::bad_alloc(); };
    bool _failed;
private:
    void fail();
public:
    /// \brief Marks a point in code which should be considered for failure injection.
    void on_alloc_point() {
        if (is_critical_alloc_section()) {
            return;
        }
        if (_alloc_count >= _fail_at) {
            fail();
        }
        ++_alloc_count;
    }

    /// Counts encountered allocation points which didn't fail and didn't have failure suppressed.
    uint64_t alloc_count() const {
        return _alloc_count;
    }

    /// Will cause count-th allocation point from now to fail, counting from 0.
    void fail_after(uint64_t count) {
        _fail_at = _alloc_count + count;
        _failed = false;
    }

    /// Cancels the failure scheduled by fail_after().
    void cancel() {
        _fail_at = std::numeric_limits<uint64_t>::max();
    }

    /// Returns true iff allocation was failed since last fail_after().
    bool failed() const {
        return _failed;
    }

    /// Runs given function with a custom failure action instead of the default std::bad_alloc throw.
    void run_with_callback(noncopyable_function<void()> callback, noncopyable_function<void()> to_run);
};

/// \cond internal
extern thread_local alloc_failure_injector the_alloc_failure_injector;
/// \endcond

/// \brief Return the shard-local \ref alloc_failure_injector instance.
inline
alloc_failure_injector& local_failure_injector() {
    return the_alloc_failure_injector;
}

#ifdef SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION

#ifdef SEASTAR_DEFAULT_ALLOCATOR
#error SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION is not supported when using SEASTAR_DEFAULT_ALLOCATOR
#endif

#endif


struct [[deprecated("Use scoped_critical_section instead")]] disable_failure_guard {
    scoped_critical_alloc_section cs;
};

/// \brief Marks a point in code which should be considered for failure injection.
inline
void on_alloc_point() {
#ifdef SEASTAR_ENABLE_ALLOC_FAILURE_INJECTION
    local_failure_injector().on_alloc_point();
#endif
}

/// Repeatedly run func with allocation failures
///
/// Initially, allocations start to fail immediately. In each
/// subsequent run the failures start one allocation later. This
/// returns when func is run and no allocation failures are detected.
void with_allocation_failures(noncopyable_function<void()> func);

}
}