我正在编写一个控制台应用程序,它接受来自 stdin
的输入(单行命令)。该应用程序在专用线程中读取输入,所有输入都存储在队列中,然后由主线程以安全的方式处理。当用户输入 exit 命令时,该命令会被输入线程拦截,该线程会停止侦听新输入,该线程会加入到主线程中,并且应用程序会根据请求停止。
现在我正在容器化这个应用程序,但我仍然希望能够附加到容器并从 stdin
输入命令,因此我指定了 tty
和 stdin_open 设置为 true
,这样就成功了。
但我也希望 docker compose 能够优雅地停止应用程序,所以我决定在我的应用程序中实现 sigTerm() ,以便它可以接收来自 docker compose 的信号并优雅地停止,但是我'我卡在了那部分,因为输入线程在等待 stdin
上的输入时被阻塞。我可以正确接收信号,这根本不是重点,但我正在寻找一种方法,能够正确停止我的容器化应用程序,同时仍然能够从键盘输入命令。
我的应用程序可以这样简化:
void gracefulStop() {
while (getThreadCount() > 1) { // this function exists somewhere else.
if (userInputThread.joinable()) {
userInputThread.join();
removeFromThreadCount(); // this function exists somewhere else.
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
exit(SUCCESS);
}
void sigTerm(int s) {
// Maybe do some stuff here, but what...
gracefulStop();
}
void userInputLoopThreadFunc() {
addToThreadCount(); // this function exists somewhere else.
while (keepGoing) {
char buf[4096];
if (!fgets(buf, sizeof(buf), stdin)) {
break; // we couldn't read from stdin, stop trying.
}
std::string input = std::string(buf); // we received a command
// Intercept exit command
if (input.starts_with("exit")) {
keepGoing = false;
}
// IRL there's thread safety
userInputQueue.push(input); // this will be processed by mainLoop() later
}
}
int main(int argc, char **argv) {
// Register the signal
signal(SIGTERM, sigTerm);
// Begin listening to user input
userInputThread = std::thread(&userInputLoopThreadFunc, this);
// this mainLoop function contains the core of the application
// as well as the processing code of the user input
mainLoop();
// if mainLoop function returned, we received the 'exit' command
gracefulStop();
}
我已经阅读了多个问题/答案,例如这个 关于非阻塞用户输入(接受的答案建议使用专用线程进行输入,这就是我正在做的),或 另一个关于如何停止读取 stdin 的,并且接受的答案似乎很有希望,但是:
- 使用 ncurses 做什么我正在尝试做的事情看起来确实有点矫枉过正
- 如果使用
select()
和所描述的超时机制,如果在键入命令时发生超时会发生什么?
我还阅读了有关 c++20 jthread
here :
jthread 类代表单个执行线程。它与 std::thread 具有相同的一般行为,除了 jthread 在销毁时自动重新加入,并且可以在某些情况下取消/停止。
但我不确定这对我有帮助。
我正在考虑解决我的问题的多种可能性:
- 找到一种无需用户交互即可将换行符发送到我的应用程序的
stdin
的方法,如果可能的话可能会很糟糕,但可能会解锁 fgets
。
- 杀死线程,我知道杀死线程被认为是一种不好的做法,但由于我在这里做的唯一一件事就是停止应用程序,也许我可以忍受这一点,会有任何副作用吗?我该怎么做呢?
- 以另一种方式重写用户输入(我还不知道,
jthread
,或者其他什么?),这将允许sigTerm()
停止应用程序。
- 也许使用 ncurses (这真的能帮助我通过接收信号来停止应用程序吗?)
- 使用 select() 和超时机制,并承受输入中断的风险
- 放弃用户输入并有一些假期时间。
I am writing a console application that accepts input (one-line commands) from stdin
. This application reads input in a dedicated thread, all input is stored in a queue and later processed by the main thread in a safe way. When the exit command is entered by the user, it is intercepted by the input thread which stops listening for new input, the thread is joined into the main one, and the application stops as requested.
Now I am containerizing this application, but I still want to be able to attach to the container and input commands from stdin
, so I specified tty
and stdin_open
to be true
in my docker compose service file, and that did the trick.
But I also want docker compose to be able to gracefully stop the application, so I decided to implement sigTerm()
in my application so that it can receive the signal from docker compose and gracefully stop, however I'm stuck on that part, because the input thread is blocking while waiting for input on stdin
. I can properly receive the signal, that's not at all the point here, but I'm looking for a way to be able to properly stop my containerized application while still being able to input commands from the keyboard.
My application could be simplified like that :
void gracefulStop() {
while (getThreadCount() > 1) { // this function exists somewhere else.
if (userInputThread.joinable()) {
userInputThread.join();
removeFromThreadCount(); // this function exists somewhere else.
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
exit(SUCCESS);
}
void sigTerm(int s) {
// Maybe do some stuff here, but what...
gracefulStop();
}
void userInputLoopThreadFunc() {
addToThreadCount(); // this function exists somewhere else.
while (keepGoing) {
char buf[4096];
if (!fgets(buf, sizeof(buf), stdin)) {
break; // we couldn't read from stdin, stop trying.
}
std::string input = std::string(buf); // we received a command
// Intercept exit command
if (input.starts_with("exit")) {
keepGoing = false;
}
// IRL there's thread safety
userInputQueue.push(input); // this will be processed by mainLoop() later
}
}
int main(int argc, char **argv) {
// Register the signal
signal(SIGTERM, sigTerm);
// Begin listening to user input
userInputThread = std::thread(&userInputLoopThreadFunc, this);
// this mainLoop function contains the core of the application
// as well as the processing code of the user input
mainLoop();
// if mainLoop function returned, we received the 'exit' command
gracefulStop();
}
I've read multiple question/answers like this one about non-blocking user input (the accepted answer advises to use a dedicated thread for input, which is what I am doing), or this other one about how to stop reading stdin, and the accepted answer seems promising but :
- using ncurses for what I'm trying to do seems really overkill
- If using
select()
and the timeout mechanism described, what would happen if the timeout occurs while typing a command?
Also I've read about the c++20 jthread
here :
The class jthread represents a single thread of execution. It has the same general behavior as std::thread, except that jthread automatically rejoins on destruction, and can be cancelled/stopped in certain situations.
But I'm not sure that would help me here.
I'm thinking about multiple possibilities to solve my issue :
- Find a way to send a newline character to the
stdin
of my application without user interaction, would be hackish if at all possible but would probably unblock fgets
.
- Kill the thread, I understand killing a thread is considered a bad practice, but since the only thing I'm doing here is stopping the application, maybe I can live with that, would there be any side effect? How would I do that?
- Rewriting user input in another way (unknown to me yet,
jthread
, something else?) that would allow sigTerm()
to stop the application.
- Maybe use ncurses (would that really help me to stop the application by receiving a signal?)
- Go with
select()
and the timeout mechanism and live with the risk of an interrupted input
- Give up on user input and have some vacation time.
发布评论
评论(1)
您可以在信号处理程序中关闭
stdin
。然后,fgets
将立即返回(并且可能返回NULL
)。好消息是
close
位于 从信号处理程序安全调用的函数列表(这是一个相当严格的列表)。快乐的日子。有一个基于
EINTR<的替代方案/code>
,但它看起来很混乱,因为您不确定
fgets
在获取它时是否会实际返回。此外,如果您切换到使用
cin
和getline
,关闭 stdin 应该仍然有效,这肯定会改进您的代码 (*)。当您关闭
,尽管代码可以比单独检查它更健壮。也许只需在信号处理程序中设置一个(badbit
>stdin易失性
)标志并进行测试即可。(*) 因为
getline
可以读入std::string
,这意味着它可以读取任意长行,而不必担心分配“足够大”的固定大小缓冲区'。You can close
stdin
in your signal handler.fgets
will then return immediately (and presumably, returnNULL
).The good news is that
close
is on the list of functions that are safe to call from a signal handler (it's a pretty restrictive list). Happy days.There's an alternative based around
EINTR
, but it looks messy since you don't know for certain thatfgets
will actually return when it gets it.Also, closing
stdin
should still work should you switch to usingcin
andgetline
, which would definitely improve your code (*). That probably returns and setsbadbit
when you closestdin
, although the code can be made more robust than by checking for that alone. Perhaps just set a (volatile
) flag in your signal handler and test that.(*) Because
getline
can read into astd::string
, which means it can read arbitrary long lines without worrying about allocating a fixed-size buffer that is 'big enough'.