Merge pull request #94434 from bruvzg/nonblock_pipes

Add support for non-blocking IO mode to `OS.execute_with_pipe`.
This commit is contained in:
Rémi Verschelde 2024-09-06 22:38:21 +02:00
commit 8aae31b879
No known key found for this signature in database
GPG Key ID: C3336907360768E1
16 changed files with 67 additions and 26 deletions

View File

@ -32,6 +32,8 @@
namespace core_bind { namespace core_bind {
// Semaphore
void Semaphore::_post_bind_compat_93605() { void Semaphore::_post_bind_compat_93605() {
post(1); post(1);
} }
@ -40,6 +42,16 @@ void Semaphore::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605); ClassDB::bind_compatibility_method(D_METHOD("post"), &Semaphore::_post_bind_compat_93605);
} }
}; // namespace core_bind // OS
#endif Dictionary OS::_execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments) {
return execute_with_pipe(p_path, p_arguments, true);
}
void OS::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::_execute_with_pipe_bind_compat_94434);
}
} // namespace core_bind
#endif // DISABLE_DEPRECATED

View File

@ -314,12 +314,12 @@ int OS::execute(const String &p_path, const Vector<String> &p_arguments, Array r
return exitcode; return exitcode;
} }
Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments) { Dictionary OS::execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking) {
List<String> args; List<String> args;
for (const String &arg : p_arguments) { for (const String &arg : p_arguments) {
args.push_back(arg); args.push_back(arg);
} }
return ::OS::get_singleton()->execute_with_pipe(p_path, args); return ::OS::get_singleton()->execute_with_pipe(p_path, args, p_blocking);
} }
int OS::create_instance(const Vector<String> &p_arguments) { int OS::create_instance(const Vector<String> &p_arguments) {
@ -619,7 +619,7 @@ void OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path); ClassDB::bind_method(D_METHOD("get_executable_path"), &OS::get_executable_path);
ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin); ClassDB::bind_method(D_METHOD("read_string_from_stdin"), &OS::read_string_from_stdin);
ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("execute", "path", "arguments", "output", "read_stderr", "open_console"), &OS::execute, DEFVAL(Array()), DEFVAL(false), DEFVAL(false));
ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments"), &OS::execute_with_pipe); ClassDB::bind_method(D_METHOD("execute_with_pipe", "path", "arguments", "blocking"), &OS::execute_with_pipe, DEFVAL(true));
ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_process", "path", "arguments", "open_console"), &OS::create_process, DEFVAL(false));
ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance); ClassDB::bind_method(D_METHOD("create_instance", "arguments"), &OS::create_instance);
ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill); ClassDB::bind_method(D_METHOD("kill", "pid"), &OS::kill);

View File

@ -128,6 +128,12 @@ protected:
static void _bind_methods(); static void _bind_methods();
static OS *singleton; static OS *singleton;
#ifndef DISABLE_DEPRECATED
Dictionary _execute_with_pipe_bind_compat_94434(const String &p_path, const Vector<String> &p_arguments);
static void _bind_compatibility_methods();
#endif
public: public:
enum RenderingDriver { enum RenderingDriver {
RENDERING_DRIVER_VULKAN, RENDERING_DRIVER_VULKAN,
@ -161,7 +167,7 @@ public:
String get_executable_path() const; String get_executable_path() const;
String read_string_from_stdin(); String read_string_from_stdin();
int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false); int execute(const String &p_path, const Vector<String> &p_arguments, Array r_output = Array(), bool p_read_stderr = false, bool p_open_console = false);
Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments); Dictionary execute_with_pipe(const String &p_path, const Vector<String> &p_arguments, bool p_blocking = true);
int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false); int create_process(const String &p_path, const Vector<String> &p_arguments, bool p_open_console = false);
int create_instance(const Vector<String> &p_arguments); int create_instance(const Vector<String> &p_arguments);
Error kill(int p_pid); Error kill(int p_pid);

View File

