Blender V5.0
BLI_subprocess.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_subprocess.hh"
10
11#if BLI_SUBPROCESS_SUPPORT
12
13/* Based on https://github.com/jarikomppa/ipc (Unlicense) */
14
15# include "BLI_assert.h"
16# include "BLI_path_utils.hh"
17# include "BLI_string_utf8.h"
18# include <iostream>
19
20namespace blender {
21
22static bool check_arguments_are_valid(Span<StringRefNull> args)
23{
24 for (StringRefNull arg : args) {
25 for (const char c : arg) {
26 if (!std::isalnum(c) && !ELEM(c, '_', '-')) {
27 return false;
28 }
29 }
30 }
31
32 return true;
33}
34
35} // namespace blender
36
37# ifdef _WIN32
38
39# define WIN32_LEAN_AND_MEAN
40# include <comdef.h>
41# include <windows.h>
42
43namespace blender {
44
45static void print_last_error(const char *function, const char *msg)
46{
47 DWORD error_code = GetLastError();
48 std::cerr << "ERROR (" << error_code << "): " << function << " : " << msg << std::endl;
49}
50
51static void check(bool result, const char *function, const char *msg)
52{
53 if (!result) {
54 print_last_error(function, msg);
55 BLI_assert(false);
56 }
57}
58
59# define CHECK(result) check((result), __func__, #result)
60# undef ERROR /* Defined in wingdi.h */
61# define ERROR(msg) check(false, __func__, msg)
62
63/* Owning process group that will close subprocesses assigned to it when the instance is destructed
64 * or the creator process ends. */
65class ProcessGroup {
66 private:
67 HANDLE handle_;
68
69 public:
70 ProcessGroup()
71 {
72 handle_ = CreateJobObject(nullptr, nullptr);
73 CHECK(handle_);
74 JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = {0};
75 info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
76 CHECK(
77 SetInformationJobObject(handle_, JobObjectExtendedLimitInformation, &info, sizeof(info)));
78 }
79
80 ~ProcessGroup()
81 {
82 CHECK(CloseHandle(handle_));
83 }
84
85 void assign_subprocess(HANDLE subprocess)
86 {
87 CHECK(AssignProcessToJobObject(handle_, subprocess));
88 }
89};
90
91bool BlenderSubprocess::create(Span<StringRefNull> args)
92{
93 BLI_assert(handle_ == nullptr);
94
95 if (!check_arguments_are_valid(args)) {
96 BLI_assert(false);
97 return false;
98 }
99
100 wchar_t path[FILE_MAX];
101 if (!GetModuleFileNameW(nullptr, path, FILE_MAX)) {
102 ERROR("GetModuleFileNameW");
103 return false;
104 }
105
106 std::string args_str;
107 for (StringRefNull arg : args) {
108 args_str += arg + " ";
109 }
110
111 const int length_wc = MultiByteToWideChar(
112 CP_UTF8, 0, args_str.c_str(), args_str.length(), nullptr, 0);
113 std::wstring w_args(length_wc, 0);
114 CHECK(MultiByteToWideChar(
115 CP_UTF8, 0, args_str.c_str(), args_str.length(), w_args.data(), length_wc));
116
117 STARTUPINFOW startup_info = {0};
118 startup_info.cb = sizeof(startup_info);
119 PROCESS_INFORMATION process_info = {0};
120 if (!CreateProcessW(path,
122 w_args.data(),
123 nullptr,
124 nullptr,
125 false,
126 CREATE_BREAKAWAY_FROM_JOB,
127 nullptr,
128 nullptr,
129 &startup_info,
130 &process_info))
131 {
132 ERROR("CreateProcessW");
133 return false;
134 }
135
136 handle_ = process_info.hProcess;
137 CHECK(CloseHandle(process_info.hThread));
138
139 static ProcessGroup group;
140 /* Don't let the subprocess outlive its parent. */
141 group.assign_subprocess(handle_);
142
143 return true;
144}
145
146BlenderSubprocess::~BlenderSubprocess()
147{
148 if (handle_) {
149 CHECK(CloseHandle(handle_));
150 }
151}
152
153bool BlenderSubprocess::is_running()
154{
155 if (!handle_) {
156 return false;
157 }
158
159 DWORD exit_code = 0;
160 if (GetExitCodeProcess(handle_, &exit_code)) {
161 return exit_code == STILL_ACTIVE;
162 }
163
164 ERROR("GetExitCodeProcess");
165 /* Assume the process is still running. */
166 return true;
167}
168
169SharedMemory::SharedMemory(std::string name, size_t size, bool is_owner)
170 : name_(name), is_owner_(is_owner)
171{
172 if (is_owner) {
173 handle_ = CreateFileMappingA(
174 INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, size, name.c_str());
175 CHECK(handle_ /*Create*/);
176 }
177 else {
178 handle_ = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, name.c_str());
179 CHECK(handle_ /*Open*/);
180 }
181
182 if (handle_) {
183 data_ = MapViewOfFile(handle_, FILE_MAP_ALL_ACCESS, 0, 0, size);
184 CHECK(data_);
185 }
186 else {
187 data_ = nullptr;
188 }
189
190 data_size_ = data_ ? size : 0;
191}
192
193SharedMemory::~SharedMemory()
194{
195 if (data_) {
196 CHECK(UnmapViewOfFile(data_));
197 }
198 if (handle_) {
199 CHECK(CloseHandle(handle_));
200 }
201}
202
203SharedSemaphore::SharedSemaphore(std::string name, bool is_owner)
204 : name_(name), is_owner_(is_owner)
205{
206 handle_ = CreateSemaphoreA(nullptr, 0, 1, name.c_str());
207 CHECK(handle_);
208}
209
210SharedSemaphore::~SharedSemaphore()
211{
212 if (handle_) {
213 CHECK(CloseHandle(handle_));
214 }
215}
216
217void SharedSemaphore::increment()
218{
219 CHECK(ReleaseSemaphore(handle_, 1, nullptr));
220}
221
222void SharedSemaphore::decrement()
223{
224 CHECK(WaitForSingleObject(handle_, INFINITE) != WAIT_FAILED);
225}
226
227bool SharedSemaphore::try_decrement(int wait_ms)
228{
229 DWORD result = WaitForSingleObject(handle_, wait_ms);
230 CHECK(result != WAIT_FAILED);
231 return result == WAIT_OBJECT_0;
232}
233
234} // namespace blender
235
236# elif defined(__linux__)
237
238# include "BLI_time.h"
239# include "BLI_vector.hh"
240# include <fcntl.h>
241# include <linux/limits.h>
242# include <stdlib.h>
243# include <sys/mman.h>
244# include <sys/stat.h>
245# include <unistd.h>
246# include <wait.h>
247
248namespace blender {
249
250static void print_last_error(const char *function, const char *msg)
251{
252 int error_code = errno;
253 std::string error_msg = "ERROR (" + std::to_string(error_code) + "): " + function + " : " + msg;
254 perror(error_msg.c_str());
255}
256
257static void check(int result, const char *function, const char *msg)
258{
259 if (result == -1) {
260 print_last_error(function, msg);
261 BLI_assert(false);
262 }
263}
264
265# define CHECK(result) check((result), __func__, #result)
266# define ERROR(msg) check(-1, __func__, msg)
267
268bool BlenderSubprocess::create(Span<StringRefNull> args)
269{
270 if (!check_arguments_are_valid(args)) {
271 BLI_assert(false);
272 return false;
273 }
274
275 char path[PATH_MAX + 1];
276 const size_t len = readlink("/proc/self/exe", path, PATH_MAX);
277 if (len == size_t(-1)) {
278 ERROR("readlink");
279 return false;
280 }
281 /* readlink doesn't append a null terminator. */
282 path[len] = '\0';
283
284 Vector<char *> char_args;
285 for (StringRefNull arg : args) {
286 char_args.append((char *)arg.data());
287 }
288 char_args.append(nullptr);
289
290 pid_ = fork();
291
292 if (pid_ == -1) {
293 ERROR("fork");
294 return false;
295 }
296 if (pid_ > 0) {
297 return true;
298 }
299
300 /* Child process initialization. */
301 execv(path, char_args.data());
302
303 /* This should only be reached if `execvp` fails and stack isn't replaced. */
304 ERROR("execv");
305
306 /* Ensure outputs are flushed as `_exit` doesn't flush. */
307 fflush(stdout);
308 fflush(stderr);
309
310 /* Use `_exit` instead of `exit` so Blender's `atexit` cleanup functions don't run. */
311 _exit(errno);
313 return false;
314}
315
316BlenderSubprocess::~BlenderSubprocess() {}
317
318bool BlenderSubprocess::is_running()
319{
320 if (pid_ == -1) {
321 return false;
322 }
323
324 pid_t result = waitpid(pid_, nullptr, WNOHANG);
325 CHECK(result);
326
327 if (result == pid_) {
328 pid_ = -1;
329 return false;
330 }
331
332 return true;
333}
334
335SharedMemory::SharedMemory(std::string name, size_t size, bool is_owner)
336 : name_(name), is_owner_(is_owner)
337{
338 constexpr mode_t user_mode = S_IRUSR | S_IWUSR;
339 if (is_owner) {
340 handle_ = shm_open(name.c_str(), O_CREAT | O_EXCL | O_RDWR, user_mode);
341 CHECK(handle_);
342 if (handle_ != -1) {
343 if (ftruncate(handle_, size) == -1) {
344 ERROR("ftruncate");
345 CHECK(close(handle_));
346 handle_ = -1;
347 }
348 }
349 }
350 else {
351 handle_ = shm_open(name.c_str(), O_RDWR, user_mode);
352 CHECK(handle_);
353 }
354
355 if (handle_ != -1) {
356 data_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle_, 0);
357 if (data_ == MAP_FAILED) {
358 ERROR("mmap");
359 data_ = nullptr;
360 }
361 /* File descriptor can close after mmap. */
362 CHECK(close(handle_));
363 }
364 else {
365 data_ = nullptr;
366 }
367
368 data_size_ = data_ ? size : 0;
369}
370
371SharedMemory::~SharedMemory()
372{
373 if (data_) {
374 CHECK(munmap(data_, data_size_));
375 if (is_owner_) {
376 CHECK(shm_unlink(name_.c_str()));
377 }
378 }
379}
380
381SharedSemaphore::SharedSemaphore(std::string name, bool is_owner)
382 : name_(name), is_owner_(is_owner)
383{
384 constexpr mode_t user_mode = S_IRUSR | S_IWUSR;
385 handle_ = sem_open(name.c_str(), O_CREAT, user_mode, 0);
386 if (!handle_) {
387 ERROR("sem_open");
388 }
389}
390
391SharedSemaphore::~SharedSemaphore()
392{
393 if (handle_) {
394 CHECK(sem_close(handle_));
395 if (is_owner_) {
396 CHECK(sem_unlink(name_.c_str()));
397 }
398 }
399}
400
401void SharedSemaphore::increment()
402{
403 CHECK(sem_post(handle_));
404}
405
406void SharedSemaphore::decrement()
407{
408 while (true) {
409 int result = sem_wait(handle_);
410 if (result == 0) {
411 return;
412 }
413 if (errno != EINTR) {
414 ERROR("sem_wait");
415 return;
416 }
417 /* Try again if interrupted by handler. */
418 }
419}
420
421bool SharedSemaphore::try_decrement(int wait_ms)
422{
423 if (wait_ms == 0) {
424 int result = sem_trywait(handle_);
425 if (result == 0) {
426 return true;
427 }
428 if (errno == EINVAL) {
429 ERROR("sem_trywait");
430 }
431 return false;
432 }
433
434 timespec time;
435 if (clock_gettime(CLOCK_REALTIME, &time) == -1) {
436 ERROR("clock_gettime");
437 BLI_time_sleep_ms(wait_ms);
438 return try_decrement(0);
439 }
440
441 time.tv_sec += wait_ms / 1000;
442 time.tv_nsec += (wait_ms % 1000) * 10e6;
443
444 while (true) {
445 int result = sem_timedwait(handle_, &time);
446 if (result == 0) {
447 return true;
448 }
449 if (errno != EINTR) {
450 if (errno != ETIMEDOUT) {
451 ERROR("sem_timedwait");
452 }
453 return false;
454 }
455 /* Try again if interrupted by handler. */
456 }
457}
458
459} // namespace blender
460
461# endif
462
463#endif
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define PATH_MAX
Definition BLI_fileops.h:26
#define FILE_MAX
Platform independent time functions.
void BLI_time_sleep_ms(int ms)
Definition time.cc:133
#define ELEM(...)
unsigned int mode_t
#define FALSE
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
void append(const T &value)
T * data()
#define CHECK(expression)
Definition log.h:116
const char * name
uint len