6

I've got the following code:

class Foo {
private:
    std::thread thread;
    void run();
    std::atomic_flag running;
    std::thread::native_handle_type native;
public:
    Foo(const std::string& filename);
    virtual ~Foo();
    virtual void doOnChange();
    void start();
    void quit();
};

#include "Foo.h"
#include <functional>

#include <iostream>

Foo::Foo(const std::string& filename) :
        thread(), running(ATOMIC_FLAG_INIT) {
    file = filename;
    native = 0;
}

Foo::~Foo() {
    quit();
}

void Foo::start() {
    running.test_and_set();
    try {
        thread = std::thread(&Foo::run, this);
    } catch (...) {
        running.clear();
        throw;
    }
    native = thread.native_handle();
}

void Foo::quit() {
    running.clear();
    pthread_cancel(native);
    pthread_join(native, nullptr);
    //c++11-style not working here
    /*if (thread.joinable()) {
        thread.join();
        thread.detach();
    }*/
}

void Foo::run() {
   while (running.test_and_set()) {
        numRead = read(fd, buf, BUF_LEN);
        .....bla bla bla.......
   }
}

I'm trying to quit from this thread in my program cleanup code. Using pthread works but I'm wondering if I can do something better with c++11 only (no native handle). It seems to me there's no good way to handle all cases using c++11 code. As you can see here the thread is blocked on a read system call. So even if I clear the flag the thread will be still blocked and join call will block forever. So what I really need is an interrupt (in this case pthread_cancel). But if I call pthread_cancel I can't call anymore the c++11 join() method because it fails, I can only call pthread_join(). So it seems the standard has a really big limitation, am I miss anything?

Edit:

After discussion below I changed the Foo class implementation replacing std::atomic_flag with std::atomic and using signal handler. I used the signal handler because in my opinion is better to have a general base class, using the self-pipe trick is too hard in a base class, the logic should be delegated to the child. Final implementation:

#include <thread>
#include <atomic>

class Foo {
private:
    std::thread thread;
    void mainFoo();
    std::atomic<bool> running;
    std::string name;
    std::thread::native_handle_type native;
    static void signalHandler(int signal);
    void run();
public:
    Thread(const std::string& name);
    virtual ~Thread();
    void start();
    void quit();
    void interrupt();
    void join();
    void detach();
    const std::string& getName() const;
    bool isRunning() const;
};

Cpp file:

#include <functional>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/inotify.h>
#include <Foo.h>
#include <csignal>
#include <iostream>

Foo::Foo(const std::string& name) :
        name(name) {
    running = false;
    native = 0;
    this->name.resize(16, '\0');
}

Foo::~Foo() {
}

void Foo::start() {
    running = true;
    try {
        thread = std::thread(&Foo::mainFoo, this);
    } catch (...) {
        running = false;
        throw;
    }
    native = thread.native_handle();
    pthread_setname_np(native, name.c_str());
}

void Foo::quit() {
    if (running) {
        running = false;
        pthread_kill(native, SIGINT);
        if (thread.joinable()) {
            thread.join();
        }
    }
}

void Foo::mainFoo() {
 //enforce POSIX semantics
 siginterrupt(SIGINT, true);
 std::signal(SIGINT, signalHandler);
    run();
    running = false;
}

void Foo::join() {
    if (thread.joinable())
        thread.join();
}

void Foo::signalHandler(int signal) {
}

void Foo::interrupt() {
    pthread_kill(native, SIGINT);
}

void Foo::detach() {
    if (thread.joinable())
        thread.detach();
}

const std::string& Foo::getName() const {
    return name;
}

bool Foo::isRunning() const {
    return running;
}

