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 }