6

I have an application, which reads data from standard input using getline() in a thread. I want to close the application from the main thread, while getline still block the other thread. How can this be achieved?

I don't want to force the users to have to press ctrl-Z to close stdin and the application.

I have tried so far with my complier settings (RuntimeLibrary=/MT) on Windows 8.1 64bit, v120 platform toolset:

  • freopen stdin, but it is blocked by internal lock
  • destructing the thread, which calls abort()
  • putback an Eof, line end to std::cin, which also blocked

* Update *

  • detach() does not work, exit() blocked by a lock
  • winapi TerminatThread() calls abort()
  • winapi CloseHandle(GetStdHandle(STD_INPUT_HANDLE)) hangs
  • calling TerminateProcess() - works, but I would like to exit gracefully

* Update 2: Solution *

  • WriteConsoleInput() can make std::getline() return from blocking read. This works with any msvc runtime libray. For code of working solution see accepted answer.

Example code showing the problem:

#include <iostream>
#include <thread>
#include <string>
#include <chrono>

int main(int argc, char *argv[])
{
    bool stop = false;
    std::thread *t = new std::thread([&]{
        std::string line;
        while (!stop && std::getline(std::cin, line, '\n')) {
            std::cout << line;
        }
    });

    std::this_thread::sleep_for(std::chrono::seconds(1));

    stop = true;
    // how to stop thread or make getline to return here?

    return 0;
}
simon
  • 1,210
  • 12
  • 26

7 Answers7

2

writeConsoleInput() can make std::getline return from blocking read, so it can solve the problem even when /MT compiler option used.

#include <Windows.h>

#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <atomic>

int main(int argc, char *argv[])
{
    std::atomic_bool stop;

    stop = false;

    std::thread t([&]{
        std::string line;
        while (!stop.load() && std::getline(std::cin, line, '\n')) {
            std::cout << line;
        }
    });


    std::this_thread::sleep_for(std::chrono::seconds(1));

    stop = true;

    DWORD dwTmp;
    INPUT_RECORD ir[2];
    ir[0].EventType = KEY_EVENT;
    ir[0].Event.KeyEvent.bKeyDown = TRUE;
    ir[0].Event.KeyEvent.dwControlKeyState = 0;
    ir[0].Event.KeyEvent.uChar.UnicodeChar = VK_RETURN;
    ir[0].Event.KeyEvent.wRepeatCount = 1;
    ir[0].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
    ir[0].Event.KeyEvent.wVirtualScanCode = MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);
    ir[1] = ir[0];
    ir[1].Event.KeyEvent.bKeyDown = FALSE;
    WriteConsoleInput(GetStdHandle(STD_INPUT_HANDLE), ir, 2, &dwTmp);

    t.join();

    return 0;
}
simon
  • 1,210
  • 12
  • 26
  • This is a valid alternative, but heads up: This will not work unless process is "attached to a console" (or whatever the terminology is). – helmesjo Apr 17 '20 at 14:37
0

Just detach the thread:

#include <iostream>
#include <thread>
#include <chrono>

bool stop = false;
int main(int argc, char *argv[])
{
    std::thread t([]{
        bool stop = false;
        std::string line;
        while (!stop && std::getline(std::cin, line, '\n')) {
            std::cout << line;
        }
    });

    std::this_thread::sleep_for(std::chrono::seconds(1));

    stop = true;
    // Without detach: g++: terminate called without an active exception
    t.detach();

    return 0;
}

