simon
simon

Reputation: 1240

Exit application while stdin blocking on windows

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:

* Update *

* Update 2: Solution *


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;
}

Upvotes: 6

Views: 1881

Answers (7)

helmesjo
helmesjo

Reputation: 655

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 */ 
}

Upvotes: 0

simon
simon

Reputation: 1240

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;
}

Upvotes: 2

Harry Johnston
Harry Johnston

Reputation: 36348

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;
}

Upvotes: 0

David Haim
David Haim

Reputation: 26536

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.

Upvotes: 0

Harry Johnston
Harry Johnston

Reputation: 36348

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.

Upvotes: 0

user2249683
user2249683

Reputation:

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

Upvotes: 0

Drop
Drop

Reputation: 13013

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)

Upvotes: 0

Related Questions