aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp/sighandler/async_signals_handler.cpp
blob: ac3771f0d5d575db909f95dce91bc7f9389266e5 (plain) (blame)
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
#include "async_signals_handler.h"

#include <util/system/platform.h>

#if !defined(_win_)

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>

#include <unistd.h>

#if defined(_linux_)
#include <dlfcn.h>
#endif

#include <library/cpp/deprecated/atomic/atomic.h>
#include <util/system/defaults.h>
#include <util/system/event.h>
#include <util/system/rwlock.h>
#include <util/system/spinlock.h>
#include <util/system/thread.h>
#include <util/system/yassert.h>
#include <util/generic/hash.h>

namespace {
    volatile int SIGNAL_PIPE_WRITE_FD = 0; // will be initialized in ctor

    void WriteAllOrDie(const int fd, const void* buf, size_t bufsize) {
        size_t totalBytesWritten = 0;

        while (totalBytesWritten != bufsize) {
            const ssize_t result = write(fd, (const char*)buf + totalBytesWritten, bufsize - totalBytesWritten);

            Y_ABORT_UNLESS(result >= 0 || (result == -1 && errno == EINTR), "write failed: %s (errno = %d)", strerror(errno), errno);
            totalBytesWritten += static_cast<size_t>(result);
        }
    }

    void PipeWriterSignalHandler(int, siginfo_t* info, void*) {
        const ui8 signum = static_cast<ui8>(info->si_signo);

        WriteAllOrDie(SIGNAL_PIPE_WRITE_FD, &signum, 1);
    }

    // Handler for the "asynchronous" unix signals (those which can occur
    // at arbitrary point of execution and have no need to be reacted on instantly
    // and/or to preserve execution context at the point of interrupt).
    //
    // Async signals -- SIGHUP, SIGUSR1 (used to cause configuration files reread for example)
    // Sync signals -- fatal errors like SIGSEGV, SIGBUS...
    class TAsyncSignalsHandler {
    private:
        TThread Thread;
        int SignalPipeReadFd;
        typedef THolder<TEventHandler> TEventHandlerPtr;
        THashMap<int, TEventHandlerPtr> Handlers;
        TRWMutex HandlersLock;

        TAtomic ShouldDie;
        TSystemEvent DieEvent;

        static void* ThreadFunc(void* data) {
            reinterpret_cast<TAsyncSignalsHandler*>(data)->RealThreadFunc();

            return nullptr;
        }

        inline void RealThreadFunc() {
            for (;;) {
                ui8 signum;
                const ssize_t bytesRead = read(SignalPipeReadFd, &signum, 1);

                Y_ABORT_UNLESS(bytesRead >= 0 || (bytesRead == -1 && errno == EINTR), "read failed: %s (errno = %d)", strerror(errno), errno);

                if (AtomicAdd(ShouldDie, 0) != 0) {
                    DieEvent.Signal();

                    break;
                }

                if (bytesRead == 0) {
                    break;
                } else if (bytesRead == -1) {
                    continue;
                }

                {
                    TReadGuard dnd(HandlersLock);

                    const TEventHandlerPtr* handler = Handlers.FindPtr(signum);
                    Y_ABORT_UNLESS(handler && handler->Get(), "Async signal handler is not set, it's a bug!");
                    handler->Get()->Handle(signum);
                }
            }
        }

    public:
        TAsyncSignalsHandler()
            : Thread(TThread::TParams(ThreadFunc, this).SetName("sighandler"))
            , SignalPipeReadFd(0)
            , ShouldDie(0)
        {
            int filedes[2] = {-1};

#ifdef _linux_
            int result;

            {
                using pipe2_t = decltype(pipe2);
                pipe2_t* pipe2Ptr = (pipe2_t*)dlsym(RTLD_DEFAULT, "pipe2");

#if defined(_musl_)
                if (!pipe2Ptr) {
                    pipe2Ptr = pipe2;
                }
#endif

                if (pipe2Ptr) {
                    result = pipe2Ptr(filedes, O_CLOEXEC);
                } else {
                    result = -1;
                    errno = ENOSYS;
                }
            }

            if (result != 0 && errno == ENOSYS) { // linux older than 2.6.27 returns "not implemented"
#endif
                Y_ABORT_UNLESS(pipe(filedes) == 0, "pipe failed: %s (errno = %d)", strerror(errno), errno);

                SignalPipeReadFd = filedes[0];
                SIGNAL_PIPE_WRITE_FD = filedes[1];

                Y_ABORT_UNLESS(fcntl(SignalPipeReadFd, F_SETFD, FD_CLOEXEC) == 0, "fcntl failed: %s (errno = %d)", strerror(errno), errno);
                Y_ABORT_UNLESS(fcntl(SIGNAL_PIPE_WRITE_FD, F_SETFD, FD_CLOEXEC) == 0, "fcntl failed: %s (errno = %d)", strerror(errno), errno);
#ifdef _linux_
            } else {
                Y_ABORT_UNLESS(result == 0, "pipe2 failed: %s (errno = %d)", strerror(errno), errno);
                SignalPipeReadFd = filedes[0];
                SIGNAL_PIPE_WRITE_FD = filedes[1];
            }
#endif

            Thread.Start();
            Thread.Detach();
        }

