1 /// Manage signals as reactor callbacks
2 module mecca.reactor.platform.linux.signals;
3 
4 // Licensed under the Boost license. Full copyright information in the AUTHORS file
5 
6 version (linux):
7 package(mecca.reactor.platform):
8 
9 import core.sys.posix.signal;
10 import core.sys.posix.unistd;
11 import core.sys.linux.sys.signalfd;
12 public import core.sys.linux.sys.signalfd : signalfd_siginfo;
13 
14 import std.traits : Parameters;
15 
16 import mecca.lib.exception;
17 import mecca.lib.reflection;
18 import mecca.lib.time : Timeout;
19 import mecca.log;
20 public import mecca.platform.os : OSSignal;
21 import mecca.platform.os;
22 import mecca.reactor.io.fd;
23 import mecca.reactor;
24 import mecca.reactor.subsystems.poller;
25 
26 // Definitions missing from the phobos headers or lacking nothrow @nogc
27 extern(C) private nothrow @trusted @nogc {
28     int signalfd(int, const(sigset_t)*, int);
29     int sigemptyset(sigset_t*);
30     int sigaddset(sigset_t*, int);
31     int sigismember(in sigset_t*, int);
32     int sigdelset(sigset_t*, int);
33     int sigprocmask(int, in sigset_t*, sigset_t*);
34 }
35 
36 // @safe wrappers
37 private {
38     @notrace int signalfd (int __fd, const ref sigset_t __mask, int __flags) nothrow @trusted @nogc {
39         return signalfd( __fd, &__mask, __flags );
40     }
41 
42     @notrace int sigemptyset(ref sigset_t set) nothrow @trusted @nogc {
43         return sigemptyset(&set);
44     }
45 
46     @notrace int sigaddset(ref sigset_t set, int signum) nothrow @trusted @nogc {
47         return sigaddset( &set, signum );
48     }
49 
50     @notrace int sigismember(ref sigset_t set, int signum) nothrow @trusted @nogc {
51         return sigismember(&set, signum);
52     }
53     @notrace int sigdelset(ref sigset_t set, int signum) nothrow @trusted @nogc {
54         return sigdelset( &set, signum );
55     }
56     @notrace int sigprocmask(int op, const ref sigset_t newMask) nothrow @trusted @nogc {
57         return sigprocmask( op, &newMask, null );
58     }
59     @notrace int sigprocmask(int op, const ref sigset_t newMask, ref sigset_t oldMask) nothrow @trusted @nogc {
60         return sigprocmask( op, &newMask, &oldMask );
61     }
62 }
63 
64 /**
65  * A singleton managing registered signals
66  */
67 package(mecca.reactor.platform) struct ReactorSignal {
68     alias SignalHandler = void delegate(OSSignal) @system;
69 private:
70     enum BATCH_SIZE = 16; // How many signals to handle with one syscall
71 
72     enum SignalFdFlags = SFD_NONBLOCK|SFD_CLOEXEC;
73     ReactorFD signalFd;
74     sigset_t signals;
75     FiberHandle fiberHandle;
76 
77     SignalHandler[NUM_SIGS] handlers;
78 public:
79     /**
80      * Must be called prior to registering any signals.
81      *
82      * Must be called after the reactor is open, and also after ReactorFS.openReactor has already been called.
83      */
84     void _open() @safe @nogc {
85         ASSERT!"ReactorSignal.open called without first calling ReactorFD.openReactor"(poller.isOpen);
86         sigemptyset(signals);
87         handlers[] = null;
88         int fd = signalfd(-1, signals, SignalFdFlags);
89         errnoEnforceNGC(fd >= 0, "signalfd creation failed");
90 
91         signalFd = ReactorFD(fd, true);
92 
93         fiberHandle = theReactor.spawnFiber(&fiberMainWrapper, &this);
94     }
95 
96     /// Call this when shutting down the reactor. Mostly necessary for unit tests
97     void _close() @safe @nogc {
98         verifyOpen();
99         signalFd.close();
100         errnoEnforceNGC( sigprocmask( SIG_UNBLOCK, &signals, null )>=0, "sigprocmask unblocking signals failed" );
101         ASSERT!"_close called while reactor still running"( !fiberHandle.isValid );
102         // theReactor.throwInFiber!TerminateFiber(fiberHandle);
103     }
104 
105     /**
106      * register a signal handler
107      *
108      * Register a handler for a specific signal. The signal must not already be handled, either through ReactorSignal or
109      * otherwise.
110      *
111      * Params:
112      * signum = the signal to be handled
113      * handler = a delegate to be called when the signal arrives
114      */
115     void registerHandler(OSSignal signum, SignalHandler handler) @trusted @nogc {
116         verifyOpen();
117         ASSERT!"registerHandler called with invalid signal %s"(signum<NUM_SIGS || signum<=0, signum);
118         ASSERT!"signal %s registered twice"(handlers[signum] is null, signum);
119         DBG_ASSERT!"signal %s has no handle but is set in sigmask"(sigismember(signals, signum)!=1, signum);
120 
121         sigaddset(signals, signum);
122         scope(failure) sigdelset(signals, signum);
123 
124         errnoEnforceNGC( signalFd.osCall!(core.sys.linux.sys.signalfd.signalfd)(&signals, SignalFdFlags)>=0,
125                 "Registering new signal handler failed" );
126         sigset_t oldSigMask;
127         sigprocmask(SIG_BLOCK, signals, oldSigMask);
128         ASSERT!"Registered signal %s already masked"(sigismember(oldSigMask, signum)!=1, signum);
129 
130         handlers[signum] = handler;
131     }
132 
133     void registerHandler(string sig, T)(T handler) @trusted {
134         registerHandler(__traits(getMember, OSSignal, sig), (_){handler();});
135     }
136 
137     void unregisterHandler(OSSignal signum) @trusted @nogc {
138         verifyOpen();
139         ASSERT!"registerHandler called with invalid signal %s"(signum<NUM_SIGS || signum<=0, signum);
140         ASSERT!"signal %s not registered"(handlers[signum] !is null, signum);
141         DBG_ASSERT!"signal %s has a handle but is not set sigmask"(sigismember(signals, signum)==1, signum);
142 
143         sigset_t clearMask;
144         sigemptyset(clearMask);
145         sigaddset(clearMask, signum);
146         errnoEnforceNGC( sigprocmask( SIG_UNBLOCK, clearMask )>=0, "sigprocmask unblocking signal failed" );
147         sigdelset( signals, signum );
148         errnoEnforceNGC( signalFd.osCall!(core.sys.linux.sys.signalfd.signalfd)(&signals, SignalFdFlags)>=0,
149                 "Deregistering signal handler failed" );
150         handlers[signum] = null;
151     }
152 
153     void unregisterHandler(string sig)() @trusted @nogc {
154         unregisterHandler(__traits(getMember, OSSignal, sig));
155     }
156 
157 
158 private:
159     class TerminateFiber : Exception {
160         this() nothrow @safe @nogc {
161             super("ReactorSignal fiber terminator exception");
162         }
163     }
164 
165     static void fiberMainWrapper(ReactorSignal* rs) @safe {
166         rs.fiberMain();
167     }
168 
169     void fiberMain() @trusted {
170         try {
171             while(true) {
172                 // XXX Consider placing the array in the struct, so it's not on the stack
173                 signalfd_siginfo[BATCH_SIZE] info;
174                 ssize_t readSize = signalFd.blockingCall!(read)(
175                         Direction.Read, &info, typeof(info).sizeof, Timeout.infinite);
176                 ASSERT!"read from signalfd returned misaligned size %s, expected a multiple of %s"(
177                         (readSize%signalfd_siginfo.sizeof) == 0, readSize, signalfd_siginfo.sizeof);
178 
179                 theReactor.enterCriticalSection();
180                 scope(exit) theReactor.leaveCriticalSection();
181 
182                 bool[NUM_SIGS] handleMask;
183                 size_t numElements = readSize / signalfd_siginfo.sizeof;
184                 foreach( ref siginfo; info[0..numElements] ) {
185                     auto signum = cast(OSSignal)siginfo.ssi_signo;
186                     if( handleMask[signum] ) {
187                         INFO!"Squelching repeated signal %s that happened multiple times"(signum); // That's temporal, not spatial, squelching
188                         continue;
189                     }
190 
191                     handleMask[signum] = true;
192                     DBG_ASSERT!"Received signal %s with no handler"(handlers[signum] !is null, signum);
193                     handlers[signum](signum);
194                 }
195             }
196         } catch(TerminateFiber ex) {
197             INFO!"ReactorSignal fiber terminated"();
198         }
199     }
200 
201     @property void verifyOpen() const nothrow @safe @nogc {
202         ASSERT!"ReactorSignal.close called without first calling open"(signalFd.isValid);
203     }
204 }