diff --git a/src/internal/event_loop/event_loop.wasm.mbt b/src/internal/event_loop/event_loop.wasm.mbt index 2f86de13..a4b0367e 100644 --- a/src/internal/event_loop/event_loop.wasm.mbt +++ b/src/internal/event_loop/event_loop.wasm.mbt @@ -54,3 +54,15 @@ fn PlatformEventLoopExtra::poll(self : Self, evloop : EventLoop) -> Unit raise { Windows(extra) => extra.poll(evloop) } } + +///| +async fn PlatformEventLoopExtra::wait_pid( + self : Self, + evloop : EventLoop, + pid : Int, +) -> Unit { + match self { + Unix(extra) => extra.wait_pid(evloop, pid) + Windows(_) => panic() + } +} diff --git a/src/internal/event_loop/event_loop_unix.mbt b/src/internal/event_loop/event_loop_unix.mbt index 95571f5a..3d06e1e7 100644 --- a/src/internal/event_loop/event_loop_unix.mbt +++ b/src/internal/event_loop/event_loop_unix.mbt @@ -90,3 +90,16 @@ fn UnixEventLoopExtra::poll(self : Self, evloop : EventLoop) -> Unit raise { } } } + +///| +#cfg(not(platform="windows")) +async fn UnixEventLoopExtra::wait_pid( + self : Self, + evloop : EventLoop, + pid : Int, +) -> Unit { + guard self.pids.get(pid) is None + self.pids[pid] = @coroutine.current_coroutine() + defer self.pids.remove(pid) + evloop.suspend() +} diff --git a/src/internal/event_loop/moon.pkg b/src/internal/event_loop/moon.pkg index 35a66313..83c3d812 100644 --- a/src/internal/event_loop/moon.pkg +++ b/src/internal/event_loop/moon.pkg @@ -57,10 +57,9 @@ options( "network_windows.mbt": [ "native", "wasm" ], "event_bus.mbt": [ "native" ], "process.mbt": [ "native", "wasm" ], - "process.wasm.mbt": [ "wasm" ], + "process_unix.mbt": [ "native", "wasm" ], + "process_windows.mbt": [ "native", "wasm" ], "event_bus.wasm.mbt": [ "wasm" ], - "process_unix.mbt": [ "native" ], - "process_windows.mbt": [ "native" ], "signal.mbt": [ "native", "wasm" ], "stdio.mbt": [ "native", "wasm" ], "thread_pool.mbt": [ "native" ], diff --git a/src/internal/event_loop/pkg.generated.mbti b/src/internal/event_loop/pkg.generated.mbti index 0780dfb1..6bff36dd 100644 --- a/src/internal/event_loop/pkg.generated.mbti +++ b/src/internal/event_loop/pkg.generated.mbti @@ -53,7 +53,7 @@ pub fn set_global_cancellation_signals(ReadOnlyArray[Int]) -> Unit pub async fn sleep(Int) -> Unit -pub async fn spawn(@os_string.OsString, @c_buffer.Buffer, env~ : @c_buffer.Buffer, inherited_env_entry_count~ : Int, stdin~ : Int, stdout~ : Int, stderr~ : Int, cwd~ : @os_string.OsString?, context~ : String) -> Process +pub async fn spawn_unix(@os_string.OsString, @c_buffer.Buffer, env~ : @c_buffer.Buffer, inherited_env_entry_count~ : Int, stdin~ : Int, stdout~ : Int, stderr~ : Int, cwd~ : @os_string.OsString?, context~ : String) -> Process pub let stderr : Result[IoHandle, Error] @@ -123,6 +123,7 @@ pub struct Process { pub fn Process::close(Self) -> Unit pub fn Process::from_pid(Int, context~ : String) -> Self raise pub async fn Process::wait_pid(Self, context~ : String) -> Int +pub async fn Process::wait_pid_unix(Self, context~ : String) -> Int type Timer pub fn Timer::cancel(Self) -> Unit diff --git a/src/internal/event_loop/process.mbt b/src/internal/event_loop/process.mbt index 9b425780..88bf4fde 100644 --- a/src/internal/event_loop/process.mbt +++ b/src/internal/event_loop/process.mbt @@ -67,3 +67,40 @@ extern "C" fn get_process_result( #unsafe_skip_stub_check #borrow(out) fn get_process_result(handle : @fd_util.Fd, pid : Int, out : Ref[Int]) -> Int = "moonbitlang/async" "process/get_process_result" + +///| +#cfg(all(target="native", not(platform="windows"))) +type SpawnJobArgv = @c_buffer.Buffer + +///| +#cfg(target="wasm") +type SpawnJobArgv = UInt64 + +///| +#cfg(target="native") +type SpawnJobEnv = @c_buffer.Buffer + +///| +#cfg(target="wasm") +type SpawnJobEnv = UInt64 + +///| +#cfg(platform="windows") +pub async fn Process::wait_pid(self : Process, context~ : String) -> Int { + self.wait_pid_windows(context~) +} + +///| +#cfg(all(target="native", not(platform="windows"))) +pub async fn Process::wait_pid(self : Process, context~ : String) -> Int { + self.wait_pid_unix(context~) +} + +///| +#cfg(target="wasm") +pub async fn Process::wait_pid(self : Process, context~ : String) -> Int { + match platform { + Linux | MacOS => self.wait_pid_unix(context~) + Windows => self.wait_pid_windows(context~) + } +} diff --git a/src/internal/event_loop/process.wasm.mbt b/src/internal/event_loop/process.wasm.mbt deleted file mode 100644 index a546f368..00000000 --- a/src/internal/event_loop/process.wasm.mbt +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2025 International Digital Economy Academy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -///| -pub async fn spawn_unix( - path : @os_string.OsString, - args : UInt64, - env~ : UInt64, - inherited_env_entry_count~ : Int, - stdin~ : @fd_util.Fd, - stdout~ : @fd_util.Fd, - stderr~ : @fd_util.Fd, - cwd~ : @os_string.OsString?, - context~ : String, -) -> Process { - let (cwd, has_cwd) = match cwd { - None => (@os_string.encode(""), false) - Some(cwd) => (cwd, true) - } - let job = Job::spawn_unix( - path, - args, - env~, - inherited_env_entry_count~, - stdin~, - stdout~, - stderr~, - cwd~, - has_cwd~, - ) - defer job.0.free() - let pid = perform_job_in_worker(job.0, context~) - let pidfd = job.result_handle() - let handle = if !@fd_util.fd_is_valid(pidfd) { - None - } else { - Some( - IoHandle::from_fd( - pidfd, - kind=Unknown, - is_async=platform is Linux, - read_only=true, - ), - ) - } - { pid, handle } -} - -///| -pub async fn spawn_windows( - command_line : @os_string.OsString, - env~ : UInt64, - stdin~ : @fd_util.Fd, - stdout~ : @fd_util.Fd, - stderr~ : @fd_util.Fd, - cwd~ : @os_string.OsString?, - no_console_window~ : Bool, - is_orphan~ : Bool, - context~ : String, -) -> Process { - let (cwd, has_cwd) = match cwd { - None => (@os_string.encode(""), false) - Some(cwd) => (cwd, true) - } - let job = Job::spawn_windows( - command_line, - env~, - stdin~, - stdout~, - stderr~, - cwd~, - has_cwd~, - no_console_window~, - is_orphan~, - ) - defer job.0.free() - let pid = perform_job_in_worker(job.0, context~) - let handle = IoHandle::from_fd( - job.result_handle(), - kind=Unknown, - is_async=false, - read_only=true, - ) - { pid, handle: Some(handle) } -} - -///| -pub async fn Process::wait_pid(self : Process, context~ : String) -> Int { - guard curr_loop.val is Some(evloop) - let handle = if self.handle is Some(io) { io.fd } else { @fd_util.invalid_fd } - - let out = Ref(0) - - match platform { - Linux => - match self.handle { - Some(io) => { - let ret = get_process_result(handle, self.pid, out) - if ret >= 0 { - return out.val - } - if !@os_error.is_nonblocking_io_error() { - @os_error.check_errno(context) - } - io.wait_read() - } - None => { - // Linux systems where pidfd is not available. - // Fall back to blocking waitpid in worker thread. - // The thread pool job will obtain the exit code and reap the process. - let job = Job::wait_for_process(@fd_util.invalid_fd, pid=self.pid) - defer job.free() - return perform_job_in_worker(job, context~, cancel=job_id => { - evloop.cancel_job_in_worker(job_id, context~) - }) - } - } - MacOS => - if evloop.bus.register_pid(self.pid) { - guard evloop.extra is Unix(extra) - guard extra.pids.get(self.pid) is None - extra.pids[self.pid] = @coroutine.current_coroutine() - defer extra.pids.remove(self.pid) - evloop.suspend() - } - Windows => { - guard self.handle is Some(io) - let job = Job::wait_for_process(io.fd, pid=self.pid) - defer job.free() - let _ = perform_job_in_worker(job, context~, cancel=job_id => { - evloop.cancel_job_in_worker(job_id, context~) - }) - } - } - - let ret = get_process_result(handle, self.pid, out) - if ret < 0 { - if platform is MacOS { - while @os_error.is_nonblocking_io_error() { - sleep(10) - let ret = get_process_result(handle, self.pid, out) - if ret >= 0 { - return out.val - } - } - } - @os_error.check_errno(context) - } - out.val -} diff --git a/src/internal/event_loop/process_unix.mbt b/src/internal/event_loop/process_unix.mbt index 2fbb5b03..b5fb5663 100644 --- a/src/internal/event_loop/process_unix.mbt +++ b/src/internal/event_loop/process_unix.mbt @@ -14,10 +14,10 @@ ///| #cfg(not(platform="windows")) -pub async fn spawn( +pub async fn spawn_unix( path : @os_string.OsString, - args : @c_buffer.Buffer, - env~ : @c_buffer.Buffer, + args : SpawnJobArgv, + env~ : SpawnJobEnv, inherited_env_entry_count~ : Int, stdin~ : @fd_util.Fd, stdout~ : @fd_util.Fd, @@ -25,8 +25,20 @@ pub async fn spawn( cwd~ : @os_string.OsString?, context~ : String, ) -> Process { - let job = Job::spawn( - path, args, env, inherited_env_entry_count, stdin, stdout, stderr, cwd, + let (cwd, has_cwd) = match cwd { + None => (@os_string.empty, false) + Some(cwd) => (cwd, true) + } + let job = Job::spawn_unix( + path, + args, + env~, + inherited_env_entry_count~, + stdin~, + stdout~, + stderr~, + cwd~, + has_cwd~, ) defer job.0.free() let pid = perform_job_in_worker(job.0, context~) @@ -41,7 +53,7 @@ pub async fn spawn( ///| #cfg(not(platform="windows")) -pub async fn Process::wait_pid(self : Process, context~ : String) -> Int { +pub async fn Process::wait_pid_unix(self : Process, context~ : String) -> Int { guard curr_loop.val is Some(evloop) let handle = if self.handle is Some(io) { io.fd } else { @fd_util.invalid_fd } @@ -59,16 +71,13 @@ pub async fn Process::wait_pid(self : Process, context~ : String) -> Int { } else if platform is MacOS { // MacOS/BSD, use `kqueue` with `EVFILT_PROC` if evloop.bus.register_pid(self.pid) { - guard evloop.extra.pids.get(self.pid) is None - evloop.extra.pids[self.pid] = @coroutine.current_coroutine() - defer evloop.extra.pids.remove(self.pid) - evloop.suspend() + evloop.extra.wait_pid(evloop, self.pid) } } else { // Linux systems where pidfd is not available. // Fall back to blocking waitpid in worker thread. // The thread pool job will obain the exit code and reap the process. - let job = Job::wait_for_process(self.pid) + let job = Job::wait_for_process(handle, pid=self.pid) defer job.free() return perform_job_in_worker(job, context~, cancel=job_id => { evloop.cancel_job_in_worker(job_id, context~) diff --git a/src/internal/event_loop/process_windows.mbt b/src/internal/event_loop/process_windows.mbt index ea722010..d1fc86f6 100644 --- a/src/internal/event_loop/process_windows.mbt +++ b/src/internal/event_loop/process_windows.mbt @@ -13,28 +13,10 @@ // limitations under the License. ///| -#cfg(platform="windows") -extern "C" fn init_global_job_object_ffi() -> Bool = "moonbitlang_async_init_global_job_objeect" - -///| -#cfg(platform="windows") -let global_job_object_initialized : Ref[Bool] = Ref(false) - -///| -#cfg(platform="windows") -fn init_global_job_object() -> Unit raise { - guard global_job_object_initialized.val is false else { } - global_job_object_initialized.val = true - if !init_global_job_object_ffi() { - @os_error.check_errno("create global job object") - } -} - -///| -#cfg(platform="windows") -pub async fn spawn( +#cfg(any(platform="windows", target="wasm")) +pub async fn spawn_windows( command_line : @os_string.OsString, - env~ : @c_buffer.Buffer, + env~ : SpawnJobEnv, stdin~ : @fd_util.Fd, stdout~ : @fd_util.Fd, stderr~ : @fd_util.Fd, @@ -43,14 +25,18 @@ pub async fn spawn( is_orphan~ : Bool, context~ : String, ) -> Process { - init_global_job_object() - let job = Job::spawn( + let (cwd, has_cwd) = match cwd { + None => (@os_string.empty, false) + Some(cwd) => (cwd, true) + } + let job = Job::spawn_windows( command_line, - env, - stdin, - stdout, - stderr, - cwd, + env~, + stdin~, + stdout~, + stderr~, + cwd~, + has_cwd~, no_console_window~, is_orphan~, ) @@ -65,14 +51,17 @@ pub async fn spawn( } ///| -#cfg(platform="windows") -pub async fn Process::wait_pid(self : Process, context~ : String) -> Int { +#cfg(any(platform="windows", target="wasm")) +pub async fn Process::wait_pid_windows( + self : Process, + context~ : String, +) -> Int { + guard curr_loop.val is Some(evloop) guard self.handle is Some(io) - let job = Job::wait_for_process(io.fd) + let job = Job::wait_for_process(io.fd, pid=self.pid) defer job.free() - let _ = perform_job_in_worker(job, context~, cancel=_ => { - job.cancel_process_waiter() - NeedWait + let _ = perform_job_in_worker(job, context~, cancel=job_id => { + evloop.cancel_job_in_worker(job_id, context~) }) let out = Ref(0) diff --git a/src/internal/event_loop/thread_pool.c b/src/internal/event_loop/thread_pool.c index 07627081..26385b4e 100644 --- a/src/internal/event_loop/thread_pool.c +++ b/src/internal/event_loop/thread_pool.c @@ -136,6 +136,10 @@ struct job { // finalizer for the job void (*free)(void *); + + // Special cancellation function for the job. + // May be `NULL`, in this case the default one is used. + int32_t (*cancel_handler)(struct job*); }; MOONBIT_FFI_EXPORT @@ -299,17 +303,29 @@ void moonbitlang_async_worker_enter_idle(struct worker *worker) { worker->job = 0; } +enum { + CANCELLATION_STATUS_RETRY_LATER = 0, + CANCELLATION_STATUS_NEED_WAIT = 1, +}; + MOONBIT_FFI_EXPORT int32_t moonbitlang_async_cancel_worker(struct worker *worker) { if (worker->waiting) return 1; + // invarint: `worker->job` is only manipulated in the main thread, + // and must be non-NULL here. + if (worker->job->cancel_handler) + return (worker->job->cancel_handler)(worker->job); + + // enter default cancellation logic + #ifdef _WIN32 if (CancelSynchronousIo(worker->id)) { - return 1; + return CANCELLATION_STATUS_NEED_WAIT; } else if (GetLastError() == ERROR_NOT_FOUND) { - return 0; + return CANCELLATION_STATUS_RETRY_LATER; } else { return -1; } @@ -317,7 +333,7 @@ int32_t moonbitlang_async_cancel_worker(struct worker *worker) { #else pthread_kill(worker->id, SIGUSR2); - return 0; + return CANCELLATION_STATUS_RETRY_LATER; #endif } @@ -518,6 +534,7 @@ struct job *make_job( job->err = 0; job->worker = worker; job->free = free; + job->cancel_handler = 0; return job; } @@ -2014,7 +2031,8 @@ char *moonbitlang_async_get_realpath_result(struct realpath_job *job) { static HANDLE global_job_object = INVALID_HANDLE_VALUE; -int32_t moonbitlang_async_init_global_job_objeect() { +static +BOOL init_global_job_object() { HANDLE job = CreateJobObjectA(NULL, NULL); if (job == NULL) return 0; @@ -2096,6 +2114,11 @@ void spawn_job_worker(struct job *job) { STD_ERROR_HANDLE }; + if (job->err) + // Handle error from initializing global job object, + // see `moonbitlang_async_make_spawn_job` below. + return; + struct spawn_job *spawn_job = (struct spawn_job *)job; HANDLE handles_to_inherit[3]; @@ -2270,6 +2293,7 @@ struct spawn_job *moonbitlang_async_make_spawn_job( HANDLE stdout_handle, HANDLE stderr_handle, LPWSTR cwd, + int32_t has_cwd, int32_t no_console_window, int32_t is_orphan ) { @@ -2279,10 +2303,17 @@ struct spawn_job *moonbitlang_async_make_spawn_job( job->stdio[0] = stdin_handle; job->stdio[1] = stdout_handle; job->stdio[2] = stderr_handle; - job->cwd = cwd; + job->cwd = has_cwd ? cwd : 0; job->no_console_window = no_console_window; job->is_orphan = is_orphan; job->result = INVALID_HANDLE_VALUE; + + if (global_job_object == INVALID_HANDLE_VALUE && !init_global_job_object()) + // To avoid race condition, we must initialize the global job object + // in the main thread. However job spawning functions cannot report error, + // so we delay the error reporting to the thread pool job + job->job.err = errno; + return job; } @@ -2308,6 +2339,13 @@ void free_wait_for_process_job(void *obj) { CloseHandle(job->cancel); }; +static +int32_t cancel_wait_for_process_job(struct job *obj) { + struct wait_for_process_job *job = (struct wait_for_process_job*)obj; + SetEvent(job->cancel); + return CANCELLATION_STATUS_NEED_WAIT; +} + static void wait_for_process_job_worker(struct job *job) { struct wait_for_process_job *wait_for_process_job = (struct wait_for_process_job*)job; @@ -2322,18 +2360,16 @@ void wait_for_process_job_worker(struct job *job) { } struct wait_for_process_job *moonbitlang_async_make_wait_for_process_job( - HANDLE process + HANDLE process, + int32_t pid ) { struct wait_for_process_job *job = MAKE_JOB(wait_for_process); job->process = process; job->cancel = CreateEventA(NULL, FALSE, FALSE, NULL); + job->job.cancel_handler = cancel_wait_for_process_job; return job; } -void moonbitlang_async_cancel_wait_for_process_job(struct wait_for_process_job *job) { - SetEvent(job->cancel); -} - #else struct spawn_job { @@ -2456,7 +2492,8 @@ struct spawn_job *moonbitlang_async_make_spawn_job( int stdin_fd, int stdout_fd, int stderr_fd, - char *cwd + char *cwd, + int32_t has_cwd ) { struct spawn_job *job = MAKE_JOB(spawn); job->path = path; @@ -2466,7 +2503,7 @@ struct spawn_job *moonbitlang_async_make_spawn_job( job->stdio[0] = stdin_fd; job->stdio[1] = stdout_fd; job->stdio[2] = stderr_fd; - job->cwd = cwd; + job->cwd = has_cwd ? cwd : 0; job->pidfd = -1; return job; } @@ -2495,7 +2532,8 @@ void wait_for_process_job_worker(struct job *job) { } struct wait_for_process_job *moonbitlang_async_make_wait_for_process_job( - int pid + HANDLE handle, + int32_t pid ) { struct wait_for_process_job *job = MAKE_JOB(wait_for_process); job->pid = pid; diff --git a/src/internal/event_loop/thread_pool.mbt b/src/internal/event_loop/thread_pool.mbt index 3b4dc860..03a9a04d 100644 --- a/src/internal/event_loop/thread_pool.mbt +++ b/src/internal/event_loop/thread_pool.mbt @@ -231,27 +231,29 @@ priv struct SpawnJob(Job) ///| #cfg(not(platform="windows")) #owned(path, args, env, cwd) -extern "C" fn Job::spawn( +extern "C" fn Job::spawn_unix( path : @os_string.OsString, args : @c_buffer.Buffer, - env : @c_buffer.Buffer, - inherited_env_entry_count : Int, - stdin : @fd_util.Fd, - stdout : @fd_util.Fd, - stderr : @fd_util.Fd, - cwd : @os_string.OsString?, + env~ : @c_buffer.Buffer, + inherited_env_entry_count~ : Int, + stdin~ : @fd_util.Fd, + stdout~ : @fd_util.Fd, + stderr~ : @fd_util.Fd, + cwd~ : @os_string.OsString, + has_cwd~ : Bool, ) -> SpawnJob = "moonbitlang_async_make_spawn_job" ///| #cfg(platform="windows") #owned(command_line, env, cwd) -extern "C" fn Job::spawn( +extern "C" fn Job::spawn_windows( command_line : @os_string.OsString, - env : @c_buffer.Buffer, - stdin : @fd_util.Fd, - stdout : @fd_util.Fd, - stderr : @fd_util.Fd, - cwd : @os_string.OsString?, + env~ : @c_buffer.Buffer, + stdin~ : @fd_util.Fd, + stdout~ : @fd_util.Fd, + stderr~ : @fd_util.Fd, + cwd~ : @os_string.OsString, + has_cwd~ : Bool, no_console_window~ : Bool, is_orphan~ : Bool, ) -> SpawnJob = "moonbitlang_async_make_spawn_job" @@ -260,16 +262,8 @@ extern "C" fn Job::spawn( extern "C" fn SpawnJob::result_handle(job : SpawnJob) -> @fd_util.Fd = "moonbitlang_async_get_spawn_job_result_handle" ///| -#cfg(not(platform="windows")) -extern "C" fn Job::wait_for_process(pid : Int) -> Job = "moonbitlang_async_make_wait_for_process_job" - -///| -#cfg(platform="windows") -extern "C" fn Job::wait_for_process(handle : @fd_util.Fd) -> Job = "moonbitlang_async_make_wait_for_process_job" - -///| -#cfg(platform="windows") -extern "C" fn Job::cancel_process_waiter(job : Job) = "moonbitlang_async_cancel_wait_for_process_job" +// `handle` is used by Windows, `pid` is used by Linux +extern "C" fn Job::wait_for_process(handle : @fd_util.Fd, pid~ : Int) -> Job = "moonbitlang_async_make_wait_for_process_job" ///| #owned(addr) diff --git a/src/internal/os_string/os_string.mbt b/src/internal/os_string/os_string.mbt index 18b4a457..06d5a19f 100644 --- a/src/internal/os_string/os_string.mbt +++ b/src/internal/os_string/os_string.mbt @@ -84,6 +84,9 @@ pub extern "C" fn decode( len? : Int = -1, ) -> String = "moonbitlang_async_c_buffer_as_string" +///| +pub let empty : OsString = encode("") + ///| #cfg(platform="windows") let _mute_unused_warning : Unit = ignore(@utf8.encode("")) diff --git a/src/internal/os_string/os_string.wasm.mbt b/src/internal/os_string/os_string.wasm.mbt index faa1e638..9b24983c 100644 --- a/src/internal/os_string/os_string.wasm.mbt +++ b/src/internal/os_string/os_string.wasm.mbt @@ -60,3 +60,6 @@ pub fn decode( fill_string(os_str, offset, len, str, str_len) str } + +///| +pub let empty : OsString = encode("") diff --git a/src/internal/os_string/pkg.generated.mbti b/src/internal/os_string/pkg.generated.mbti index 4ff9b400..09beb0f0 100644 --- a/src/internal/os_string/pkg.generated.mbti +++ b/src/internal/os_string/pkg.generated.mbti @@ -8,6 +8,8 @@ import { // Values pub fn decode(@c_buffer.Buffer, offset? : Int, len? : Int) -> String +pub let empty : OsString + pub fn encode(StringView) -> OsString // Errors diff --git a/src/process/moon.pkg b/src/process/moon.pkg index 2f92ce54..ba681ec8 100644 --- a/src/process/moon.pkg +++ b/src/process/moon.pkg @@ -25,21 +25,20 @@ options( "max-concurrent-tests": 5, "native-stub": [ "unix.c", "windows.c" ], targets: { - "basic_test.mbt": [ "native" ], - "cancel_test.mbt": [ "native" ], + "basic_test.mbt": [ "native", "wasm" ], + "cancel_test.mbt": [ "native", "wasm" ], "cancellation.mbt": [ "native", "wasm" ], - "commands_for_test.mbt": [ "native" ], - "cwd_test.mbt": [ "native" ], - "env_test.mbt": [ "native" ], - "helper_test.mbt": [ "native" ], + "commands_for_test.mbt": [ "native", "wasm" ], + "cwd_test.mbt": [ "native", "wasm" ], + "env_test.mbt": [ "native", "wasm" ], + "helper_test.mbt": [ "native", "wasm" ], "process.mbt": [ "native", "wasm" ], "redirect.mbt": [ "native", "wasm" ], - "redirect_test.mbt": [ "native" ], - "spawn_in_group_test.mbt": [ "native" ], + "redirect_test.mbt": [ "native", "wasm" ], + "spawn_in_group_test.mbt": [ "native", "wasm" ], "unix.mbt": [ "native" ], "wasm.mbt": [ "wasm" ], - "wait_test.mbt": [ "native" ], - "wasm_test.mbt": [ "wasm" ], + "wait_test.mbt": [ "native", "wasm" ], "windows.mbt": [ "native" ], }, ) diff --git a/src/process/unix.mbt b/src/process/unix.mbt index cd3f4adf..da23d92d 100644 --- a/src/process/unix.mbt +++ b/src/process/unix.mbt @@ -143,7 +143,7 @@ async fn raw_spawn( // so `is_orphan` makes no difference on actual spawning ignore(is_orphan) ignore(no_console_window) - @event_loop.spawn( + @event_loop.spawn_unix( cmd, argv.0, env=env.0, diff --git a/src/process/wasm_test.mbt b/src/process/wasm_test.mbt deleted file mode 100644 index 3ea43359..00000000 --- a/src/process/wasm_test.mbt +++ /dev/null @@ -1,97 +0,0 @@ -///| -// Copyright 2025 International Digital Economy Academy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -///| -fn shell(command : String) -> (String, Array[String]) { - if @event_loop.platform is Windows { - ("powershell.exe", ["-NoProfile", "-Command", command]) - } else { - ("/bin/sh", ["-c", command]) - } -} - -///| -fn write_stdout_command(text : String) -> String { - if @event_loop.platform is Windows { - "[Console]::Out.Write('\{text}')" - } else { - "printf '\{text}'" - } -} - -///| -fn write_env_command(name : String) -> String { - if @event_loop.platform is Windows { - "[Console]::Out.Write($env:\{name})" - } else { - "printf \"$\{name}\"" - } -} - -///| -fn list_directory_command() -> String { - if @event_loop.platform is Windows { - "Get-ChildItem -Name" - } else { - "ls" - } -} - -///| -async test "wasm collect stdout" { - let (cmd, args) = shell(write_stdout_command("moonbit-process")) - let (code, output) = @process.collect_stdout(cmd, args) - inspect(code, content="0") - inspect(output.text(), content="moonbit-process") -} - -///| -async test "wasm pass env" { - let (cmd, args) = shell(write_env_command("MOONBIT_PROCESS_TEST")) - let (code, output) = @process.collect_stdout(cmd, args, extra_env={ - "MOONBIT_PROCESS_TEST": "env-ok", - }) - inspect(code, content="0") - inspect(output.text(), content="env-ok") -} - -///| -async test "wasm set cwd" { - let (cmd, args) = shell(list_directory_command()) - let (code, output) = @process.collect_stdout( - cmd, - args, - cwd="test_directory", - extra_env={ "LC_ALL": "C" }, - ) - inspect(code, content="0") - let files = output - .text() - .trim(char_set="\n\r ") - .replace_all(old="\r\n", new="\n") - .split("\n") - .to_array() - files.sort() - json_inspect(files, content=["file", "inner1"]) -} - -///| -async test "wasm reject nul argument" { - try @process.run("bad\u{0}cmd", []) catch { - _ => inspect(true, content="true") - } noraise { - _ => fail("expected process argument validation to fail") - } -} diff --git a/src/process/windows.mbt b/src/process/windows.mbt index 9f2e71f6..18c31aae 100644 --- a/src/process/windows.mbt +++ b/src/process/windows.mbt @@ -182,7 +182,7 @@ async fn raw_spawn( env.add_entry(offset, key=k, key_len~, value=v, value_len~) continue offset + key_len + value_len + 2 } - @event_loop.spawn( + @event_loop.spawn_windows( command_line, env=env.0, stdin~,