        ~TAsyncSignalsHandler() {
            AtomicSwap(&ShouldDie, TAtomic(1));
            ui8 fakeSignal = 0;
            WriteAllOrDie(SIGNAL_PIPE_WRITE_FD, &fakeSignal, 1);

            DieEvent.WaitT(TDuration::Seconds(15));

            /* may cause VERIFY failure in signal handler, propably we should leave it to process clean procedure
        close(SIGNAL_PIPE_WRITE_FD);
        close(SignalPipeReadFd);
*/
        }

        bool DoInstall(int signum, THolder<TEventHandler> handler) {
            TWriteGuard dnd(HandlersLock);
            TEventHandlerPtr& ev = Handlers[signum];
            const bool ret = !ev;

            ev = std::move(handler);

            return ret;
        }

        void Install(int signum, THolder<TEventHandler> handler) {
            if (DoInstall(signum, std::move(handler))) {
                struct sigaction a;

                memset(&a, 0, sizeof(a));
                a.sa_sigaction = PipeWriterSignalHandler;
                a.sa_flags = SA_SIGINFO | SA_RESTART;

                Y_ABORT_UNLESS(!sigaction(signum, &a, nullptr), "sigaction failed: %s (errno = %d)", strerror(errno), errno);
            }
        }
    };

    // This pointer is never deleted - yeah, it's intended memory leak.
    // It is necessary to prevent problems when user's signal handler calls exit function
    // which destroys all global variables including this one.
    // It such situation we have 2 options:
    //  - wait for auxiliary thread to die - which will cause dead lock
    //  - destruct variable, ignoring thread - which will cause data corruption.
    std::atomic<TAsyncSignalsHandler*> SIGNALS_HANDLER = nullptr;
}

void SetAsyncSignalHandler(int signum, THolder<TEventHandler> handler) {
    static TAdaptiveLock lock;

    // Must be in HB with Handler's constructor.
    auto* currentHandler = SIGNALS_HANDLER.load(std::memory_order::acquire);

    if (Y_UNLIKELY(currentHandler == nullptr)) {
        TGuard dnd(lock);

        // If we read non-null here it means that we have a concurrent thread
        // unlocking the lock establishing strongly HB with us.
        // next line is sequenced before lock call thus relaxed is enough here.
        currentHandler = SIGNALS_HANDLER.load(std::memory_order::relaxed);

        if (currentHandler == nullptr) {
            // NEVERS GETS DESTROYED
            currentHandler = new TAsyncSignalsHandler();

            // Ensure HB with constructor for future readers.
            SIGNALS_HANDLER.store(currentHandler, std::memory_order::release);
        }
    }

    currentHandler->Install(signum, std::move(handler));
}

#else //_win_

void SetAsyncSignalHandler(int, THolder<TEventHandler>) {
    // TODO: it's really easy to port using _pipe, _read and _write, but it must be tested properly.
}

#endif

namespace {
    template <typename TFunc>
    class TFunctionEventHandler: public TEventHandler {
        TFunc Func;

    public:
        TFunctionEventHandler(TFunc func) {
            if (func)
                Func = func;
        }

        int Handle(int signum) override {
            if (Func) {
                Func(signum);
            }

            return 0;
        }
    };
}

void SetAsyncSignalHandler(int signum, void (*handler)(int)) {
    SetAsyncSignalHandler(signum, MakeHolder<TFunctionEventHandler<void (*)(int)>>(handler));
}

void SetAsyncSignalFunction(int signum, std::function<void(int)> func) {
    typedef std::function<void(int)> TFunc;
    SetAsyncSignalHandler(signum, MakeHolder<TFunctionEventHandler<TFunc>>(func));
}