aboutsummaryrefslogtreecommitdiff
path: root/src/zenutil/process/exitwatcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/zenutil/process/exitwatcher.cpp')
-rw-r--r--src/zenutil/process/exitwatcher.cpp61
1 files changed, 41 insertions, 20 deletions
diff --git a/src/zenutil/process/exitwatcher.cpp b/src/zenutil/process/exitwatcher.cpp
index cef31ebca..2e88dfdeb 100644
--- a/src/zenutil/process/exitwatcher.cpp
+++ b/src/zenutil/process/exitwatcher.cpp
@@ -38,7 +38,7 @@ namespace zen {
#if ZEN_PLATFORM_LINUX
-struct ProcessExitWatcher::Impl
+struct ProcessExitWatcher::Impl : public TRefCounted<Impl>
{
asio::io_context& m_IoContext;
std::unique_ptr<asio::posix::stream_descriptor> m_Descriptor;
@@ -64,8 +64,11 @@ struct ProcessExitWatcher::Impl
m_Descriptor = std::make_unique<asio::posix::stream_descriptor>(m_IoContext, m_PidFd);
+ // Capture a strong Ref so the Impl outlives the pending async_wait even
+ // if the owning ProcessExitWatcher is destroyed while a completion is
+ // in flight on the io_context.
m_Descriptor->async_wait(asio::posix::stream_descriptor::wait_read,
- [this, Callback = std::move(OnExit)](const asio::error_code& Ec) {
+ [Self = Ref<Impl>(this), Callback = std::move(OnExit)](const asio::error_code& Ec) {
if (Ec)
{
return; // Cancelled or error
@@ -74,7 +77,7 @@ struct ProcessExitWatcher::Impl
int ExitCode = -1;
int Status = 0;
// The pidfd told us the process exited. Reap it with waitpid.
- if (waitpid(m_Pid, &Status, WNOHANG) > 0)
+ if (waitpid(Self->m_Pid, &Status, WNOHANG) > 0)
{
if (WIFEXITED(Status))
{
@@ -115,7 +118,7 @@ struct ProcessExitWatcher::Impl
#elif ZEN_PLATFORM_WINDOWS
-struct ProcessExitWatcher::Impl
+struct ProcessExitWatcher::Impl : public TRefCounted<Impl>
{
asio::io_context& m_IoContext;
std::unique_ptr<asio::windows::object_handle> m_ObjectHandle;
@@ -147,16 +150,21 @@ struct ProcessExitWatcher::Impl
// object_handle takes ownership of the handle
m_ObjectHandle = std::make_unique<asio::windows::object_handle>(m_IoContext, m_DuplicatedHandle);
- m_ObjectHandle->async_wait([this, DupHandle = m_DuplicatedHandle, Callback = std::move(OnExit)](const asio::error_code& Ec) {
- if (Ec)
- {
- return;
- }
-
- DWORD ExitCode = 0;
- GetExitCodeProcess(static_cast<HANDLE>(DupHandle), &ExitCode);
- Callback(static_cast<int>(ExitCode));
- });
+ // Capture a strong Ref so the duplicated handle (owned by m_ObjectHandle,
+ // which is owned by *this) cannot be closed out from under a completion
+ // that the io_context is still about to dispatch.
+ m_ObjectHandle->async_wait(
+ [Self = Ref<Impl>(this), DupHandle = m_DuplicatedHandle, Callback = std::move(OnExit)](const asio::error_code& Ec) {
+ (void)Self;
+ if (Ec)
+ {
+ return;
+ }
+
+ DWORD ExitCode = 0;
+ GetExitCodeProcess(static_cast<HANDLE>(DupHandle), &ExitCode);
+ Callback(static_cast<int>(ExitCode));
+ });
}
void Cancel()
@@ -182,7 +190,7 @@ struct ProcessExitWatcher::Impl
#elif ZEN_PLATFORM_MAC
-struct ProcessExitWatcher::Impl
+struct ProcessExitWatcher::Impl : public TRefCounted<Impl>
{
asio::io_context& m_IoContext;
std::unique_ptr<asio::posix::stream_descriptor> m_Descriptor;
@@ -218,8 +226,11 @@ struct ProcessExitWatcher::Impl
m_Descriptor = std::make_unique<asio::posix::stream_descriptor>(m_IoContext, m_KqueueFd);
+ // Capture a strong Ref so the Impl outlives the pending async_wait even
+ // if the owning ProcessExitWatcher is destroyed while a completion is
+ // in flight on the io_context.
m_Descriptor->async_wait(asio::posix::stream_descriptor::wait_read,
- [this, Callback = std::move(OnExit)](const asio::error_code& Ec) {
+ [Self = Ref<Impl>(this), Callback = std::move(OnExit)](const asio::error_code& Ec) {
if (Ec)
{
return;
@@ -228,11 +239,11 @@ struct ProcessExitWatcher::Impl
// Drain the kqueue event
struct kevent Event;
struct timespec Timeout = {0, 0};
- kevent(m_KqueueFd, nullptr, 0, &Event, 1, &Timeout);
+ kevent(Self->m_KqueueFd, nullptr, 0, &Event, 1, &Timeout);
int ExitCode = -1;
int Status = 0;
- if (waitpid(m_Pid, &Status, WNOHANG) > 0)
+ if (waitpid(Self->m_Pid, &Status, WNOHANG) > 0)
{
if (WIFEXITED(Status))
{
@@ -273,11 +284,21 @@ struct ProcessExitWatcher::Impl
// Common wrapper (delegates to Impl)
// ============================================================================
-ProcessExitWatcher::ProcessExitWatcher(asio::io_context& IoContext) : m_Impl(std::make_unique<Impl>(IoContext))
+ProcessExitWatcher::ProcessExitWatcher(asio::io_context& IoContext) : m_Impl(new Impl(IoContext))
{
}
-ProcessExitWatcher::~ProcessExitWatcher() = default;
+ProcessExitWatcher::~ProcessExitWatcher()
+{
+ // Explicitly cancel pending async ops. The Impl may outlive this call if a
+ // completion is still in the io_context queue (the handler holds a strong
+ // Ref back to Impl to keep it alive). Cancel() here guarantees the watch
+ // stops even if nobody has called Cancel() on the wrapper.
+ if (m_Impl)
+ {
+ m_Impl->Cancel();
+ }
+}
void
ProcessExitWatcher::Watch(const ProcessHandle& Handle, std::function<void(int ExitCode)> OnExit)