void Foo::run() {
    while(isRunning()) {
         num = read(.....);
         //if read is interrupted loop again, this time
         //isRunning() will return false
    }
}
greywolf82
  • 21,813
  • 18
  • 54
  • 108
  • 8
    Forcibly "killing" a thread is never a good idea, as the thread will not be able to release any possible resources it might have allocated. If you need to be able to *ask* a thread to exit before it's end, then consider using e.g. non-blocking I/O or similar. – Some programmer dude Aug 08 '18 at 08:40
  • 3
    There are some platforms (MS Windows) where terminating a thread will leave your application in an unstable state. This is documented by MS. Simple example: thread is holding the (internal) C++ heap lock when it gets terminated - now you have no heap. – Richard Critten Aug 08 '18 at 08:41
  • 1
    @Some programmer dude It's actually possible to call a cleanup handler using pthread, not in my example but possible – greywolf82 Aug 08 '18 at 08:41
  • 3
    It's still not enough. Lets take this hypothetical case: You allocate objects in a loop in the thread with `new`. The allocation have succeeded, but the assignment to the pointer haven't been done yet. Then the thread is killed, and you leak the object (not to mention the possible UB if the pointer is not initialized). Unless you can synchronize the thread and the killing of it there will always be chances of leaks and UB. And if you can synchronize the killing of the thread, why forcibly kill it in the first place instead of letting it clean up itself? – Some programmer dude Aug 08 '18 at 08:51
  • So the reply to my question is: using c++11 (maybe even without it) don't call any blocking system call in the thread. It seems a big limitation to me. – greywolf82 Aug 08 '18 at 08:54
  • May be helpful: https://stackoverflow.com/a/12207835/5376789 – xskxzr Aug 08 '18 at 08:58
  • @xskxzr I know there is no portable way in C++11 to non-cooperatively kill a single thread in a multi-thread program, but I'm wondering how to handle a case where the thread is blocked in IO and the response seems to be: *never use blocking io in a thread*. – greywolf82 Aug 08 '18 at 09:01
  • 2
    Don't block. Look into "readsome", "in_avail", and related functions. – Jive Dadson Aug 08 '18 at 09:03
  • @Someprogrammerdude Just to add info here: if you use a deferred cancellation point, the thread will be canceled when calling the next cancellation point, not immediately. So in your example and with this configuration no leak can happen because the thread won't be stopped during allocation but only to the next cancellation point. – greywolf82 Aug 08 '18 at 09:06
  • Yup, non-blocking read. Its the only clean solution imo. – Galik Aug 08 '18 at 09:21
  • Just a side note: The use of `ATOMIC_FLAG_INIT` in a (default) member initializer seems to be unspecified since C++14: https://stackoverflow.com/questions/24437396/stdatomic-flag-as-member-variable . My suggestion: Use `std::atomic`. – Julius Aug 08 '18 at 09:37
  • @Julius, no it's unspecified in a constructor mem-initializer (as used in the OP's question) It's fine in a default member initializer (i.e. declaring the member as `std::atomic_flag running = ATOMIC_FLAG_INIT;`). I still agree with the suggestion to use `atomic` though. – Jonathan Wakely Aug 08 '18 at 10:45
  • 1
    @greywolf82 to add to the reasons to avoid non-cooperative thread cancellation in C++: on GNU/Linux `pthread_cancel` is implemented with a magic unstoppable exception, so if any function in the thread's call stack is `noexcept` then `pthread_cancel` will terminate the whole process, and you can't prevent it. – Jonathan Wakely Aug 08 '18 at 10:47
  • If on Linux or POSIX learn about [poll(2)](http://man7.org/linux/man-pages/man2/poll.2.html) (you'll call it before the blocking read) – Basile Starynkevitch Aug 09 '18 at 10:26
  • That's right, don't use any indefinitely blocking system calls, with threads or without. – n. m. could be an AI Aug 09 '18 at 10:33

2 Answers2

8

As you can see here the thread is blocked on a read system call. So even if I clear the flag the thread will be still blocked and join call will block forever.

The solution to this is to std::raise a signal such as SIGINT Edit: You need to raise the signal using pthread_kill so that the signal will be handled by the correct thread. As you can read from the manual, read is interrupted by signals. You must handle the std::signal or else the entire process will terminate prematurely.

On systems that use BSD signal handling instead of POSIX, system calls are by default restarted rather than failed upon interrupt. My suggested approach relies on the POSIX behaviour, where the call sets EINTR and returns. The POSIX behaviour can set explicitly using siginterrupt. Another option is to register the signal handler using sigaction, which does not restart, unless specified by a flag.

After read has been interrupted, you must check whether the thread should stop before retrying the read.

using c++11 (maybe even without it) don't call any blocking system call in the thread

Calling blocking system calls is just fine. What you shouldn't do is call uninterruptible system calls that may block indefinitely long time, if you wish to terminate the thread without terminating the process (within finite time). Off the top of my head, I don't know if any system call matches such description.

A minimal example (complete except for indefinitely blocking read. You can use sleep(100000) to simulate it):

#include <thread>
#include <iostream>
#include <csignal>
#include <cerrno>
#include <unistd.h>

constexpr int quit_signal = SIGINT;
thread_local volatile std::sig_atomic_t quit = false;

int main()
{
    // enforce POSIX semantics
    siginterrupt(quit_signal, true);

    // register signal handler
    std::signal(quit_signal, [](int) {
        quit = true;
    });

    auto t = std::thread([]() {
        char buf[10];
        while(!quit) {
            std::cout << "initiated read\n";
            int count = read(some_fd_that_never_finishes, buf, sizeof buf);
            if (count == -1) {
                if (errno == EINTR) {
                    std::cout << "read was interrupted due to a signal.\n";
                    continue;
                }
            }
        }
        std::cout << "quit is true. Exiting\n";;
    });

    // wait for a while and let the child thread initiate read
    sleep(1);

    // send signal to thread
    pthread_kill(t.native_handle(), quit_signal);

    t.join();
}

Forcibly killing a thread is usually a very bad idea, especially in C++, which is probably why std::thread API doesn't provide interface for it.

If you really want to kill a thread of execution - which isn't necessary in this case, since you can safely interrupt the system call instead - then you should use a child process instead of child thread. Killing a child process won't break the heap of the parent process. That said, C++ standard library does not provide an inter-process API.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • 1
    A signal is application-wide not just for the thread so it could work but it's really tricky with a real-world application, however it's just an idea. About your comment about uninterruptible system calls: honestly no idea what you mean, read is interruptible but from other users comments is clear that it's not a good strategy anyway, so your comment seems not applicable. – greywolf82 Aug 08 '18 at 09:28
  • 1
    @greywolf82 good point about signals being global. You'll need to send the signal using `pthread_kill` instead to target the correct thread. Too bad this has no standard API. – eerorika Aug 08 '18 at 09:33
  • @JiveDadson & greywolf82 I added an example. – eerorika Aug 08 '18 at 18:37
  • your example code doesn't work because std::signal will use BSD behavior for the signal so read won't return EINTR. In addition it doesn't accept a lambda if I well remember. – greywolf82 Aug 08 '18 at 19:16
  • @greywolf82 yes, the example is for POSIX systems. For BSD signal handling, you can use [siginterrupt](https://www.freebsd.org/cgi/man.cgi?query=siginterrupt&sektion=3&apropos=0&manpath=freebsd) to set the same behaviour that this approach relies on. I don't see why one couldn't use a lambda. Works fine on my system at least. – eerorika Aug 08 '18 at 19:28
  • It doesn't work at all on Linux because signal uses BSD style, just as important note. See my code in edit section. – greywolf82 Aug 08 '18 at 19:30
  • @greywolf82 assuming that by Linux you mean GNU, according to [manual](https://www.gnu.org/software/libc/manual/html_node/Interrupted-Primitives.html) `The default choice in the GNU C Library is to make primitives fail with EINTR.` Nevertheless, you should be able to use `siginterrupt` in Linux as well to be explicit and therefore work on both signaling schemes. I'm not able to reproduce "doesn't work at all" on my system (which is Linux). – eerorika Aug 08 '18 at 19:41
  • It's possible it depends on glibc, I don't know, but it doesn't work for me using signal. – greywolf82 Aug 08 '18 at 19:43
  • From siginterrupt man: The siginterrupt() function changes the restart behavior when a system call is interrupted by the signal sig. If the flag argument is false (0), then system calls will be restarted if interrupted by the specified signal sig. *This is the default behavior in Linux.* – greywolf82 Aug 08 '18 at 19:54
  • @greywolf82 Our quoted manuals conflict each other. It appears that [this](ftp://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_chapter/libc_24.html) has a more complete story: `When you don't specify with sigaction or siginterrupt what a particular handler should do, it uses a default choice. The default choice in the GNU library depends on the feature test macros you have defined. If you define _BSD_SOURCE or _GNU_SOURCE before calling signal, the default is to resume primitives; otherwise, the default is to make them fail with EINTR.` – eerorika Aug 08 '18 at 20:02
  • calling explicitly siginterrupt seems the correct thing, it's possible it depends on macro definition at this point – greywolf82 Aug 08 '18 at 20:04
5

As others have said, killing a running thread is a Bad Idea™.

However, in this case, you somehow know the thread is blocking on a read, and want it to stop.

A simple way of doing this is to use the "self pipe trick". Open a pipe, and have the thread block on a select() or poll() call, checking the read end of the pipe and the file descriptor being read. When you want the thread to stop, write a single byte to the write descriptor. The thread wakes up, sees the byte on the pipe and can then terminate.

This approaches avoids the undefined behaviour of killing a thread outright, allows you to use a blocking system call to avoid polling and is responsive to a termination request.

janm
  • 17,976
  • 1
  • 43
  • 61
  • i am a little confused. if you block on read() and read() is interrupted, you do not have to block on poll() , just a quick check on self-pipe should be sufficient, no ? – Malkocoglu Aug 08 '18 at 11:28
  • 1
    @Malkocoglu Q: If your read is blocked, how do you interrupt it? A: No good way; closing the descriptor has file descriptor reuse race conditions. The alternative of using `poll()` will tell you which file descriptor is ready for reading, avoiding the need for `read()` itself to block. If the self-pipe is ready, then the thread knows to stop. If the file descriptor is ready the thread can safely read without blocking. – janm Aug 08 '18 at 13:58
  • thanks. your last sentence in your answer confused me, it is my bad, now it is clear... – Malkocoglu Aug 08 '18 at 14:29
  • 1
    @Malkocoglu Thanks for the feedback -- I reworded the last sentence a bit, hopefully a bit clearer. – janm Aug 08 '18 at 14:52