mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-10 09:39:03 -04:00
We need support three distinct paths for destruction of ncsubprocs: (1) external call to ncsubproc_destroy() while the subprocess is running, (2) external call to ncsubproc_destroy() after the subprocess has terminated, and (3) internal call to ncsubproc_destroy() without any external call. To do this properly, we always waitid() on the subprocess in our ncsubproc thread, and do not cancel said thread. This guarantees that the subprocess has been reaped if the thread has exited. We throw a pidfd_send_signal() into the thread prior to the waitid(), because this is safe with pidfds. The thread reclaims no resources otherwise. ncsubproc_destroy(), instead, reclaims them, after joining the ncsubproc thread. It sends SIGKILL before the join, which is once again safe thanks to pidfds. Resolves #552.
170 lines
4.9 KiB
C++
170 lines
4.9 KiB
C++
#include "main.h"
|
|
#include <cerrno>
|
|
#include <mutex>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include "internal.h"
|
|
#include <condition_variable>
|
|
|
|
std::mutex lock;
|
|
std::condition_variable cond;
|
|
bool inline_cancelled = false;
|
|
|
|
int testfdcb(struct ncfdplane* ncfd, const void* buf, size_t s, void* curry){
|
|
struct ncplane* n = ncfdplane_plane(ncfd);
|
|
lock.lock();
|
|
if(ncplane_putstr(n, static_cast<const char*>(buf)) <= 0){
|
|
lock.unlock();
|
|
return -1;
|
|
}
|
|
lock.unlock();
|
|
(void)curry;
|
|
(void)s;
|
|
return 0;
|
|
}
|
|
|
|
int testfdeof(struct ncfdplane* n, int fderrno, void* curry){
|
|
bool* outofline_cancelled = static_cast<bool*>(curry);
|
|
lock.lock();
|
|
*outofline_cancelled = true;
|
|
lock.unlock();
|
|
cond.notify_one();
|
|
(void)n;
|
|
(void)fderrno;
|
|
return 0;
|
|
}
|
|
|
|
int testfdeofdestroys(struct ncfdplane* n, int fderrno, void* curry){
|
|
lock.lock();
|
|
inline_cancelled = true;
|
|
int ret = ncfdplane_destroy(n);
|
|
lock.unlock();
|
|
cond.notify_one();
|
|
(void)curry;
|
|
(void)fderrno;
|
|
return ret;
|
|
}
|
|
|
|
// test ncfdplanes and ncsubprocs
|
|
TEST_CASE("FdsAndSubprocs") {
|
|
if(getenv("TERM") == nullptr){
|
|
return;
|
|
}
|
|
notcurses_options nopts{};
|
|
nopts.inhibit_alternate_screen = true;
|
|
nopts.suppress_banner = true;
|
|
FILE* outfp_ = fopen("/dev/tty", "wb");
|
|
REQUIRE(outfp_);
|
|
struct notcurses* nc_ = notcurses_init(&nopts, outfp_);
|
|
REQUIRE(nc_);
|
|
struct ncplane* n_ = notcurses_stdplane(nc_);
|
|
REQUIRE(n_);
|
|
REQUIRE(0 == ncplane_cursor_move_yx(n_, 0, 0));
|
|
|
|
// destroy the ncfdplane outside of its own context
|
|
SUBCASE("FdPlaneDestroyOffline") {
|
|
bool outofline_cancelled = false;
|
|
ncfdplane_options opts{};
|
|
opts.curry = &outofline_cancelled;
|
|
int fd = open("/dev/null", O_RDONLY|O_CLOEXEC);
|
|
REQUIRE(0 <= fd);
|
|
auto ncfdp = ncfdplane_create(n_, &opts, fd, testfdcb, testfdeof);
|
|
REQUIRE(ncfdp);
|
|
std::unique_lock<std::mutex> lck(lock);
|
|
CHECK(0 == notcurses_render(nc_));
|
|
while(!outofline_cancelled){
|
|
cond.wait(lck);
|
|
}
|
|
CHECK(0 == ncfdplane_destroy(ncfdp));
|
|
CHECK(0 == notcurses_render(nc_));
|
|
lock.unlock();
|
|
}
|
|
|
|
// destroy the ncfdplane within its own context, i.e. from the eof callback
|
|
SUBCASE("FdPlaneDestroyInline") {
|
|
inline_cancelled = false;
|
|
ncfdplane_options opts{};
|
|
opts.curry = n_;
|
|
int fd = open("/dev/null", O_RDONLY|O_CLOEXEC);
|
|
REQUIRE(0 <= fd);
|
|
auto ncfdp = ncfdplane_create(n_, &opts, fd, testfdcb, testfdeofdestroys);
|
|
REQUIRE(ncfdp);
|
|
std::unique_lock<std::mutex> lck(lock);
|
|
CHECK(0 == notcurses_render(nc_));
|
|
while(!inline_cancelled){
|
|
cond.wait(lck);
|
|
}
|
|
CHECK(0 == notcurses_render(nc_));
|
|
lock.unlock();
|
|
}
|
|
|
|
SUBCASE("SubprocDestroyCmdExecFails") {
|
|
char * const argv[] = { strdup("/dev/nope"), NULL, };
|
|
bool outofline_cancelled = false;
|
|
ncsubproc_options opts{};
|
|
opts.popts.curry = &outofline_cancelled;
|
|
auto ncsubp = ncsubproc_createvp(n_, &opts, argv[0], argv, testfdcb, testfdeof);
|
|
REQUIRE(ncsubp);
|
|
std::unique_lock<std::mutex> lck(lock);
|
|
CHECK(0 == notcurses_render(nc_));
|
|
while(!outofline_cancelled){
|
|
cond.wait(lck);
|
|
}
|
|
CHECK(0 == ncsubproc_destroy(ncsubp));
|
|
// FIXME we ought get indication of an error here! or via callback...
|
|
// FIXME should be 0 !=, methinks!
|
|
CHECK(0 == notcurses_render(nc_));
|
|
lock.unlock();
|
|
}
|
|
|
|
// FIXME SIGCHLD seems to blow up doctest...
|
|
SUBCASE("SubprocDestroyCmdSucceeds") {
|
|
char * const argv[] = { strdup("/bin/cat"), strdup("/dev/null"), NULL, };
|
|
bool outofline_cancelled = false;
|
|
ncsubproc_options opts{};
|
|
opts.popts.curry = &outofline_cancelled;
|
|
auto ncsubp = ncsubproc_createvp(n_, &opts, argv[0], argv, testfdcb, testfdeof);
|
|
REQUIRE(ncsubp);
|
|
std::unique_lock<std::mutex> lck(lock);
|
|
CHECK(0 == notcurses_render(nc_));
|
|
while(!outofline_cancelled){
|
|
cond.wait(lck);
|
|
}
|
|
CHECK(0 == ncsubproc_destroy(ncsubp));
|
|
CHECK(0 == notcurses_render(nc_));
|
|
lock.unlock();
|
|
}
|
|
|
|
SUBCASE("SubprocDestroyCmdFailed") {
|
|
char * const argv[] = { strdup("/bin/cat"), strdup("/dev/nope"), NULL, };
|
|
bool outofline_cancelled = false;
|
|
ncsubproc_options opts{};
|
|
opts.popts.curry = &outofline_cancelled;
|
|
auto ncsubp = ncsubproc_createvp(n_, &opts, argv[0], argv, testfdcb, testfdeof);
|
|
REQUIRE(ncsubp);
|
|
std::unique_lock<std::mutex> lck(lock);
|
|
CHECK(0 == notcurses_render(nc_));
|
|
while(!outofline_cancelled){
|
|
cond.wait(lck);
|
|
}
|
|
CHECK(0 == ncsubproc_destroy(ncsubp));
|
|
CHECK(0 == notcurses_render(nc_));
|
|
lock.unlock();
|
|
}
|
|
|
|
SUBCASE("SubprocDestroyCmdHung") {
|
|
char * const argv[] = { strdup("/bin/cat"), NULL, };
|
|
bool outofline_cancelled = false;
|
|
ncsubproc_options opts{};
|
|
opts.popts.curry = &outofline_cancelled;
|
|
auto ncsubp = ncsubproc_createvp(n_, &opts, argv[0], argv, testfdcb, testfdeof);
|
|
REQUIRE(ncsubp);
|
|
CHECK(0 == ncsubproc_destroy(ncsubp));
|
|
CHECK(0 == notcurses_render(nc_));
|
|
}
|
|
|
|
CHECK(0 == notcurses_stop(nc_));
|
|
CHECK(0 == fclose(outfp_));
|
|
}
|