]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | /* |
2 | * This file is open source software, licensed to you under the terms | |
3 | * of the Apache License, Version 2.0 (the "License"). See the NOTICE file | |
4 | * distributed with this work for additional information regarding copyright | |
5 | * ownership. You may not use this file except in compliance with the License. | |
6 | * | |
7 | * You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, | |
12 | * software distributed under the License is distributed on an | |
13 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
14 | * KIND, either express or implied. See the License for the | |
15 | * specific language governing permissions and limitations | |
16 | * under the License. | |
17 | */ | |
18 | /* | |
19 | * Copyright (C) 2017 ScyllaDB. | |
20 | */ | |
21 | ||
22 | #pragma once | |
23 | ||
24 | #include <seastar/util/noncopyable_function.hh> | |
25 | #include <seastar/util/optimized_optional.hh> | |
26 | #include <seastar/util/std-compat.hh> | |
27 | ||
28 | #include <boost/intrusive/list.hpp> | |
29 | ||
30 | #include <exception> | |
31 | ||
32 | namespace bi = boost::intrusive; | |
33 | ||
34 | namespace seastar { | |
35 | ||
36 | /// \addtogroup fiber-module | |
37 | /// @{ | |
38 | ||
39 | /// Exception thrown when an \ref abort_source object has been | |
40 | /// notified by the \ref abort_source::request_abort() method. | |
41 | class abort_requested_exception : public std::exception { | |
42 | public: | |
43 | virtual const char* what() const noexcept override { | |
44 | return "abort requested"; | |
45 | } | |
46 | }; | |
47 | ||
48 | /// Facility to communicate a cancellation request to a fiber. | |
49 | /// Callbacks can be registered with the \c abort_source, which are called | |
50 | /// atomically with a call to request_abort(). | |
51 | class abort_source { | |
1e59de90 TL |
52 | using subscription_callback_type = noncopyable_function<void (const std::optional<std::exception_ptr>&) noexcept>; |
53 | using naive_subscription_callback_type = noncopyable_function<void() noexcept>; | |
11fdf7f2 TL |
54 | |
55 | public: | |
56 | /// Represents a handle to the callback registered by a given fiber. Ending the | |
57 | /// lifetime of the \c subscription will unregister the callback, if it hasn't | |
58 | /// been invoked yet. | |
59 | class subscription : public bi::list_base_hook<bi::link_mode<bi::auto_unlink>> { | |
60 | friend class abort_source; | |
61 | ||
11fdf7f2 TL |
62 | subscription_callback_type _target; |
63 | ||
64 | explicit subscription(abort_source& as, subscription_callback_type target) | |
20effc67 | 65 | : _target(std::move(target)) { |
11fdf7f2 TL |
66 | as._subscriptions->push_back(*this); |
67 | } | |
68 | ||
1e59de90 TL |
69 | struct naive_cb_tag {}; // to disambiguate constructors |
70 | explicit subscription(naive_cb_tag, abort_source& as, naive_subscription_callback_type naive_cb) | |
71 | : _target([cb = std::move(naive_cb)] (const std::optional<std::exception_ptr>&) noexcept { cb(); }) { | |
72 | as._subscriptions->push_back(*this); | |
73 | } | |
74 | ||
75 | void on_abort(const std::optional<std::exception_ptr>& ex) noexcept { | |
76 | _target(ex); | |
11fdf7f2 TL |
77 | } |
78 | ||
79 | public: | |
80 | subscription() = default; | |
81 | ||
82 | subscription(subscription&& other) noexcept(std::is_nothrow_move_constructible<subscription_callback_type>::value) | |
20effc67 | 83 | : _target(std::move(other._target)) { |
11fdf7f2 TL |
84 | subscription_list_type::node_algorithms::swap_nodes(other.this_ptr(), this_ptr()); |
85 | } | |
86 | ||
87 | subscription& operator=(subscription&& other) noexcept(std::is_nothrow_move_assignable<subscription_callback_type>::value) { | |
88 | if (this != &other) { | |
89 | _target = std::move(other._target); | |
1e59de90 | 90 | unlink(); |
11fdf7f2 TL |
91 | subscription_list_type::node_algorithms::swap_nodes(other.this_ptr(), this_ptr()); |
92 | } | |
93 | return *this; | |
94 | } | |
95 | ||
96 | explicit operator bool() const noexcept { | |
20effc67 | 97 | return is_linked(); |
11fdf7f2 TL |
98 | } |
99 | }; | |
100 | ||
101 | private: | |
102 | using subscription_list_type = bi::list<subscription, bi::constant_time_size<false>>; | |
f67539c2 | 103 | std::optional<subscription_list_type> _subscriptions = subscription_list_type(); |
1e59de90 TL |
104 | std::exception_ptr _ex; |
105 | ||
106 | void do_request_abort(std::optional<std::exception_ptr> ex) noexcept { | |
107 | assert(_subscriptions); | |
108 | _ex = ex.value_or(get_default_exception()); | |
109 | auto subs = std::exchange(_subscriptions, std::nullopt); | |
110 | while (!subs->empty()) { | |
111 | subscription& s = subs->front(); | |
112 | s.unlink(); | |
113 | s.on_abort(ex); | |
114 | } | |
115 | } | |
11fdf7f2 TL |
116 | |
117 | public: | |
1e59de90 TL |
118 | abort_source() = default; |
119 | virtual ~abort_source() = default; | |
120 | ||
121 | abort_source(abort_source&&) = default; | |
122 | abort_source& operator=(abort_source&&) = default; | |
123 | ||
11fdf7f2 TL |
124 | /// Delays the invocation of the callback \c f until \ref request_abort() is called. |
125 | /// \returns an engaged \ref optimized_optional containing a \ref subscription that can be used to control | |
126 | /// the lifetime of the callback \c f, if \ref abort_requested() is \c false. Otherwise, | |
127 | /// returns a disengaged \ref optimized_optional. | |
1e59de90 TL |
128 | template <typename Func> |
129 | SEASTAR_CONCEPT(requires | |
130 | requires (Func f, const std::optional<std::exception_ptr>& opt_ex) { { f(opt_ex) } noexcept -> std::same_as<void>; } | |
131 | || requires (Func f) { { f() } noexcept -> std::same_as<void>; } | |
132 | ) | |
20effc67 | 133 | [[nodiscard]] |
1e59de90 | 134 | optimized_optional<subscription> subscribe(Func&& f) { |
11fdf7f2 TL |
135 | if (abort_requested()) { |
136 | return { }; | |
137 | } | |
1e59de90 TL |
138 | if constexpr (std::is_invocable_v<Func, std::exception_ptr>) { |
139 | return { subscription(*this, std::forward<Func>(f)) }; | |
140 | } else { | |
141 | return { subscription(subscription::naive_cb_tag{}, *this, std::forward<Func>(f)) }; | |
142 | } | |
11fdf7f2 TL |
143 | } |
144 | ||
145 | /// Requests that the target operation be aborted. Current subscriptions | |
1e59de90 TL |
146 | /// are invoked inline with this call with a disengaged optional<std::exception_ptr>, |
147 | /// and no new ones can be registered. | |
148 | /// Must be called exactly once, otherwise the program will be aborted. | |
149 | void request_abort() noexcept { | |
150 | do_request_abort(std::nullopt); | |
151 | } | |
152 | ||
153 | /// Requests that the target operation be aborted with a given \c exception_ptr. | |
154 | /// Current subscriptions are invoked inline with this exception, | |
155 | /// and no new ones can be registered. | |
156 | /// Must be called exactly once, otherwise the program will be aborted. | |
157 | void request_abort_ex(std::exception_ptr ex) noexcept { | |
158 | do_request_abort(std::make_optional(std::move(ex))); | |
159 | } | |
160 | ||
161 | /// Requests that the target operation be aborted with a given \c Exception object. | |
162 | /// Current subscriptions are invoked inline with this exception, converted to std::exception_ptr, | |
163 | /// and no new ones can be registered. | |
164 | /// Must be called exactly once, otherwise the program will be aborted. | |
165 | template <typename Exception> | |
166 | void request_abort_ex(Exception&& e) noexcept { | |
167 | do_request_abort(std::make_optional(std::make_exception_ptr(std::forward<Exception>(e)))); | |
11fdf7f2 TL |
168 | } |
169 | ||
170 | /// Returns whether an abort has been requested. | |
171 | bool abort_requested() const noexcept { | |
172 | return !_subscriptions; | |
173 | } | |
174 | ||
175 | ||
f67539c2 | 176 | /// Throws a \ref abort_requested_exception if cancellation has been requested. |
11fdf7f2 TL |
177 | void check() const { |
178 | if (abort_requested()) { | |
1e59de90 | 179 | std::rethrow_exception(_ex); |
11fdf7f2 TL |
180 | } |
181 | } | |
1e59de90 TL |
182 | |
183 | /// Returns the default exception type (\ref abort_requested_exception) for this abort source. | |
184 | /// Overridable by derived classes. | |
185 | virtual std::exception_ptr get_default_exception() const noexcept { | |
186 | return make_exception_ptr(abort_requested_exception()); | |
187 | } | |
11fdf7f2 TL |
188 | }; |
189 | ||
190 | /// @} | |
191 | ||
192 | } |