1

I want to write an C++ programm which should wait for a linux signal (millisecond resolution), but I am not able to find a possibility to achieve this.

The following test code should terminate after 500ms, but it doesn't.

#include <iostream>
#include <csignal>
#include <unistd.h>
#include <chrono>
#include <future>

using namespace std::chrono_literals;

extern "C" void handler(int s) {

}

int main() {
    std::signal(SIGUSR1, handler);

    bool started = false;
    auto f = std::async(std::launch::async, [&] {
        auto start = std::chrono::high_resolution_clock::now();
        started = true;

        //usleep(1000000);
        sleep(1);
        //std::this_thread::sleep_for(1s);


        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now() - start).count() << "ms";

    });
    std::this_thread::sleep_for(500ms);
    std::raise(SIGUSR1);
}

Does anybody know how to fix this behaviour?

gerum
  • 974
  • 1
  • 8
  • 21
  • This is easily done using signal and timer file descriptors, however this is a fairly advanced topic. Start with Linux manual pages for timerfd and signalfd system calls, and work your way from there. – Sam Varshavchik Oct 23 '20 at 17:17
  • Does the program display anything? – ti7 Oct 23 '20 at 17:53

2 Answers2

0

The program returns to its normal operation after executing your signal handler. Sleep doesn't terminate because it's in a different thread to the one handling the signal as stark notes in the comments.

Instead, this answer C/C++: How to exit sleep() when an interrupt arrives? suggests using a mutex to block operation. This is made easier with try_lock_for

Also displaying the thread IDs (showing they are different threads), this could instead be:

#include <iostream>
#include <csignal>
#include <unistd.h>
#include <chrono>
#include <future>

using namespace std::chrono_literals;

std::timed_mutex mtx;

extern "C" void handler(int s) {
    std::cout << std::this_thread::get_id() << " signal handler"
        << std::endl;
    mtx.unlock();
}

int main() {
    std::signal(SIGUSR1, handler);
    auto start_outer = std::chrono::high_resolution_clock::now();

    mtx.lock();

    auto f = std::async(std::launch::async, [&] {
        auto start = std::chrono::high_resolution_clock::now();

        mtx.try_lock_for(std::chrono::milliseconds(1000));

        std::cout << std::this_thread::get_id() << " "
            << std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now() - start).count() << "ms"
            << std::endl;
    });

    std::this_thread::sleep_for(500ms);
    std::cout << std::this_thread::get_id() << " "
        << std::chrono::duration_cast<std::chrono::milliseconds>(
            std::chrono::high_resolution_clock::now() - start_outer).count() << "ms"
        << std::endl;
    std::raise(SIGUSR1);
}

build, run, output

% g++ -std=c++17 test.cpp && ./a.out
0x112cb9dc0 506ms
0x112cb9dc0 signal handler
0x700009e86000 506ms
ti7
  • 16,375
  • 6
  • 40
  • 68
  • Thats correct, but the sleep should be abort by the signal, so that it will complete faster – gerum Oct 23 '20 at 17:15
  • @gerum A low-level system call may be interrupted by a signal, but the high-level `sleep_for` isn't. For example, you can see libstdc++'s implementation [here](https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/thread#L378-L397) handles the `EINTR` and continues sleeping for any remaining time. – Miles Budnek Oct 23 '20 at 18:14
  • I would prefer to not use undefined behavior and I see two possible sources of UB in your code. First you unlock the mutex in the signal_handler, but I'm not sure if that is the same thread as the main thread, where you must unlock the mutex. And you use the mutex in the signal handler, but I don't think, that using C++ classes in extern "C" functions is well defined. – gerum Oct 23 '20 at 19:21
  • @MilesBudnek Yes, I the this_thread sleep was only added for completeness, I don't really expect, that that works, but the version with sleep or usleep should work. – gerum Oct 23 '20 at 19:25
  • Side note: There's some risky stuff in that signal handler. @gerum just pegged unlocking from a different context, but in addition what you can safely do in a signal handler, at least in *nix systems is [well defined and very limited](https://man7.org/linux/man-pages/man7/signal-safety.7.html). – user4581301 Oct 23 '20 at 19:32
0

With the idea of ti7 and user4581301 I finally found a solution.

Using the idea of an mutex in the signal_handler but restricted to the set of allowed system calls, I use a semaphore.

sem_t *sem_g = nullptr;
extern "C" void handler(int s) {
    if (sem_g)
        sem_post(sem_g);
}

int main() {
    sem_t sem = {};
    sem_init(&sem, 0, 0);
    sem_g = &sem;

    std::signal(SIGUSR1, handler);


    auto f = std::async(std::launch::async, [&] {
        auto start = std::chrono::high_resolution_clock::now();

        auto time_point = std::chrono::system_clock::now() + 10s;
        auto duration = time_point.time_since_epoch();
        auto secs = std::chrono::duration_cast<std::chrono::seconds>(duration);
        auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(duration - secs);

        timespec t{secs.count(), nanos.count()};

        auto r = sem_timedwait(&sem, &t);

        std::cout << std::chrono::duration_cast<std::chrono::milliseconds>(
                std::chrono::high_resolution_clock::now() - start).count() << "ms";

    });
    std::this_thread::sleep_for(500ms);
    std::raise(SIGUSR1);
}
gerum
  • 974
  • 1
  • 8
  • 21