Terminal: Handle SIGCHLD in a dedicated thread
Block SIGCHLD in all threads and spawn a dedicated thread that handles the signal in a loop (via sigwait()). This avoids the issue that the SIGCHLD could be handled in any of our threads and thus possibly interrupt a syscall. Fixes #10941.
This commit is contained in:
@@ -56,7 +56,10 @@ main()
|
|||||||
#define B_TRANSLATION_CONTEXT "Terminal TermApp"
|
#define B_TRANSLATION_CONTEXT "Terminal TermApp"
|
||||||
|
|
||||||
TermApp::TermApp()
|
TermApp::TermApp()
|
||||||
: BApplication(TERM_SIGNATURE),
|
:
|
||||||
|
BApplication(TERM_SIGNATURE),
|
||||||
|
fChildCleanupThread(-1),
|
||||||
|
fTerminating(false),
|
||||||
fStartFullscreen(false),
|
fStartFullscreen(false),
|
||||||
fTermWindow(NULL),
|
fTermWindow(NULL),
|
||||||
fArgs(NULL)
|
fArgs(NULL)
|
||||||
@@ -81,24 +84,39 @@ TermApp::ReadyToRun()
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Install a SIGCHLD signal handler, so that we will be notified, when
|
// Install a SIGCHLD signal handler, so that we will be notified, when
|
||||||
// a shell exits.
|
// a shell exits. The handler itself will never be executed, since we block
|
||||||
|
// the signal in all threads and handle it with sigwaitinfo() in the child
|
||||||
|
// cleanup thread.
|
||||||
struct sigaction action;
|
struct sigaction action;
|
||||||
#ifdef __HAIKU__
|
|
||||||
action.sa_handler = (__sighandler_t)_SigChildHandler;
|
action.sa_handler = (__sighandler_t)_SigChildHandler;
|
||||||
#else
|
|
||||||
action.sa_handler = (__signal_func_ptr)_SigChildHandler;
|
|
||||||
#endif
|
|
||||||
sigemptyset(&action.sa_mask);
|
sigemptyset(&action.sa_mask);
|
||||||
#ifdef SA_NODEFER
|
action.sa_flags = 0;
|
||||||
action.sa_flags = SA_NODEFER;
|
|
||||||
#endif
|
|
||||||
action.sa_userdata = this;
|
|
||||||
if (sigaction(SIGCHLD, &action, NULL) < 0) {
|
if (sigaction(SIGCHLD, &action, NULL) < 0) {
|
||||||
fprintf(stderr, "sigaction() failed: %s\n",
|
fprintf(stderr, "sigaction() failed: %s\n", strerror(errno));
|
||||||
strerror(errno));
|
|
||||||
// continue anyway
|
// continue anyway
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// block SIGCHLD and SIGUSR1 -- we send the latter to wake up the child
|
||||||
|
// cleanup thread when quitting.
|
||||||
|
sigset_t blockedSignals;
|
||||||
|
sigemptyset(&blockedSignals);
|
||||||
|
sigaddset(&blockedSignals, SIGCHLD);
|
||||||
|
sigaddset(&blockedSignals, SIGUSR1);
|
||||||
|
|
||||||
|
int error = pthread_sigmask(SIG_BLOCK, &blockedSignals, NULL);
|
||||||
|
if (error != 0)
|
||||||
|
fprintf(stderr, "pthread_sigmask() failed: %s\n", strerror(errno));
|
||||||
|
|
||||||
|
// spawn the child cleanup thread
|
||||||
|
fChildCleanupThread = spawn_thread(_ChildCleanupThreadEntry,
|
||||||
|
"child cleanup", B_NORMAL_PRIORITY, this);
|
||||||
|
if (fChildCleanupThread >= 0) {
|
||||||
|
resume_thread(fChildCleanupThread);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Failed to start child cleanup thread: %s\n",
|
||||||
|
strerror(fChildCleanupThread));
|
||||||
|
}
|
||||||
|
|
||||||
// init the mouse copy'n'paste clipboard
|
// init the mouse copy'n'paste clipboard
|
||||||
gMouseClipboard = new BClipboard(MOUSE_CLIPBOARD_NAME, true);
|
gMouseClipboard = new BClipboard(MOUSE_CLIPBOARD_NAME, true);
|
||||||
|
|
||||||
@@ -145,6 +163,13 @@ TermApp::QuitRequested()
|
|||||||
void
|
void
|
||||||
TermApp::Quit()
|
TermApp::Quit()
|
||||||
{
|
{
|
||||||
|
fTerminating = true;
|
||||||
|
|
||||||
|
if (fChildCleanupThread >= 0) {
|
||||||
|
send_signal(fChildCleanupThread, SIGUSR1);
|
||||||
|
wait_for_thread(fChildCleanupThread, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
BApplication::Quit();
|
BApplication::Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,10 +182,6 @@ TermApp::MessageReceived(BMessage* message)
|
|||||||
fTermWindow->Activate();
|
fTermWindow->Activate();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MSG_CHECK_CHILDREN:
|
|
||||||
_HandleChildCleanup();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
BApplication::MessageReceived(message);
|
BApplication::MessageReceived(message);
|
||||||
break;
|
break;
|
||||||
@@ -264,35 +285,41 @@ TermApp::_MakeTermWindow()
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
TermApp::_HandleChildCleanup()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*static*/ void
|
/*static*/ void
|
||||||
TermApp::_SigChildHandler(int signal, void* data)
|
TermApp::_SigChildHandler(int signal, void* data)
|
||||||
{
|
{
|
||||||
// Spawing a thread that does the actual signal handling is pretty much
|
fprintf(stderr, "Terminal: _SigChildHandler() called! That should never "
|
||||||
// the only safe thing to do in a multi-threaded application. The
|
"happen!\n");
|
||||||
// interrupted thread might have been anywhere, e.g. in a critical section,
|
|
||||||
// holding locks. If we do anything that does require locking at any point
|
|
||||||
// (e.g. memory allocation, messaging), we risk a dead-lock or data
|
|
||||||
// structure corruption. Spawing a thread is safe though, since its only
|
|
||||||
// a system call.
|
|
||||||
thread_id thread = spawn_thread(_ChildCleanupThread, "child cleanup",
|
|
||||||
B_NORMAL_PRIORITY, ((TermApp*)data)->fTermWindow);
|
|
||||||
if (thread >= 0)
|
|
||||||
resume_thread(thread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*static*/ status_t
|
/*static*/ status_t
|
||||||
TermApp::_ChildCleanupThread(void* data)
|
TermApp::_ChildCleanupThreadEntry(void* data)
|
||||||
{
|
{
|
||||||
// Just drop the windowa message and let it do the actual work. This
|
return ((TermApp*)data)->_ChildCleanupThread();
|
||||||
// saves us additional synchronization measures.
|
}
|
||||||
return ((TermWindow*)data)->PostMessage(MSG_CHECK_CHILDREN);
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
TermApp::_ChildCleanupThread()
|
||||||
|
{
|
||||||
|
sigset_t waitForSignals;
|
||||||
|
sigemptyset(&waitForSignals);
|
||||||
|
sigaddset(&waitForSignals, SIGCHLD);
|
||||||
|
sigaddset(&waitForSignals, SIGUSR1);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int signal;
|
||||||
|
int error = sigwait(&waitForSignals, &signal);
|
||||||
|
|
||||||
|
if (fTerminating)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (error == 0 && signal == SIGCHLD)
|
||||||
|
fTermWindow->PostMessage(MSG_CHECK_CHILDREN);
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,13 +43,15 @@ protected:
|
|||||||
private:
|
private:
|
||||||
status_t _MakeTermWindow();
|
status_t _MakeTermWindow();
|
||||||
|
|
||||||
void _HandleChildCleanup();
|
|
||||||
static void _SigChildHandler(int signal, void* data);
|
static void _SigChildHandler(int signal, void* data);
|
||||||
static status_t _ChildCleanupThread(void* data);
|
static status_t _ChildCleanupThreadEntry(void* data);
|
||||||
|
status_t _ChildCleanupThread();
|
||||||
|
|
||||||
void _Usage(char *name);
|
void _Usage(char *name);
|
||||||
void _InitDefaultPalette();
|
void _InitDefaultPalette();
|
||||||
private:
|
private:
|
||||||
|
thread_id fChildCleanupThread;
|
||||||
|
bool fTerminating;
|
||||||
bool fStartFullscreen;
|
bool fStartFullscreen;
|
||||||
BString fWindowTitle;
|
BString fWindowTitle;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user