@ -182,7 +182,7 @@ public:
virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); }; virtual Vector<String> get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale = String(), const String &p_script = String(), int p_weight = 400, int p_stretch = 100, bool p_italic = false) const { return Vector<String>(); };
virtual String get_executable_path() const; virtual String get_executable_path() const;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) = 0;
virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) { return Dictionary(); } virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) { return Dictionary(); }
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) = 0;
virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); }; virtual Error create_instance(const List<String> &p_arguments, ProcessID *r_child_id = nullptr) { return create_process(get_executable_path(), p_arguments, r_child_id); };
virtual Error kill(const ProcessID &p_pid) = 0; virtual Error kill(const ProcessID &p_pid) = 0;

View File

@ -132,8 +132,10 @@
<return type="Dictionary" /> <return type="Dictionary" />
<param index="0" name="path" type="String" /> <param index="0" name="path" type="String" />
<param index="1" name="arguments" type="PackedStringArray" /> <param index="1" name="arguments" type="PackedStringArray" />
<param index="2" name="blocking" type="bool" default="true" />
<description> <description>
Creates a new process that runs independently of Godot with redirected IO. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space. Creates a new process that runs independently of Godot with redirected IO. It will not terminate when Godot terminates. The path specified in [param path] must exist and be an executable file or macOS [code].app[/code] bundle. The path is resolved based on the current platform. The [param arguments] are used in the given order and separated by a space.
If [param blocking] is [code]false[/code], created pipes work in non-blocking mode, i.e. read and write operations will return immediately. Use [method FileAccess.get_error] to check if the last read/write operation was successful.
If the process cannot be created, this method returns an empty [Dictionary]. Otherwise, this method returns a [Dictionary] with the following keys: If the process cannot be created, this method returns an empty [Dictionary]. Otherwise, this method returns a [Dictionary] with the following keys:
- [code]"stdio"[/code] - [FileAccess] to access the process stdin and stdout pipes (read/write). - [code]"stdio"[/code] - [FileAccess] to access the process stdin and stdout pipes (read/write).
- [code]"stderr"[/code] - [FileAccess] to access the process stderr pipe (read only). - [code]"stderr"[/code] - [FileAccess] to access the process stderr pipe (read only).

View File

