Skip to content

Commit 764c539

Browse files
committed
refs qorelanguage/qore#4272 fixed a race condition in Process::wait(); the exit code is set from the async on_exit_handler() as well as after wait() if necessary
1 parent e4d2c48 commit 764c539

File tree

7 files changed

+83
-77
lines changed

7 files changed

+83
-77
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ option(USE_INTERNAL_BOOST "Enforce using internal copy of boost libraries" OFF)
88

99
set (VERSION_MAJOR 1)
1010
set (VERSION_MINOR 0)
11-
set (VERSION_PATCH 2)
11+
set (VERSION_PATCH 3)
1212

1313
# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked
1414
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake )

qore-process-module.spec

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
Summary: process module for Qore
4646
Name: qore-process-module
47-
Version: 1.0.2
47+
Version: 1.0.3
4848
Release: 1%{dist}
4949
License: LGPL
5050
Group: Development/Languages
@@ -97,5 +97,8 @@ rm -rf $RPM_BUILD_ROOT
9797
%doc COPYING README test/process.qtest test/test_cwd.q test/test_env.q test/test_false.q test/test_io.q test/test_output.q test/test_sleep.q test/test_true.q test/test_utf8.q
9898

9999
%changelog
100+
* Mon Dec 27 2021 David Nichols <david.nichols@qoretechnologies.com>
101+
- updated to version 1.0.3
102+
100103
* Fri Sep 17 2021 David Nichols <david.nichols@qoretechnologies.com>
101104
- initial spec file