Cleaner ways are

  • If stdin is getting user input, have a proper exit in the thread (do not terminate interactive input, out of the blue)
  • Non-blocking reads from stdin (which is system dependent)
  • Setting up a pipeline
  • Using a socket
  • 1
    Already tried. The application does not exit this way. Waits for a lock in _locterm(). – simon Jul 27 '15 at 16:13
  • I was able to make this approach work in my code by having the stdin-reading thread block in ReadFile() rather than getline(). Seems to work okay for me (although I think it's a horrible hack to leave a thread running like that). The relevant code can be seen starting at line 28 of this file: https://public.msli.com/lcs/muscle/muscle/dataio/StdinDataIO.cpp – Jeremy Friesner Jul 27 '15 at 16:36
0

There is no standard and even cross-platform solution to interrupt std:cin or std::thread. You will need to use OS-specific APIs in both cases. You could retrieve OS-specific handle for a thread with std::thread::native_handle()

As a quick and dirty hack you could just detach thread. But be aware of this and that.

int main(int argc, char *argv[]) {
    std::thread t([&] {
        std::string line;
        while (std::getline(std::cin, line, '\n')) {
            std::cout << line;
        }
    });
    t.detach();

    std::this_thread::sleep_for(std::chrono::seconds(1));
}

Also:

  • No need to allocate thread on heap:

    std::thread t([]{
    
    });
    
  • return 0; is unnecessary in C++
  • stop = true; will trigger compilation error, as stop is not declared in this scope
  • If you are planning to share boolean flag in this manner, you will have a typical race condition and thus UB
  • Probably closest to "standard" or "cross-platform" solution for non-blocking input could be ncurses (as is on *nix, and pdcurses on Windows)
Community
  • 1
  • 1
Ivan Aksamentov - Drop
  • 12,860
  • 3
  • 34
  • 61
  • Right, too much edit... I fixed the example to be compilable. – simon Jul 27 '15 at 16:10
  • @simon What exactly doesn't work? What is "exit is blocked by a lock" supposed to mean? For me, program starts and exits in 1 second on both Linux (GCC 4.9.2) and Windows (VS2013u5). What platform, compiler and standard lib do you use? Did you try my code snippet verbatim? – Ivan Aksamentov - Drop Jul 27 '15 at 19:13
  • Yes. I have tried it verbatim. Exit is blocked by a lock means that i see in the call stack that exit function is called but one of the function called by it waits for a lock to be released. VS2013 v120 – simon Jul 27 '15 at 21:42
  • @simon can you send me entire solution folder so I could check it on my machine? – Ivan Aksamentov - Drop Jul 27 '15 at 21:48
  • I tried with a new clean solution, and your code is working there. Unfortunately I have to use C++/CodeGeneration/RuntimeLibrary=/MT in my project, and with the corresponding runtime library it fails to exit. – simon Jul 28 '15 at 08:27
0

If nothing else works, there's always the nuclear option:

TerminateProcess(GetCurrentProcess(), 0);

Just make sure you've flushed any of the runtime buffers you care about.

Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
0

this code is multi-threaded flawd. first of all, why create a new thread on the heap? just declare it on the stack and call std::thread::detach.
second, who promised you that stop in this context will work? it is more than possible that the processor caches this boolean and never looks at the real one (if not partially optimize it away, or other compiling tricks..). you need to make it atomic:

int main(int argc, char *argv[])
{
    std::atomic_bool stop;
    stop = false;
    std::thread t([&]{
        std::string line;
        while (!stop.load() && std::getline(std::cin, line, '\n')) {
            std::cout << line;
        }
    });
    t.detach();
    stop = true;
}

compiled with visual studio 2013 on windows 7 and works as expected.

David Haim
  • 25,446
  • 3
  • 44
  • 78
  • That was just an example code to show the task I want to accomplish. Your code won't exit with /MT compiler switch, which I have to use. – simon Jul 28 '15 at 08:45
  • so your problem is not there. is your real thread is static to some class? – David Haim Jul 28 '15 at 08:45
0

This works for me, although it's a bit dodgy:

#include <Windows.h>

#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <atomic>

int main(int argc, char *argv[])
{
    std::atomic_bool stop;

    stop = false;

    std::thread t([&]{
        std::string line;
        while (!stop.load() && std::getline(std::cin, line, '\n')) {
            std::cout << line;
        }
    });

    std::this_thread::sleep_for(std::chrono::seconds(1));

    stop = true;

    CloseHandle(GetStdHandle(STD_INPUT_HANDLE));

    t.join();

    return 0;
}
Harry Johnston
  • 35,639
  • 6
  • 68
  • 158
  • Not working for me. CloseHandle call does not return. – simon Jul 29 '15 at 08:00
  • Are you able to try it on a freshly installed VM? I'm thinking there may be third-party software (anti-virus, perhaps) interfering. AFAIK, CloseHandle is not supposed to block under any circumstances. – Harry Johnston Jul 29 '15 at 21:41
  • Yet another approach, I suppose, would be to insert input characters via WriteConsoleInput so that getline exits naturally. Pretty messy though. – Harry Johnston Jul 29 '15 at 21:43
0

I actually had this same problem for a while, specifically because of linking the static runtime (/MT). I got some bits and pieces from here an there, and wrapped it in a simple-to-use RAII object that does it for me (obviously this is not in any header because of Windows.h):

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

#else
#include <pthread.h>
#endif

struct forcefully_stop_thread_on_destruction
{
    forcefully_stop_thread_on_destruction(std::thread&& thread, bool isBlockedByStdin) :
        thread_(std::move(thread)),
        isBlockedByStdin_(isBlockedByStdin)
    {}
    ~forcefully_stop_thread_on_destruction()
    {
#ifdef _WIN32
        // Main issue on Windows is where we link the static runtime (/MT) which locks the stdin file,
        // so it doesn't matter if we read stdin on background thread, it still deadlocks the process on exit & even terminate.

        if (isBlockedByStdin_)
        {
            // On windows, if a console is attached, write to stdin so that std::getline(..) unblocks, and thread bails out naturally.
            CONSOLE_SCREEN_BUFFER_INFO csbi;
            const bool hasConsole = ::GetConsoleScreenBufferInfo(::GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
            if (hasConsole)
            {
                DWORD dwTmp;
                INPUT_RECORD ir[2];
                ir[0].EventType = KEY_EVENT;
                ir[0].Event.KeyEvent.bKeyDown = TRUE;
                ir[0].Event.KeyEvent.dwControlKeyState = 0;
                ir[0].Event.KeyEvent.uChar.UnicodeChar = VK_RETURN;
                ir[0].Event.KeyEvent.wRepeatCount = 1;
                ir[0].Event.KeyEvent.wVirtualKeyCode = VK_RETURN;
                ir[0].Event.KeyEvent.wVirtualScanCode = ::MapVirtualKey(VK_RETURN, MAPVK_VK_TO_VSC);
                ir[1] = ir[0];
                ir[1].Event.KeyEvent.bKeyDown = FALSE;
                ::WriteConsoleInput(::GetStdHandle(STD_INPUT_HANDLE), ir, 2, &dwTmp);

                // Wait for blocking read to release and thread finish execution.
                thread_.join();
            }
            // No console = no reliable way to unblock stdin
            else
            {
                // WE ARE GOING NUCLEAR AT THIS POINT
                // No console, so we can't release blocking stdin read: Kill whole process. Sigh.

                struct terminate_process
                {
                    ~terminate_process()
                    {
                        TerminateProcess(GetCurrentProcess(), 0);
                    }
                };
                // Instantiate in "static storage" so that termination happens as late as possible (after main() returns)
                static terminate_process nuclear;
                // Don't wait for blocking read to release
                thread_.detach();
            }
        }
        else
        {
            thread_.join();
        }
#else
        // On unix, forcefully terminate thread.
        if (isBlockedByStdin_)
        {
            pthread_cancel(thread_.native_handle());
        }
        // Wait for blocking read to release and thread finish execution.
        thread_.join();
#endif
    }
private:
    std::thread thread_;
    bool isBlockedByStdin_;
};

Example usage:

auto thread = std::thread([buff = inputStream.rdbuf()](){
    std::string input;
    std::istream inputStream(buff);
    while (true)
    {
        std::getline(inputStream, input);
        // Use input
    }
});
// `inputStream` can be any stream, so verify it's stdin since that's the problem.
const auto isBlockedByStdin = inputStream.rdbuf() == std::cin.rdbuf();
auto handleProblems = forcefully_stop_thread_on_destruction(std::move(thread), isBlockedByStdin);
// Hold on to `handleProblems` until done polling stdin.

In essence:

if(windows && hasConsole)
{ 
    /* Write to console to unblock stdin */ 
}
else if(windows && !hasConsole)
{ 
    /* Terminate process with exit code 0 after main() has exit (before hitting deadlock) */
}
else
{ 
    /* Assume "Unix" & call pthread_cancel */ 
}
helmesjo
  • 625
  • 5
  • 17