Skip to content

Commit 25be3cd

Browse files
committed
Core/Common: Output stdout/stderr from child process without waiting for it to finish
(cherry picked from commit f270686201c84a07f67a9615fd610d917fc8f802)
1 parent bbc8e22 commit 25be3cd

File tree

3 files changed

+113
-148
lines changed

3 files changed

+113
-148
lines changed

src/common/Utilities/StartProcess.cpp

Lines changed: 106 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -28,117 +28,10 @@
2828
#include <boost/process/search_path.hpp>
2929
#include <fmt/ranges.h>
3030

31-
using namespace boost::process;
31+
namespace bp = boost::process;
3232

3333
namespace Trinity
3434
{
35-
template<typename T>
36-
static int CreateChildProcess(T waiter, std::string const& executable,
37-
std::vector<std::string> const& argsVector,
38-
std::string const& logger, std::string const& input,
39-
bool secure)
40-
{
41-
#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
42-
#pragma warning(push)
43-
#pragma warning(disable:4297)
44-
/*
45-
Silence warning with boost 1.83
46-
47-
boost/process/pipe.hpp(132,5): warning C4297: 'boost::process::basic_pipebuf<char,std::char_traits<char>>::~basic_pipebuf': function assumed not to throw an exception but does
48-
boost/process/pipe.hpp(132,5): message : destructor or deallocator has a (possibly implicit) non-throwing exception specification
49-
boost/process/pipe.hpp(124,6): message : while compiling class template member function 'boost::process::basic_pipebuf<char,std::char_traits<char>>::~basic_pipebuf(void)'
50-
boost/process/pipe.hpp(304,42): message : see reference to class template instantiation 'boost::process::basic_pipebuf<char,std::char_traits<char>>' being compiled
51-
*/
52-
#endif
53-
ipstream outStream;
54-
ipstream errStream;
55-
#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
56-
#pragma warning(pop)
57-
#endif
58-
59-
if (!secure)
60-
{
61-
TC_LOG_TRACE(logger, "Starting process \"{}\" with arguments: \"{}\".",
62-
executable, fmt::join(argsVector, " "));
63-
}
64-
65-
// prepare file with only read permission (boost process opens with read_write)
66-
auto inputFile = std::shared_ptr<FILE>(!input.empty() ? fopen(input.c_str(), "rb") : nullptr, [](FILE* f) { if (f) fclose(f); });
67-
68-
// Start the child process
69-
child c = [&]()
70-
{
71-
if (inputFile)
72-
{
73-
// With binding stdin
74-
return child{
75-
exe = boost::filesystem::absolute(executable).string(),
76-
args = argsVector,
77-
env = environment(boost::this_process::environment()),
78-
std_in = inputFile.get(),
79-
std_out = outStream,
80-
std_err = errStream
81-
};
82-
}
83-
else
84-
{
85-
// Without binding stdin
86-
return child{
87-
exe = boost::filesystem::absolute(executable).string(),
88-
args = argsVector,
89-
env = environment(boost::this_process::environment()),
90-
std_in = boost::process::close,
91-
std_out = outStream,
92-
std_err = errStream
93-
};
94-
}
95-
}();
96-
97-
std::string line;
98-
while (std::getline(outStream, line, '\n'))
99-
{
100-
std::erase(line, '\r');
101-
if (!line.empty())
102-
TC_LOG_INFO(logger, "{}", line);
103-
}
104-
105-
while (std::getline(errStream, line, '\n'))
106-
{
107-
std::erase(line, '\r');
108-
if (!line.empty())
109-
TC_LOG_ERROR(logger, "{}", line);
110-
}
111-
112-
// Call the waiter in the current scope to prevent
113-
// the streams from closing too early on leaving the scope.
114-
int const result = waiter(c);
115-
116-
if (!secure)
117-
{
118-
TC_LOG_TRACE(logger, ">> Process \"{}\" finished with return value {}.",
119-
executable, result);
120-
}
121-
122-
return result;
123-
}
124-
125-
int StartProcess(std::string const& executable, std::vector<std::string> const& args,
126-
std::string const& logger, std::string input_file, bool secure)
127-
{
128-
return CreateChildProcess([](child& c) -> int
129-
{
130-
try
131-
{
132-
c.wait();
133-
return c.exit_code();
134-
}
135-
catch (...)
136-
{
137-
return EXIT_FAILURE;
138-
}
139-
}, executable, args, logger, input_file, secure);
140-
}
141-
14235
class AsyncProcessResultImplementation
14336
: public AsyncProcessResult
14437
{
@@ -150,59 +43,129 @@ class AsyncProcessResultImplementation
15043

15144
std::atomic<bool> was_terminated;
15245

153-
// Workaround for missing move support in boost < 1.57
154-
Optional<std::shared_ptr<std::future<int>>> result;
155-
Optional<std::reference_wrapper<child>> my_child;
46+
Optional<std::future<int>> futureResult;
47+
Optional<bp::child> my_child;
15648

15749
public:
15850
explicit AsyncProcessResultImplementation(std::string executable_, std::vector<std::string> args_,
15951
std::string logger_, std::string input_file_,
16052
bool secure)
16153
: executable(std::move(executable_)), args(std::move(args_)),
162-
logger(std::move(logger_)), input_file(input_file_),
54+
logger(std::move(logger_)), input_file(std::move(input_file_)),
16355
is_secure(secure), was_terminated(false) { }
16456

16557
AsyncProcessResultImplementation(AsyncProcessResultImplementation const&) = delete;
16658
AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation const&) = delete;
16759
AsyncProcessResultImplementation(AsyncProcessResultImplementation&&) = delete;
16860
AsyncProcessResultImplementation& operator= (AsyncProcessResultImplementation&&) = delete;
16961

62+
~AsyncProcessResultImplementation() = default;
63+
17064
int StartProcess()
17165
{
17266
ASSERT(!my_child, "Process started already!");
17367

174-
return CreateChildProcess([&](child& c) -> int
68+
#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
69+
#pragma warning(push)
70+
#pragma warning(disable:4297)
71+
/*
72+
Silence warning with boost 1.83
73+
74+
boost/process/pipe.hpp(132,5): warning C4297: 'boost::process::basic_pipebuf<char,std::char_traits<char>>::~basic_pipebuf': function assumed not to throw an exception but does
75+
boost/process/pipe.hpp(132,5): message : destructor or deallocator has a (possibly implicit) non-throwing exception specification
76+
boost/process/pipe.hpp(124,6): message : while compiling class template member function 'boost::process::basic_pipebuf<char,std::char_traits<char>>::~basic_pipebuf(void)'
77+
boost/process/pipe.hpp(304,42): message : see reference to class template instantiation 'boost::process::basic_pipebuf<char,std::char_traits<char>>' being compiled
78+
*/
79+
#endif
80+
bp::ipstream outStream;
81+
bp::ipstream errStream;
82+
#if TRINITY_COMPILER == TRINITY_COMPILER_MICROSOFT
83+
#pragma warning(pop)
84+
#endif
85+
86+
if (!is_secure)
87+
{
88+
TC_LOG_TRACE(logger, "Starting process \"{}\" with arguments: \"{}\".",
89+
executable, fmt::join(args, " "));
90+
}
91+
92+
// prepare file with only read permission (boost process opens with read_write)
93+
auto inputFile = std::shared_ptr<FILE>(!input_file.empty() ? fopen(input_file.c_str(), "rb") : nullptr, [](FILE* f) { if (f) fclose(f); });
94+
95+
// Start the child process
96+
if (inputFile)
97+
{
98+
my_child.emplace(
99+
bp::exe = boost::filesystem::absolute(executable).string(),
100+
bp::args = args,
101+
bp::env = bp::environment(boost::this_process::environment()),
102+
bp::std_in = inputFile.get(),
103+
bp::std_out = outStream,
104+
bp::std_err = errStream
105+
);
106+
}
107+
else
175108
{
176-
int result;
177-
my_child = std::reference_wrapper<child>(c);
109+
my_child.emplace(
110+
bp::exe = boost::filesystem::absolute(executable).string(),
111+
bp::args = args,
112+
bp::env = bp::environment(boost::this_process::environment()),
113+
bp::std_in = boost::process::close,
114+
bp::std_out = outStream,
115+
bp::std_err = errStream
116+
);
117+
}
178118

179-
try
119+
std::future<void> stdOutReader = std::async(std::launch::async, [&]
120+
{
121+
std::string line;
122+
while (std::getline(outStream, line, '\n'))
180123
{
181-
c.wait();
182-
result = c.exit_code();
124+
std::erase(line, '\r');
125+
if (!line.empty())
126+
TC_LOG_INFO(logger, "{}", line);
183127
}
184-
catch (...)
128+
});
129+
130+
std::future<void> stdErrReader = std::async(std::launch::async, [&]
131+
{
132+
std::string line;
133+
while (std::getline(errStream, line, '\n'))
185134
{
186-
result = EXIT_FAILURE;
135+
std::erase(line, '\r');
136+
if (!line.empty())
137+
TC_LOG_ERROR(logger, "{}", line);
187138
}
139+
});
188140

189-
my_child.reset();
190-
return was_terminated ? EXIT_FAILURE : result;
141+
std::error_code ec;
142+
my_child->wait(ec);
143+
int32 const result = !ec && !was_terminated ? my_child->exit_code() : EXIT_FAILURE;
144+
my_child.reset();
191145

192-
}, executable, args, logger, input_file, is_secure);
146+
stdOutReader.wait();
147+
stdErrReader.wait();
148+
149+
if (!is_secure)
150+
{
151+
TC_LOG_TRACE(logger, ">> Process \"{}\" finished with return value {}.",
152+
executable, result);
153+
}
154+
155+
return result;
193156
}
194157

