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