src/process.qpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ Version 1.0. (See http://www.boost.org/LICENSE_1_0.txt)
5353

5454
@section process_relnotes Process Module Release History
5555

56+
@subsection process_v1_0_3 process v1.0.3
57+
- fixed a race condition in @ref Qore::Process::Process::wait() "Process::wait()"
58+
(<a href="https://github.com/qorelanguage/qore/issues/4272">issue 4272</a>)
59+
5660
@subsection process_v1_0_2 process v1.0.2
5761
- updated builtin boost to 1.71
5862

src/processpriv.cpp

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ ProcessPriv::ProcessPriv(pid_t pid, ExceptionSink* xsink) :
6969
// default I/O buffer size
7070
static constexpr unsigned process_buf_size = 4096;
7171

72-
ProcessPriv::ProcessPriv(const char* command, const QoreListNode* arguments, const QoreHashNode *opts, ExceptionSink* xsink) :
72+
ProcessPriv::ProcessPriv(const char* command, const QoreListNode* arguments, const QoreHashNode *opts,
73+
ExceptionSink* xsink) :
7374
m_asio_ctx(),
7475
m_in_pipe(m_asio_ctx),
7576
m_out_pipe(m_asio_ctx),
@@ -139,15 +140,16 @@ ProcessPriv::ProcessPriv(const char* command, const QoreListNode* arguments, con
139140

140141
// launch child process
141142
try {
142-
QoreProcessHandler handler(xsink,
143-
on_success,
144-
on_setup,
145-
on_error,
146-
on_fork_error,
147-
on_exec_setup,
148-
on_exec_error);
149-
150-
launchChild(p, exeArgs, env, cwd.c_str(), handler, stdoutFile, stderrFile, xsink);
143+
handler = new QoreProcessHandler(xsink,
144+
on_success,
145+
on_setup,
146+
on_error,
147+
on_fork_error,
148+
on_exec_setup,
149+
on_exec_error,
150+
exit_code);
151+
152+
launchChild(p, exeArgs, env, cwd.c_str(), stdoutFile, stderrFile, xsink);
151153
} catch (const std::exception& ex) {
152154
xsink->raiseException("PROCESS-CONSTRUCTOR-ERROR", ex.what());
153155
}
@@ -159,6 +161,9 @@ ProcessPriv::ProcessPriv(const char* command, const QoreListNode* arguments, con
159161
}
160162

161163
ProcessPriv::~ProcessPriv() {
164+
if (handler) {
165+
delete handler;
166+
}
162167
// in case the object is obliterated (exception in constructor), the destructor is not run
163168
delete m_process;
164169
assert(!bg_xsink);
@@ -524,19 +529,18 @@ void ProcessPriv::prepareClosures() {
524529
}
525530

526531
void ProcessPriv::launchChild(boost::filesystem::path p,
527-
std::vector<std::string>& args,
528-
bp::environment env,
529-
const char* cwd,
530-
QoreProcessHandler& handler,
531-
FILE* stdoutFile,
532-
FILE* stderrFile,
533-
ExceptionSink* xsink) {
532+
std::vector<std::string>& args,
533+
bp::environment env,
534+
const char* cwd,
535+
FILE* stdoutFile,
536+
FILE* stderrFile,
537+
ExceptionSink* xsink) {
534538
if (stdoutFile && stderrFile) {
535539
m_process = new bp::child(bp::exe = p.string(),
536540
bp::args = args,
537541
bp::env = env,
538542
bp::start_dir = cwd,
539-
handler,
543+
*handler,
540544
bp::std_out > stdoutFile,
541545
bp::std_err > stderrFile,
542546
bp::std_in < m_in_pipe,
@@ -546,7 +550,7 @@ void ProcessPriv::launchChild(boost::filesystem::path p,
546550
bp::args = args,
547551
bp::env = env,
548552
bp::start_dir = cwd,
549-
handler,
553+
*handler,
550554
bp::std_out > stdoutFile,
551555
bp::std_err > m_err_pipe,
552556
bp::std_in < m_in_pipe,
@@ -556,7 +560,7 @@ void ProcessPriv::launchChild(boost::filesystem::path p,
556560
bp::args = args,
557561
bp::env = env,
558562
bp::start_dir = cwd,
559-
handler,
563+
*handler,
560564
bp::std_out > m_out_pipe,
561565
bp::std_err > stderrFile,
562566
bp::std_in < m_in_pipe,
@@ -566,7 +570,7 @@ void ProcessPriv::launchChild(boost::filesystem::path p,
566570
bp::args = args,
567571
bp::env = env,
568572
bp::start_dir = cwd,
569-
handler,
573+
*handler,
570574
bp::std_out > m_out_pipe,
571575
bp::std_err > m_err_pipe,
572576
bp::std_in < m_in_pipe,
@@ -608,7 +612,8 @@ void ProcessPriv::launchChild(boost::filesystem::path p,
608612
}
609613

610614
int ProcessPriv::exitCode(ExceptionSink* xsink) {
611-
if (!processCheck(xsink)) { return -1;
615+
if (!processCheck(xsink)) {
616+
return -1;
612617
}
613618

614619
return exit_code;
@@ -737,23 +742,24 @@ bool ProcessPriv::wait(ExceptionSink* xsink) {
737742
return true;
738743
}
739744

745+
//printd(5, "ProcessPriv::wait() valid: %d exit_code: %d\n", m_process->valid(), exit_code);
746+
740747
try {
741748
if (m_process->valid()) {
742749
std::error_code ec;
743750
m_process->wait(ec);
744-
if (ec) {
751+
if (ec && ec != std::errc::no_child_process) {
745752
xsink->raiseException("PROCESS-WAIT-ERROR", "cannot wait on process: %s", ec.message().c_str());
746753
return false;
747754
}
748755

749-
// get exit code if possible
750-
getExitCode(xsink);
756+
if (exit_code == -1) {
757+
// get exit code if possible
758+
getExitCode(xsink);
759+
}
751760

752761
// rethrows any background exceptions
753762
finalizeStreams(xsink);
754-
} else {
755-
// invalidate the exit code since the return value of m_process->exit_code() is without any meaning now
756-
exit_code = 0;
757763
}
758764
return true;
759765
} catch (const std::exception& ex) {
@@ -778,22 +784,21 @@ bool ProcessPriv::wait(int64 t, ExceptionSink* xsink) {
778784
if (m_process->valid()) {
779785
std::error_code ec;
780786
bool rv = m_process->wait_for(std::chrono::milliseconds(t), ec);
781-
if (ec) {
787+
if (ec && ec != std::errc::no_child_process) {
782788
xsink->raiseException("PROCESS-WAIT-ERROR", "cannot wait on process: %s", ec.message().c_str());
783789
return false;
784790
}
785791
if (rv) {
786-
// get exit code if possible
787-
getExitCode(xsink);
792+
if (exit_code == -1) {
793+
// get exit code if possible
794+
getExitCode(xsink);
795+
}
788796

789797
// rethrows any background exceptions
790798
finalizeStreams(xsink);
791799

792800
return true;
793801
}
794-
} else {
795-
// invalidate the exit code since the return value of m_process->exit_code() is without any meaning now
796-
exit_code = 0;
797802
}
798803
return false;
799804
} catch (const std::exception& ex) {

src/processpriv.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,6 @@ class ProcessPriv : public AbstractPrivateData {
146146
std::vector<std::string>& args,
147147
bp::environment env,
148148
const char* cwd,
149-
QoreProcessHandler& handler,
150149
FILE* stdoutFile,
151150
FILE* stderrFile,
152151
ExceptionSink* xsink);
@@ -474,6 +473,9 @@ class ProcessPriv : public AbstractPrivateData {
474473
}
475474
};
476475

476+
//! async process shandler
477+
QoreProcessHandler* handler = nullptr;
478+
477479
//! Child process.
478480
bp::child* m_process = nullptr;
479481

src/qoreprocesshandler.h

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
Qore Programming Language process Module
33
4-
Copyright (C) 2003 - 2020 Qore Technologies, s.r.o.
4+
Copyright (C) 2003 - 2021 Qore Technologies, s.r.o.
55
66
Permission is hereby granted, free of charge, to any person obtaining a
77
copy of this software and associated documentation files (the "Software"),
@@ -33,79 +33,60 @@
3333
namespace bp = boost::process;
3434
namespace ex = boost::process::extend;
3535

36-
3736
class QoreProcessHandler : public ex::async_handler {
3837
public:
39-
QoreProcessHandler(ExceptionSink* xsink,
40-
ResolvedCallReferenceNode* on_success,
41-
ResolvedCallReferenceNode* on_setup,
42-
ResolvedCallReferenceNode* on_error,
43-
ResolvedCallReferenceNode* on_fork_error,
44-
ResolvedCallReferenceNode* on_exec_setup,
45-
ResolvedCallReferenceNode* on_exec_error) :
38+
DLLLOCAL QoreProcessHandler(ExceptionSink* xsink,
39+
ResolvedCallReferenceNode* on_success,
40+
ResolvedCallReferenceNode* on_setup,
41+
ResolvedCallReferenceNode* on_error,
42+
ResolvedCallReferenceNode* on_fork_error,
43+
ResolvedCallReferenceNode* on_exec_setup,
44+
ResolvedCallReferenceNode* on_exec_error,
45+
int& exit_code) :
4646
m_xsink(xsink),
4747
m_on_success(on_success, xsink),
4848
m_on_setup(on_setup, xsink),
4949
m_on_error(on_error, xsink),
5050
m_on_fork_error(on_fork_error, xsink),
5151
m_on_exec_setup(on_exec_setup, xsink),
52-
m_on_exec_error(on_exec_error, xsink) {
52+
m_on_exec_error(on_exec_error, xsink),
53+
exit_code(exit_code) {
5354
}
5455

55-
QoreProcessHandler(QoreProcessHandler& old) :
56-
m_xsink(old.m_xsink),
57-
m_on_success(*old.m_on_success, old.m_xsink),
58-
m_on_setup(*old.m_on_setup, old.m_xsink),
59-
m_on_error(*old.m_on_error, old.m_xsink),
60-
m_on_fork_error(*old.m_on_fork_error, old.m_xsink),
61-
m_on_exec_setup(*old.m_on_exec_setup, old.m_xsink),
62-
m_on_exec_error(*old.m_on_exec_error, old.m_xsink) {
63-
if (m_on_success)
64-
m_on_success->ref();
65-
if (m_on_setup)
66-
m_on_setup->ref();
67-
if (m_on_error)
68-
m_on_error->ref();
69-
if (m_on_fork_error)
70-
m_on_fork_error->ref();
71-
if (m_on_exec_setup)
72-
m_on_exec_setup->ref();
73-
if (m_on_exec_error)
74-
m_on_exec_error->ref();
75-
}
56+
QoreProcessHandler(QoreProcessHandler& old) = delete;
7657

7758
template<typename Executor>
78-
void on_success(Executor& exec) const {
59+
DLLLOCAL void on_success(Executor& exec) const {
7960
call("on_success", *m_on_success, exec, std::error_code());
8061
}
8162

8263
template<typename Executor>
83-
void on_setup(Executor& exec) const {
64+
DLLLOCAL void on_setup(Executor& exec) const {
8465
call("on_setup", *m_on_setup, exec, std::error_code());
8566
}
8667

8768
template<typename Executor>
88-
void on_error(Executor& exec, const std::error_code& ec) const {
69+
DLLLOCAL void on_error(Executor& exec, const std::error_code& ec) const {
8970
call("on_error", *m_on_error, exec, ec);
9071
}
9172

9273
template<typename Executor>
93-
void on_fork_error(Executor& exec, const std::error_code& ec) const {
74+
DLLLOCAL void on_fork_error(Executor& exec, const std::error_code& ec) const {
9475
call("on_fork_error", *m_on_fork_error, exec, ec);
9576
}
9677

9778
template<typename Executor>
98-
void on_exec_setup(Executor& exec) const {
79+
DLLLOCAL void on_exec_setup(Executor& exec) const {
9980
call("on_exec_setup", *m_on_exec_setup, exec, std::error_code());
10081
}
10182

10283
template<typename Executor>
103-
void on_exec_error(Executor& exec, const std::error_code& ec) const {
84+
DLLLOCAL void on_exec_error(Executor& exec, const std::error_code& ec) const {
10485
call("on_exec_error", *m_on_exec_error, exec, ec);
10586
}
10687

10788
template<typename Executor>
108-
void call(const char* info,
89+
DLLLOCAL void call(const char* info,
10990
const ResolvedCallReferenceNode* callref,
11091
Executor& exec,
11192
const std::error_code& ec) const {
@@ -135,16 +116,26 @@ class QoreProcessHandler : public ex::async_handler {
135116
}
136117

137118
template<typename Executor>
138-
std::function<void(int, const std::error_code&)> on_exit_handler(Executor& exec) {
119+
DLLLOCAL std::function<void(int, const std::error_code&)> on_exit_handler(Executor& exec) {
139120
boost::asio::io_context& ios = ex::get_io_context(exec.seq);
140-
return [&ios](int exit_code, const std::error_code& ec) {
121+
return [this, &ios](int exit_code, const std::error_code& ec) {
141122
ios.stop();
142123
ios.run();
124+
125+
#if defined(BOOST_POSIX_API)
126+
if (WIFEXITED(exit_code)) {
127+
this->exit_code = WEXITSTATUS(exit_code);
128+
//printd(5, "on_exit_handler() set exit_code: %d\n", exit_code);
129+
}
130+
#else
131+
this->exit_code = exit_code;
132+
#endif
143133
};
144134
}
145135

146136
private:
147137
ExceptionSink* m_xsink;
138+
int& exit_code;
148139

149140
ReferenceHolder<ResolvedCallReferenceNode> m_on_success;
150141
ReferenceHolder<ResolvedCallReferenceNode> m_on_setup;

test/process.qtest

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class Main inherits QUnit::Test {
6262
"simpleTest_exe": "ls",
6363
"simpleTest_arg": ("-l", "-s",),
6464
"executorsTest_success": "Undefined error: 0",
65+
"executorsTest_exit_code_383": 383,
6566
},
6667
);
6768
}

0 commit comments

Comments
 (0)