Search code examples
c++multithreadingioroscan-bus

How to exit a C++ program with Ctrl+C, if I/O read() is in blocking stage?


I am working on ROS environment, and trying to read CANBUS on a parallel thread. I initialized canbus in main thread because I want to make sure that CAN cables are connected. By initializing, I mean setsockopt(), ioctl(), bind() to configure the socket.

void readCanbus(int soktId) {
    while(true)
        int nbytes = read(soktId, ...);
}

int main() {
    int soktId;
    someSocketSetupFn(soktId);

    std::thread t(readCanbus, soktId);

    t.join();
}

Problem: If there is no incoming CAN messages, read() is blocked. Ctrl+C doesn't terminate the C++11 Program.

How can I make the read() terminate and so the whole program?

Terminate thread c++11 blocked on read This post proposed a solution for POSIX. I am working on ubuntu16.04.


Solution

  • If you want to mimic the interrupt behavior on the other threads, those threads have to allow interruption, and your signal handling thread has to deliver the signal to them. Consider the following snippet:

    static volatile std::atomic<bool> quit;
    static volatile std::deque<std::thread> all;
    static volatile pthread_t main_thread;
    
    void sigint_handler (int) {
        if (pthread_self() == main_thread) {
            write(2, "\rQuitting.\n", 11);
            quit = true;
            for (auto &t : all) pthread_kill(t.native_handle(), SIGINT);
        } else if (!quit) pthread_kill(main_thread, SIGINT);
    }
    

    Quitting is communicated by setting a global variable. A thread is to wakeup and check that variable. Waking up the thread is a matter of visiting the thread and sending it a signal.

    In the event a worker thread intercepts SIGINT before the main thread does, then it sends it to the main thread to initiate a proper shutdown sequence.

    In order to allow being interrupted, a thread may call siginterrupt().

    void readCanbus(int s) {
        siginterrupt(SIGINT, 1);
        while(!quit) {
            char buf[256];
            int nbytes = read(s, buf, sizeof(buf));
        }
        write(2, "Quit.\n", 6);
    }
    

    We define two ways of installing a signal handler, one using signal, the other using sigaction, but using a flag that provides semantics similar to signal.

    template <decltype(signal)>
    void sighandler(int sig, sighandler_t handler) {
        signal(sig, handler);
    }
    
    template <decltype(sigaction)>
    void sighandler(int sig, sighandler_t handler) {
        struct sigaction sa = {};
        sa.sa_handler = handler;
        sa.sa_flags = SA_RESTART;
        sigaction(sig, &sa, NULL);
    }
    

    The main thread installs the signal handler, initializes main_thread, and populates the container with the worker threads that need to be shutdown later.

    int main () {
        int sp[2];
        main_thread = pthread_self();
        socketpair(AF_UNIX, SOCK_STREAM, 0, sp);
        all.push_back(std::thread(readCanbus, sp[0]));
        sighandler<sigaction>(SIGINT, sigint_handler);
        for (auto &t : all) t.join();
    }