195-
void SetFuture(std::future<int> result_)
158+
void SetFuture(std::future<int32> result_)
196159
{
197-
result = std::make_shared<std::future<int>>(std::move(result_));
160+
futureResult.emplace(std::move(result_));
198161
}
199162

200163
/// Returns the future which contains the result of the process
201164
/// as soon it is finished.
202-
std::future<int>& GetFutureResult() override
165+
std::future<int32>& GetFutureResult() override
203166
{
204-
ASSERT(*result, "The process wasn't started!");
205-
return **result;
167+
ASSERT(futureResult.has_value(), "The process wasn't started!");
168+
return *futureResult;
206169
}
207170

208171
/// Tries to terminate the process
@@ -211,23 +174,25 @@ class AsyncProcessResultImplementation
211174
if (my_child)
212175
{
213176
was_terminated = true;
214-
try
215-
{
216-
my_child->get().terminate();
217-
}
218-
catch(...)
219-
{
220-
// Do nothing
221-
}
177+
std::error_code ec;
178+
my_child->terminate(ec);
222179
}
223180
}
224181
};
225182

226-
std::shared_ptr<AsyncProcessResult>
227-
StartAsyncProcess(std::string executable, std::vector<std::string> args,
228-
std::string logger, std::string input_file, bool secure)
183+
int StartProcess(std::string executable, std::vector<std::string> args,
184+
std::string logger, std::string input_file, bool secure)
185+
{
186+
AsyncProcessResultImplementation handle(
187+
std::move(executable), std::move(args), std::move(logger), std::move(input_file), secure);
188+
189+
return handle.StartProcess();
190+
}
191+
192+
std::shared_ptr<AsyncProcessResult> StartAsyncProcess(std::string executable, std::vector<std::string> args,
193+
std::string logger, std::string input_file, bool secure)
229194
{
230-
auto handle = std::make_shared<AsyncProcessResultImplementation>(
195+
std::shared_ptr<AsyncProcessResultImplementation> handle = std::make_shared<AsyncProcessResultImplementation>(
231196
std::move(executable), std::move(args), std::move(logger), std::move(input_file), secure);
232197

233198
handle->SetFuture(std::async(std::launch::async, [handle] { return handle->StartProcess(); }));
@@ -238,7 +203,7 @@ std::string SearchExecutableInPath(std::string const& filename)
238203
{
239204
try
240205
{
241-
return search_path(filename).string();
206+
return bp::search_path(filename).string();
242207
}
243208
catch (...)
244209
{

src/common/Utilities/StartProcess.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
* with this program. If not, see <http://www.gnu.org/licenses/>.
1616
*/
1717

18-
#ifndef Process_h__
19-
#define Process_h__
18+
#ifndef TRINITYCORE_START_PROCESS_H
19+
#define TRINITYCORE_START_PROCESS_H
2020

2121
#include "Define.h"
2222
#include <future>
@@ -32,8 +32,8 @@ namespace Trinity
3232
/// When an input path is given, the file will be routed to the processes stdin.
3333
/// When the process is marked as secure no arguments are leaked to logs.
3434
/// Note that most executables expect it's name as the first argument.
35-
TC_COMMON_API int StartProcess(std::string const& executable, std::vector<std::string> const& args,
36-
std::string const& logger, std::string input_file = "",
35+
TC_COMMON_API int32 StartProcess(std::string executable, std::vector<std::string> args,
36+
std::string logger, std::string input_file = "",
3737
bool secure = false);
3838

3939
/// Platform and library independent representation
@@ -45,7 +45,7 @@ class AsyncProcessResult
4545

4646
/// Returns the future which contains the result of the process
4747
/// as soon it is finished.
48-
virtual std::future<int>& GetFutureResult() = 0;
48+
virtual std::future<int32>& GetFutureResult() = 0;
4949

5050
/// Tries to terminate the process
5151
virtual void Terminate() = 0;
@@ -67,4 +67,4 @@ TC_COMMON_API std::string SearchExecutableInPath(std::string const& filename);
6767

6868
} // namespace Trinity
6969

70-
#endif // Process_h__
70+
#endif // TRINITYCORE_START_PROCESS_H

src/server/database/Updater/DBUpdater.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& hos
391391
args.emplace_back(database);
392392

393393
// Invokes a mysql process which doesn't leak credentials to logs
394-
int const ret = Trinity::StartProcess(DBUpdaterUtil::GetCorrectedMySQLExecutable(), args,
394+
int const ret = Trinity::StartProcess(DBUpdaterUtil::GetCorrectedMySQLExecutable(), std::move(args),
395395
"sql.updates", "", true);
396396

397397
if (ret != EXIT_SUCCESS)

0 commit comments

Comments
 (0)