2020#include < atomic>
2121#include < mutex>
2222#include < sstream>
23+ #include < thread>
2324#include < utility>
2425
2526#include " arrow/result.h"
27+ #include " arrow/util/atfork_internal.h"
2628#include " arrow/util/io_util.h"
2729#include " arrow/util/logging.h"
30+ #include " arrow/util/mutex.h"
2831#include " arrow/util/visibility.h"
2932
3033namespace arrow {
@@ -33,7 +36,9 @@ namespace arrow {
3336#error Lock-free atomic int required for signal safety
3437#endif
3538
39+ using internal::AtForkHandler;
3640using internal::ReinstateSignalHandler;
41+ using internal::SelfPipe;
3742using internal::SetSignalHandler;
3843using internal::SignalHandler;
3944
@@ -99,16 +104,58 @@ Status StopToken::Poll() const {
99104
100105namespace {
101106
102- struct SignalStopState {
107+ struct SignalStopState : public std ::enable_shared_from_this<SignalStopState> {
103108 struct SavedSignalHandler {
104109 int signum;
105110 SignalHandler handler;
106111 };
107112
113+ // NOTE: shared_from_this() doesn't work from constructor
114+ void Init () {
115+ // XXX this pattern appears in several places, factor it out?
116+ atfork_handler_ = std::make_shared<AtForkHandler>(
117+ /* before=*/
118+ [weak_self = std::weak_ptr<SignalStopState>(shared_from_this ())] {
119+ auto self = weak_self.lock ();
120+ if (self) {
121+ self->BeforeFork ();
122+ }
123+ return self;
124+ },
125+ /* parent_after=*/
126+ [](std::any token) {
127+ auto self = std::any_cast<std::shared_ptr<SignalStopState>>(std::move (token));
128+ self->ParentAfterFork ();
129+ },
130+ /* child_after=*/
131+ [](std::any token) {
132+ auto self = std::any_cast<std::shared_ptr<SignalStopState>>(std::move (token));
133+ self->ChildAfterFork ();
134+ });
135+ RegisterAtFork (atfork_handler_);
136+ }
137+
108138 Status RegisterHandlers (const std::vector<int >& signals) {
139+ std::lock_guard<std::mutex> lock (mutex_);
109140 if (!saved_handlers_.empty ()) {
110141 return Status::Invalid (" Signal handlers already registered" );
111142 }
143+ if (!self_pipe_) {
144+ // Make sure the self-pipe is initialized
145+ // (NOTE: avoid std::atomic_is_lock_free() which may require libatomic)
146+ #if ATOMIC_POINTER_LOCK_FREE != 2
147+ return Status::NotImplemented (
148+ " Cannot setup signal StopSource because atomic pointers are not "
149+ " lock-free on this platform" );
150+ #else
151+ ARROW_ASSIGN_OR_RAISE (self_pipe_, SelfPipe::Make (/* signal_safe=*/ true ));
152+ #endif
153+ }
154+ if (!signal_receiving_thread_) {
155+ // Spawn thread for receiving signals
156+ SpawnSignalReceivingThread ();
157+ }
158+ self_pipe_ptr_.store (self_pipe_.get ());
112159 for (int signum : signals) {
113160 ARROW_ASSIGN_OR_RAISE (auto handler,
114161 SetSignalHandler (signum, SignalHandler{&HandleSignal}));
@@ -118,78 +165,135 @@ struct SignalStopState {
118165 }
119166
120167 void UnregisterHandlers () {
168+ std::lock_guard<std::mutex> lock (mutex_);
169+ self_pipe_ptr_.store (nullptr );
121170 auto handlers = std::move (saved_handlers_);
122171 for (const auto & h : handlers) {
123172 ARROW_CHECK_OK (SetSignalHandler (h.signum , h.handler ).status ());
124173 }
125174 }
126175
127176 ~SignalStopState () {
177+ atfork_handler_.reset ();
128178 UnregisterHandlers ();
129179 Disable ();
180+ if (signal_receiving_thread_) {
181+ // Tell the receiving thread to stop
182+ auto st = self_pipe_->Shutdown ();
183+ ARROW_WARN_NOT_OK (st, " Failed to shutdown self-pipe" );
184+ if (st.ok ()) {
185+ signal_receiving_thread_->join ();
186+ } else {
187+ signal_receiving_thread_->detach ();
188+ }
189+ }
130190 }
131191
132- StopSource* stop_source () { return stop_source_.get (); }
192+ StopSource* stop_source () {
193+ std::lock_guard<std::mutex> lock (mutex_);
194+ return stop_source_.get ();
195+ }
133196
134- bool enabled () { return stop_source_ != nullptr ; }
197+ bool enabled () {
198+ std::lock_guard<std::mutex> lock (mutex_);
199+ return stop_source_ != nullptr ;
200+ }
135201
136202 void Enable () {
137- // Before creating a new StopSource, delete any lingering reference to
138- // the previous one in the trash can. See DoHandleSignal() for details.
139- EmptyTrashCan ();
140- std::atomic_store (&stop_source_, std::make_shared<StopSource>());
203+ std::lock_guard<std::mutex> lock (mutex_);
204+ stop_source_ = std::make_shared<StopSource>();
141205 }
142206
143- void Disable () { std::atomic_store (&stop_source_, NullSource ()); }
207+ void Disable () {
208+ std::lock_guard<std::mutex> lock (mutex_);
209+ stop_source_.reset ();
210+ }
144211
145- static SignalStopState* instance () { return &instance_; }
212+ static SignalStopState* instance () {
213+ static std::shared_ptr<SignalStopState> instance = []() {
214+ auto ptr = std::make_shared<SignalStopState>();
215+ ptr->Init ();
216+ return ptr;
217+ }();
218+ return instance.get ();
219+ }
146220
147221 private:
148- // For readability
149- std::shared_ptr<StopSource> NullSource () { return nullptr ; }
150-
151- void EmptyTrashCan () { std::atomic_store (&trash_can_, NullSource ()); }
222+ void SpawnSignalReceivingThread () {
223+ signal_receiving_thread_ = std::make_unique<std::thread>(ReceiveSignals, self_pipe_);
224+ }
152225
153- static void HandleSignal (int signum) { instance_.DoHandleSignal (signum); }
226+ static void HandleSignal (int signum) {
227+ auto self = instance ();
228+ if (self) {
229+ self->DoHandleSignal (signum);
230+ }
231+ }
154232
155233 void DoHandleSignal (int signum) {
156234 // async-signal-safe code only
157- auto source = std::atomic_load (&stop_source_);
158- if (source) {
159- source->RequestStopFromSignal (signum);
160- // Disable() may have been called in the meantime, but we can't
161- // deallocate a shared_ptr here, so instead move it to a "trash can".
162- // This minimizes the possibility of running a deallocator here,
163- // however it doesn't entirely preclude it.
164- //
165- // Possible case:
166- // - a signal handler (A) starts running, fetches the current source
167- // - Disable() then Enable() are called, emptying the trash can and
168- // replacing the current source
169- // - a signal handler (B) starts running, fetches the current source
170- // - signal handler A resumes, moves its source (the old source) into
171- // the trash can (the only remaining reference)
172- // - signal handler B resumes, moves its source (the current source)
173- // into the trash can. This triggers deallocation of the old source,
174- // since the trash can had the only remaining reference to it.
175- //
176- // This case should be sufficiently unlikely, but we cannot entirely
177- // rule it out. The problem might be solved properly with a lock-free
178- // linked list of StopSources.
179- std::atomic_store (&trash_can_, std::move (source));
235+ SelfPipe* self_pipe = self_pipe_ptr_.load ();
236+ if (self_pipe) {
237+ self_pipe->Send (/* payload=*/ signum);
180238 }
181239 ReinstateSignalHandler (signum, &HandleSignal);
182240 }
183241
184- std::shared_ptr<StopSource> stop_source_;
185- std::shared_ptr<StopSource> trash_can_;
242+ static void ReceiveSignals (std::shared_ptr<SelfPipe> self_pipe) {
243+ // Wait for signals on the self-pipe and propagate them to the current StopSource
244+ DCHECK (self_pipe);
245+ while (true ) {
246+ auto maybe_payload = self_pipe->Wait ();
247+ if (maybe_payload.status ().IsInvalid ()) {
248+ // Pipe shut down
249+ return ;
250+ }
251+ if (!maybe_payload.ok ()) {
252+ maybe_payload.status ().Warn ();
253+ return ;
254+ }
255+ const int signum = static_cast <int >(maybe_payload.ValueUnsafe ());
256+ instance ()->ReceiveSignal (signum);
257+ }
258+ }
186259
187- std::vector<SavedSignalHandler> saved_handlers_;
260+ void ReceiveSignal (int signum) {
261+ std::lock_guard<std::mutex> lock (mutex_);
262+ if (stop_source_) {
263+ stop_source_->RequestStopFromSignal (signum);
264+ }
265+ }
188266
189- static SignalStopState instance_;
190- };
267+ // At-fork handlers
268+
269+ void BeforeFork () { mutex_.lock (); }
270+
271+ void ParentAfterFork () { mutex_.unlock (); }
191272
192- SignalStopState SignalStopState::instance_{};
273+ void ChildAfterFork () {
274+ new (&mutex_) std::mutex;
275+ // Leak previous thread, as it has become invalid.
276+ // We can't spawn a new one here as it would have unfortunate side effects;
277+ // especially in the frequent context of a fork+exec.
278+ // (for example the Python subprocess module closes all fds before calling exec)
279+ ARROW_UNUSED (signal_receiving_thread_.release ());
280+ // Make internal state consistent: with no listening thread, we shouldn't
281+ // feed the self-pipe from the signal handler.
282+ UnregisterHandlers ();
283+ }
284+
285+ std::mutex mutex_;
286+ std::vector<SavedSignalHandler> saved_handlers_;
287+ std::shared_ptr<StopSource> stop_source_;
288+ std::unique_ptr<std::thread> signal_receiving_thread_;
289+ std::shared_ptr<AtForkHandler> atfork_handler_;
290+
291+ // For signal handler interaction
292+ std::shared_ptr<SelfPipe> self_pipe_;
293+ // Raw atomic pointer, as atomic load/store of a shared_ptr may not be lock-free
294+ // (it is not on libstdc++).
295+ std::atomic<SelfPipe*> self_pipe_ptr_;
296+ };
193297
194298} // namespace
195299
0 commit comments