diff --git a/slsDetectorGui/src/qDetectorMain.cpp b/slsDetectorGui/src/qDetectorMain.cpp index 438707c36..be08272d2 100644 --- a/slsDetectorGui/src/qDetectorMain.cpp +++ b/slsDetectorGui/src/qDetectorMain.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "sls/Version.h" #include @@ -100,6 +101,12 @@ int main(int argc, char **argv) { } QApplication app(argc, argv); + // Use the same (platform independent) widget style everywhere so that the + // gui looks the same on linux and macOS. Fusion is built into Qt. + // Can still be overridden with -style or QT_STYLE_OVERRIDE. + if (QStyleFactory::keys().contains("Fusion", Qt::CaseInsensitive)) { + app.setStyle(QStyleFactory::create("Fusion")); + } try { sls::qDetectorMain det(multiId, fname, isDeveloper); det.show(); diff --git a/slsReceiverSoftware/src/ClientInterface.cpp b/slsReceiverSoftware/src/ClientInterface.cpp index a88688b77..7f929fe00 100644 --- a/slsReceiverSoftware/src/ClientInterface.cpp +++ b/slsReceiverSoftware/src/ClientInterface.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-other // Copyright (C) 2021 Contributors to the SLS Detector Package #include "ClientInterface.h" +#include "sls/ClientSocket.h" #include "sls/ServerSocket.h" #include "sls/StaticVector.h" #include "sls/ToString.h" @@ -34,6 +35,16 @@ ClientInterface::~ClientInterface() { killTcpThread = true; LOG(logINFO) << "Shutting down TCP Socket on port " << portNumber; server.shutdown(); +#ifdef __APPLE__ + // On macOS shutdown() on a listening socket fails with ENOTCONN and does + // not wake up the blocking accept() in the tcp thread. Connect to our own + // port so that accept() returns and the thread sees killTcpThread. + try { + ReceiverSocket wakeup("localhost", portNumber); + } catch (const RuntimeError &e) { + LOG(logWARNING) << "Could not wake up TCP server thread: " << e.what(); + } +#endif LOG(logDEBUG) << "TCP Socket closed on port " << portNumber; tcpThread->join(); } @@ -82,6 +93,9 @@ void ClientInterface::startTCPServer() { while (!killTcpThread) { try { auto socket = server.accept(); + if (killTcpThread) { + break; + } try { verifyLock(); // lock should be checked only for set (not get), // Move it back? diff --git a/slsReceiverSoftware/src/FrameSynchronizerApp.cpp b/slsReceiverSoftware/src/FrameSynchronizerApp.cpp index 014ab0d10..9a6ae0559 100644 --- a/slsReceiverSoftware/src/FrameSynchronizerApp.cpp +++ b/slsReceiverSoftware/src/FrameSynchronizerApp.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include //wait #include @@ -489,19 +488,8 @@ void GetDataCallback(slsDetectorDefs::sls_receiver_header &header, stat->available.release(); } -std::vector semaphores; - -/** - * Control+C Interrupt Handler - * to let all the processes know to exit properly - * Only the main thread will call this handler - */ -void sigInterruptHandler(int p) { - (void)signal; // suppress unused warning if needed - for (auto &s : semaphores) { - sem_post(&s); - } -} +/** Released once per receiver thread by the main thread on Ctrl+C */ +sls::counting_semaphore<> stopSemaphore{0}; int main(int argc, char *argv[]) { CommandLineOptions cli(AppType::FrameSynchronizer); @@ -519,16 +507,12 @@ int main(int argc, char *argv[]) { LOG(sls::logINFOBLUE) << "Current Process [ Tid: " << sls::getThreadId() << ']'; - // close files on ctrl+c - sls::setupSignalHandler(SIGINT, sigInterruptHandler); + // close files on ctrl+c: SIGINT is blocked (before any thread is created, + // so they all inherit the mask) and waited for in the main thread + sls::blockSignal(SIGINT); // handle locally on socket crash sls::setupSignalHandler(SIGPIPE, SIG_IGN); - semaphores.resize(f.numReceivers); - for (auto &s : semaphores) { - sem_init(&s, 0, 0); - } - FrameStatus stat{true, false, f.numReceivers}; // store pointer for signal handler global_frame_status = &stat; @@ -540,9 +524,7 @@ int main(int argc, char *argv[]) { std::exception_ptr threadException = nullptr; for (int i = 0; i != f.numReceivers; ++i) { uint16_t port = f.port + i; - sem_t *semaphore = &semaphores[i]; - threads.emplace_back([i, semaphore, port, user_data, - &threadException]() { + threads.emplace_back([i, port, user_data, &threadException]() { LOG(sls::logINFOBLUE) << "Thread " << i << " [ Tid: " << sls::getThreadId() << ']'; try { @@ -555,24 +537,27 @@ int main(int argc, char *argv[]) { user_data); /** - as long as no Ctrl+C */ - // each child shares the common semaphore - sem_wait(semaphore); + stopSemaphore.acquire(); } catch (...) { - // capture exception and raise SIGINT to exit gracefully + // capture exception and send SIGINT to the process (not + // raise(), which would target this thread where it is + // blocked) so the main thread shuts everything down threadException = std::current_exception(); - raise(SIGINT); + kill(getpid(), SIGINT); } LOG(sls::logINFOBLUE) << "Exiting Thread " << i << " [ Tid: " << sls::getThreadId() << " ]"; }); } + LOG(sls::logINFO) << "[ Press \'Ctrl+c\' to exit ]"; + sls::waitForSignal(SIGINT); + stopSemaphore.release(f.numReceivers); + for (auto &t : threads) { t.join(); } - for (auto &s : semaphores) - sem_destroy(&s); cleanup(); { diff --git a/slsReceiverSoftware/src/MultiReceiverApp.cpp b/slsReceiverSoftware/src/MultiReceiverApp.cpp index 180b8a084..958f04218 100644 --- a/slsReceiverSoftware/src/MultiReceiverApp.cpp +++ b/slsReceiverSoftware/src/MultiReceiverApp.cpp @@ -13,7 +13,6 @@ #include //SIGINT #include -#include #include //wait #include @@ -125,18 +124,6 @@ void GetData(slsDetectorDefs::sls_receiver_header &header, // imageSize = 26000; } -sem_t semaphore; - -/** - * Control+C Interrupt Handler - * to let all the processes know to exit properly - * All child processes will call the handler (parent process set to ignore) - */ -void sigInterruptHandler(int signal) { - (void)signal; // suppress unused warning if needed - sem_post(&semaphore); -} - int main(int argc, char *argv[]) { CommandLineOptions cli(AppType::MultiReceiver); @@ -154,13 +141,12 @@ int main(int argc, char *argv[]) { LOG(sls::logINFOBLUE) << "Current Process [ Tid: " << sls::getThreadId() << ']'; - // close files on ctrl+c - sls::setupSignalHandler(SIGINT, sigInterruptHandler); + // close files on ctrl+c: SIGINT is blocked before forking so that every + // child process inherits the mask and can wait for it synchronously + sls::blockSignal(SIGINT); // handle locally on socket crash sls::setupSignalHandler(SIGPIPE, SIG_IGN); - sem_init(&semaphore, 1, 0); - /** - loop over receivers */ for (int i = 0; i < m.numReceivers; ++i) { @@ -206,15 +192,12 @@ int main(int argc, char *argv[]) { } /** - as long as no Ctrl+C */ - // each child process gets a copy of the semaphore - sem_wait(&semaphore); - sem_destroy(&semaphore); + sls::waitForSignal(SIGINT); LOG(sls::logINFOBLUE) << "Exiting Child Process [ Tid: " << sls::getThreadId() << ']'; exit(EXIT_SUCCESS); } catch (...) { - sem_destroy(&semaphore); LOG(sls::logINFOBLUE) << "Exiting Child Process [ Tid: " << sls::getThreadId() << " ]"; diff --git a/slsReceiverSoftware/src/ReceiverApp.cpp b/slsReceiverSoftware/src/ReceiverApp.cpp index 9a382b577..30f046282 100644 --- a/slsReceiverSoftware/src/ReceiverApp.cpp +++ b/slsReceiverSoftware/src/ReceiverApp.cpp @@ -11,20 +11,8 @@ #include "sls/thread_utils.h" #include //SIGINT -#include #include -sem_t semaphore; - -/** - * Control+C Interrupt Handler - * to let all the other process know to exit properly - */ -void sigInterruptHandler(int signal) { - (void)signal; // suppress unused warning if needed - sem_post(&semaphore); -} - int main(int argc, char *argv[]) { CommandLineOptions cli(AppType::SingleReceiver); @@ -42,20 +30,17 @@ int main(int argc, char *argv[]) { LOG(sls::logINFOBLUE) << "Current Process [ Tid: " << sls::getThreadId() << " ]"; - // close files on ctrl+c - sls::setupSignalHandler(SIGINT, sigInterruptHandler); + // close files on ctrl+c: SIGINT is blocked (before the receiver threads + // are created, so they inherit the mask) and waited for synchronously + sls::blockSignal(SIGINT); // handle locally on socket crash sls::setupSignalHandler(SIGPIPE, SIG_IGN); - sem_init(&semaphore, 1, 0); - try { sls::Receiver r(o.port); LOG(sls::logINFO) << "[ Press \'Ctrl+c\' to exit ]"; - sem_wait(&semaphore); - sem_destroy(&semaphore); + sls::waitForSignal(SIGINT); } catch (...) { - sem_destroy(&semaphore); LOG(sls::logINFOBLUE) << "Exiting [ Tid: " << sls::getThreadId() << " ]"; throw; diff --git a/slsSupportLib/include/sls/network_utils.h b/slsSupportLib/include/sls/network_utils.h index ef7e52489..c9eb9bc22 100644 --- a/slsSupportLib/include/sls/network_utils.h +++ b/slsSupportLib/include/sls/network_utils.h @@ -91,4 +91,13 @@ IpAddr InterfaceNameToIp(const std::string &ifn); void validatePortNumber(uint16_t port); void validatePortRange(uint16_t startPort, int numPorts); void setupSignalHandler(int signal, void (*handler)(int)); + +/** Block a signal in the calling thread. Threads created afterwards (and + * child processes) inherit the mask, so call this before spawning them. */ +void blockSignal(int signal); + +/** Wait synchronously until the (previously blocked) signal is delivered to + * the process. Replaces the signal handler + unnamed POSIX semaphore + * (sem_init) pattern, which is not implemented on macOS. */ +void waitForSignal(int signal); } // namespace sls diff --git a/slsSupportLib/src/UdpRxSocket.cpp b/slsSupportLib/src/UdpRxSocket.cpp index 2cb8c079c..f47bb58b9 100644 --- a/slsSupportLib/src/UdpRxSocket.cpp +++ b/slsSupportLib/src/UdpRxSocket.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -91,5 +92,35 @@ void UdpRxSocket::setBufferSize(int size) { void UdpRxSocket::Shutdown() { // not closing yet on purpose, but read gives -1 shutdown(sockfd_, SHUT_RDWR); +#ifdef __APPLE__ + // On macOS shutdown() on an unconnected UDP socket fails with ENOTCONN + // and does not wake up a blocking recvfrom(). Send an empty datagram to + // our own port instead: it is read as 0 bytes, so ReceivePacket returns + // false and the listener thread sees that the socket is no longer alive. + sockaddr_storage addr{}; + socklen_t addrlen = sizeof(addr); + if (getsockname(sockfd_, reinterpret_cast(&addr), &addrlen) == + -1) { + return; + } + if (addr.ss_family == AF_INET) { + auto *a = reinterpret_cast(&addr); + if (a->sin_addr.s_addr == htonl(INADDR_ANY)) + a->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } else if (addr.ss_family == AF_INET6) { + auto *a = reinterpret_cast(&addr); + if (IN6_IS_ADDR_UNSPECIFIED(&a->sin6_addr)) + a->sin6_addr = in6addr_loopback; + } else { + return; + } + int wakefd = socket(addr.ss_family, SOCK_DGRAM, 0); + if (wakefd == -1) { + return; + } + sendto(wakefd, nullptr, 0, 0, reinterpret_cast(&addr), + addrlen); + close(wakefd); +#endif } } // namespace sls \ No newline at end of file diff --git a/slsSupportLib/src/network_utils.cpp b/slsSupportLib/src/network_utils.cpp index c8f92732d..af3b5485a 100644 --- a/slsSupportLib/src/network_utils.cpp +++ b/slsSupportLib/src/network_utils.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -14,6 +15,10 @@ #include #include #include +#ifdef __APPLE__ +#include +#endif +#include #include #include #include @@ -179,7 +184,33 @@ IpAddr InterfaceNameToIp(const std::string &ifn) { MacAddr InterfaceNameToMac(const std::string &inf) { #ifdef __APPLE__ - throw RuntimeError("InterfaceNameToMac not implemented on macOS yet"); + // No SIOCGIFHWADDR on macOS: the hardware address is the AF_LINK entry + // that getifaddrs() returns for the interface + struct ifaddrs *ifaddr = nullptr; + if (getifaddrs(&ifaddr) == -1) { + throw RuntimeError("getifaddrs failed [" + + std::string(strerror(errno)) + ']'); + } + MacAddr result{}; + for (auto *ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == nullptr || ifa->ifa_addr->sa_family != AF_LINK || + inf != ifa->ifa_name) { + continue; + } + auto *sdl = reinterpret_cast(ifa->ifa_addr); + if (sdl->sdl_alen != 6) { + continue; // not an ethernet style address (e.g. utun, lo0) + } + auto *hw = reinterpret_cast(LLADDR(sdl)); + uint64_t addr = 0; + for (int j = 0; j < 6; ++j) { + addr = (addr << 8) | hw[j]; + } + result = MacAddr(addr); + break; + } + freeifaddrs(ifaddr); + return result; #else // TODO! Copied from genericSocket needs to be refactored! @@ -234,4 +265,29 @@ void setupSignalHandler(int signal, void (*handler)(int)) { std::string(strsignal(signal))); } } + +void blockSignal(int signal) { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, signal); + if (pthread_sigmask(SIG_BLOCK, &set, nullptr) != 0) { + throw RuntimeError("Could not block " + std::string(strsignal(signal))); + } +} + +void waitForSignal(int signal) { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, signal); + int received = 0; + int ret = 0; + do { + ret = sigwait(&set, &received); + } while (ret == EINTR); + if (ret != 0) { + throw RuntimeError("Could not wait for " + + std::string(strsignal(signal)) + ": " + + strerror(ret)); + } +} } // namespace sls