@ -41,7 +41,7 @@
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h> #include <unistd.h>
Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) { Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd, bool p_blocking) {
// Open pipe using handles created by pipe(fd) call in the OS.execute_with_pipe. // Open pipe using handles created by pipe(fd) call in the OS.execute_with_pipe.
_close(); _close();
@ -51,6 +51,11 @@ Error FileAccessUnixPipe::open_existing(int p_rfd, int p_wfd) {
fd[0] = p_rfd; fd[0] = p_rfd;
fd[1] = p_wfd; fd[1] = p_wfd;
if (!p_blocking) {
fcntl(fd[0], F_SETFL, fcntl(fd[0], F_GETFL) | O_NONBLOCK);
fcntl(fd[1], F_SETFL, fcntl(fd[1], F_GETFL) | O_NONBLOCK);
}
last_error = OK; last_error = OK;
return OK; return OK;
} }
@ -74,7 +79,7 @@ Error FileAccessUnixPipe::open_internal(const String &p_path, int p_mode_flags)
ERR_FAIL_COND_V_MSG(!S_ISFIFO(st.st_mode), ERR_ALREADY_IN_USE, "Pipe name is already used by file."); ERR_FAIL_COND_V_MSG(!S_ISFIFO(st.st_mode), ERR_ALREADY_IN_USE, "Pipe name is already used by file.");
} }
int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC); int f = ::open(path.utf8().get_data(), O_RDWR | O_CLOEXEC | O_NONBLOCK);
if (f < 0) { if (f < 0) {
switch (errno) { switch (errno) {
case ENOENT: { case ENOENT: {
@ -129,8 +134,11 @@ uint64_t FileAccessUnixPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) const
ERR_FAIL_COND_V_MSG(fd[0] < 0, -1, "Pipe must be opened before use."); ERR_FAIL_COND_V_MSG(fd[0] < 0, -1, "Pipe must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
uint64_t read = ::read(fd[0], p_dst, p_length); ssize_t read = ::read(fd[0], p_dst, p_length);
if (read == p_length) { if (read == -1) {
last_error = ERR_FILE_CANT_READ;
read = 0;
} else if (read != (ssize_t)p_length) {
last_error = ERR_FILE_CANT_READ; last_error = ERR_FILE_CANT_READ;
} else { } else {
last_error = OK; last_error = OK;

View File

@ -50,7 +50,7 @@ class FileAccessUnixPipe : public FileAccess {
void _close(); void _close();
public: public:
Error open_existing(int p_rfd, int p_wfd); Error open_existing(int p_rfd, int p_wfd, bool p_blocking);
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
virtual bool is_open() const override; ///< true when file is open virtual bool is_open() const override; ///< true when file is open

View File

@ -493,7 +493,7 @@ Dictionary OS_Unix::get_memory_info() const {
return meminfo; return meminfo;
} }
Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
#define CLEAN_PIPES \ #define CLEAN_PIPES \
if (pipe_in[0] >= 0) { \ if (pipe_in[0] >= 0) { \
::close(pipe_in[0]); \ ::close(pipe_in[0]); \
@ -578,11 +578,11 @@ Dictionary OS_Unix::execute_with_pipe(const String &p_path, const List<String> &
Ref<FileAccessUnixPipe> main_pipe; Ref<FileAccessUnixPipe> main_pipe;
main_pipe.instantiate(); main_pipe.instantiate();
main_pipe->open_existing(pipe_out[0], pipe_in[1]); main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking);
Ref<FileAccessUnixPipe> err_pipe; Ref<FileAccessUnixPipe> err_pipe;
err_pipe.instantiate(); err_pipe.instantiate();
err_pipe->open_existing(pipe_err[0], 0); err_pipe->open_existing(pipe_err[0], 0, p_blocking);
ProcessInfo pi; ProcessInfo pi;
process_map_mutex.lock(); process_map_mutex.lock();

View File

@ -83,7 +83,7 @@ public:
virtual Dictionary get_memory_info() const override; virtual Dictionary get_memory_info() const override;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override; virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override; virtual int get_process_id() const override;

View File

@ -35,7 +35,7 @@
#include "core/os/os.h" #include "core/os/os.h"
#include "core/string/print_string.h" #include "core/string/print_string.h"
Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) { Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_blocking) {
// Open pipe using handles created by CreatePipe(rfd, wfd, NULL, 4096) call in the OS.execute_with_pipe. // Open pipe using handles created by CreatePipe(rfd, wfd, NULL, 4096) call in the OS.execute_with_pipe.
_close(); _close();
@ -44,6 +44,12 @@ Error FileAccessWindowsPipe::open_existing(HANDLE p_rfd, HANDLE p_wfd) {
fd[0] = p_rfd; fd[0] = p_rfd;
fd[1] = p_wfd; fd[1] = p_wfd;
if (!p_blocking) {
DWORD mode = PIPE_READMODE_BYTE | PIPE_NOWAIT;
SetNamedPipeHandleState(fd[0], &mode, nullptr, nullptr);
SetNamedPipeHandleState(fd[1], &mode, nullptr, nullptr);
}
last_error = OK; last_error = OK;
return OK; return OK;
} }
@ -58,7 +64,7 @@ Error FileAccessWindowsPipe::open_internal(const String &p_path, int p_mode_flag
HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE h = CreateFileW((LPCWSTR)path.utf16().get_data(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (h == INVALID_HANDLE_VALUE) { if (h == INVALID_HANDLE_VALUE) {
h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, 1, 4096, 4096, 0, nullptr); h = CreateNamedPipeW((LPCWSTR)path.utf16().get_data(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT, 1, 4096, 4096, 0, nullptr);
if (h == INVALID_HANDLE_VALUE) { if (h == INVALID_HANDLE_VALUE) {
last_error = ERR_FILE_CANT_OPEN; last_error = ERR_FILE_CANT_OPEN;
return last_error; return last_error;
@ -100,7 +106,7 @@ uint64_t FileAccessWindowsPipe::get_buffer(uint8_t *p_dst, uint64_t p_length) co
ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use."); ERR_FAIL_COND_V_MSG(fd[0] == 0, -1, "Pipe must be opened before use.");
ERR_FAIL_COND_V(!p_dst && p_length > 0, -1); ERR_FAIL_COND_V(!p_dst && p_length > 0, -1);
DWORD read = -1; DWORD read = 0;
if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) { if (!ReadFile(fd[0], p_dst, p_length, &read, nullptr) || read != p_length) {
last_error = ERR_FILE_CANT_READ; last_error = ERR_FILE_CANT_READ;
} else { } else {

View File

@ -49,7 +49,7 @@ class FileAccessWindowsPipe : public FileAccess {
void _close(); void _close();
public: public:
Error open_existing(HANDLE p_rfd, HANDLE p_wfd); Error open_existing(HANDLE p_rfd, HANDLE p_wfd, bool p_blocking);
virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file virtual Error open_internal(const String &p_path, int p_mode_flags) override; ///< open a file
virtual bool is_open() const override; ///< true when file is open virtual bool is_open() const override; ///< true when file is open

View File

@ -73,3 +73,10 @@ Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_nod
Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_property_selector/arguments': size changed value in new API, from 3 to 4. Validate extension JSON: Error: Field 'classes/EditorInterface/methods/popup_property_selector/arguments': size changed value in new API, from 3 to 4.
Added optional argument to popup_property_selector and popup_node_selector to specify the current value. Added optional argument to popup_property_selector and popup_node_selector to specify the current value.
GH-94434
--------
Validate extension JSON: Error: Field 'classes/OS/methods/execute_with_pipe/arguments': size changed value in new API, from 2 to 3.
Optional argument added. Compatibility method registered.

View File

@ -105,7 +105,7 @@ Error OS_Web::execute(const String &p_path, const List<String> &p_arguments, Str
return create_process(p_path, p_arguments); return create_process(p_path, p_arguments);
} }
Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { Dictionary OS_Web::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform."); ERR_FAIL_V_MSG(Dictionary(), "OS::execute_with_pipe is not available on the Web platform.");
} }

View File

@ -80,7 +80,7 @@ public:
bool main_loop_iterate(); bool main_loop_iterate();
Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
Error kill(const ProcessID &p_pid) override; Error kill(const ProcessID &p_pid) override;
int get_process_id() const override; int get_process_id() const override;

View File

@ -878,7 +878,7 @@ Dictionary OS_Windows::get_memory_info() const {
return meminfo; return meminfo;
} }
Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments) { Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking) {
#define CLEAN_PIPES \ #define CLEAN_PIPES \
if (pipe_in[0] != 0) { \ if (pipe_in[0] != 0) { \
CloseHandle(pipe_in[0]); \ CloseHandle(pipe_in[0]); \
@ -977,11 +977,11 @@ Dictionary OS_Windows::execute_with_pipe(const String &p_path, const List<String
Ref<FileAccessWindowsPipe> main_pipe; Ref<FileAccessWindowsPipe> main_pipe;
main_pipe.instantiate(); main_pipe.instantiate();
main_pipe->open_existing(pipe_out[0], pipe_in[1]); main_pipe->open_existing(pipe_out[0], pipe_in[1], p_blocking);
Ref<FileAccessWindowsPipe> err_pipe; Ref<FileAccessWindowsPipe> err_pipe;
err_pipe.instantiate(); err_pipe.instantiate();
err_pipe->open_existing(pipe_err[0], 0); err_pipe->open_existing(pipe_err[0], 0, p_blocking);
ret["stdio"] = main_pipe; ret["stdio"] = main_pipe;
ret["stderr"] = err_pipe; ret["stderr"] = err_pipe;

View File

@ -188,7 +188,7 @@ public:
virtual Dictionary get_memory_info() const override; virtual Dictionary get_memory_info() const override;
virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override; virtual Error execute(const String &p_path, const List<String> &p_arguments, String *r_pipe = nullptr, int *r_exitcode = nullptr, bool read_stderr = false, Mutex *p_pipe_mutex = nullptr, bool p_open_console = false) override;
virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments) override; virtual Dictionary execute_with_pipe(const String &p_path, const List<String> &p_arguments, bool p_blocking = true) override;
virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override; virtual Error create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id = nullptr, bool p_open_console = false) override;
virtual Error kill(const ProcessID &p_pid) override; virtual Error kill(const ProcessID &p_pid) override;
virtual int get_process_id() const override; virtual int get_process_id() const override;