os: remove process_close and add process_terminate

`process_wait` (optionally prefaced with a `process_kill`) can be used
to properly close and free resources of the process.

`process_terminate` was added because `process_kill` is a forceful
exit, we were missing a way to request the process to terminate.
This commit is contained in:
Laytan Laats
2026-02-18 20:03:52 +01:00
parent 06a56c5edd
commit e87e3fba1b
10 changed files with 109 additions and 54 deletions

View File

@@ -311,7 +311,8 @@ This procedure obtains a process handle of a process specified by `pid`.
This procedure can be subject to race conditions. See the description of
`Process`.
Use `process_close()` function to close the process handle.
Use the `process_wait()` procedure (optionally prefaced with a `process_kill()`)
to close and free the process handle.
*/
@(require_results)
process_open :: proc(pid: int, flags := Process_Open_Flags {}) -> (Process, Error) {
@@ -360,10 +361,8 @@ be created. It contains information such as the command line, the
environment of the process, the starting directory and many other options.
Most of the fields in the struct can be set to `nil` or an empty value.
Use `process_close` to close the handle to the process. Note, that this
is not the same as terminating the process. One can terminate the process
and not close the handle, in which case the handle would be leaked. In case
the function returns an error, an invalid handle is returned.
Use the `process_wait()` procedure (optionally prefaced with a `process_kill()`)
to close and free the process handle.
This procedure is not thread-safe. It may alter the inheritance properties
of file handles in an unpredictable manner. In case multiple threads change
@@ -495,7 +494,7 @@ Process_State :: struct {
// Will also store the number of the exception or signal that has crashed the
// process.
exit_code: int,
// Specifies whether the termination of the process was successfull or not,
// Specifies whether the termination of the process was successful or not,
// i.e. whether it has crashed or not.
// **Note(windows)**: On windows `true` is always returned, as there is no
// reliable way to obtain information about whether the process has crashed.
@@ -511,7 +510,9 @@ Wait for a process event.
This procedure blocks the execution until the process has exited or the
timeout (if specified) has reached zero. If the timeout is `TIMEOUT_INFINITE`,
no timeout restriction is imposed and the procedure can block indefinately.
no timeout restriction is imposed and the procedure can block indefinitely.
If the timeout is 0, no blocking will be done and the current state is returned.
If the timeout has expired, the `General_Error.Timeout` is returned as
the error.
@@ -525,24 +526,25 @@ process_wait :: proc(process: Process, timeout := TIMEOUT_INFINITE) -> (Process_
}
/*
Close the handle to a process.
Kill a process.
This procedure closes the handle associated with a process. It **does not**
terminate a process, in case it was running. In case a termination is
desired, kill the process first, wait for the process to finish,
then close the handle.
This procedure kills a process, specified by it's handle, `process`.
The process is forced to exit and can't ignore the request.
*/
@(require_results)
process_close :: proc(process: Process) -> (Error) {
return _process_close(process)
process_kill :: proc(process: Process) -> (Error) {
return _process_kill(process)
}
/*
Terminate a process.
This procedure terminates a process, specified by it's handle, `process`.
The process is requested to exit and can ignore the request.
*/
@(require_results)
process_kill :: proc(process: Process) -> (Error) {
return _process_kill(process)
process_terminate :: proc(process: Process) -> (Error) {
return _process_terminate(process)
}

View File

@@ -2,19 +2,17 @@
#+build freebsd
package os
import "core:c"
foreign import libc "system:c"
foreign import dl "system:dl"
foreign libc {
@(link_name="sysctlbyname")
_sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
_sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> i32 ---
}
foreign dl {
@(link_name="pthread_getthreadid_np")
pthread_getthreadid_np :: proc() -> c.int ---
pthread_getthreadid_np :: proc() -> i32 ---
}
@(require_results)
@@ -33,4 +31,4 @@ _get_processor_core_count :: proc() -> int {
}
return 1
}
}

View File

@@ -66,11 +66,11 @@ _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_stat
return
}
_process_close :: proc(process: Process) -> Error {
_process_kill :: proc(process: Process) -> (err: Error) {
return .Unsupported
}
_process_kill :: proc(process: Process) -> (err: Error) {
_process_terminate :: proc(process: Process) -> (err: Error) {
return .Unsupported
}

View File

@@ -5,7 +5,6 @@ package os
import "base:runtime"
import "base:intrinsics"
import "core:c"
import "core:time"
import "core:slice"
import "core:strings"
@@ -13,12 +12,6 @@ import "core:strconv"
import "core:sys/unix"
import "core:sys/linux"
foreign import libc "system:c"
foreign libc {
@(link_name="get_nprocs") _unix_get_nprocs :: proc() -> c.int ---
}
PIDFD_UNASSIGNED :: ~uintptr(0)
@(private="package")
@@ -682,6 +675,8 @@ _reap_terminated :: proc(process: Process) -> (state: Process_State, err: Error)
state.exit_code = int(info.status)
state.success = false
}
_process_close(process)
return
}
@@ -723,6 +718,8 @@ _timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (proc
start_tick = time.tick_now()
continue
}
_process_close(process)
return process_state, _get_platform_error(errno)
}
@@ -733,6 +730,7 @@ _timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (proc
}
if errno = linux.waitid(.PIDFD, linux.Id(process.handle), &info, {.WEXITED, .WNOHANG, .WNOWAIT}, nil); errno != .NONE {
_process_close(process)
return process_state, _get_platform_error(errno)
}
@@ -767,6 +765,8 @@ _timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (proc
process_state.success = false
}
}
_process_close(process)
return
}
@@ -783,6 +783,7 @@ _timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process
org_sigset: linux.Sig_Set
errno := linux.rt_sigprocmask(.SIG_BLOCK, &sigchld_set, &org_sigset)
if errno != .NONE {
_process_close(process)
return process_state, _get_platform_error(errno)
}
defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil)
@@ -814,6 +815,7 @@ _timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process
timeout -= time.tick_since(start_tick)
start_tick = time.tick_now()
case .EINVAL:
_process_close(process)
return process_state, _get_platform_error(errno)
}
}
@@ -852,13 +854,13 @@ _process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_Stat
return process_state, .Timeout
}
if errno != .NONE {
_process_close(process)
return process_state, _get_platform_error(errno)
}
return _reap_terminated(process)
}
@(private="package")
_process_close :: proc(process: Process) -> Error {
if process.handle == 0 || process.handle == PIDFD_UNASSIGNED {
return nil
@@ -872,3 +874,7 @@ _process_kill :: proc(process: Process) -> Error {
return _get_platform_error(linux.kill(linux.Pid(process.pid), .SIGKILL))
}
@(private="package")
_process_terminate :: proc(process: Process) -> Error {
return _get_platform_error(linux.kill(linux.Pid(process.pid), .SIGTERM))
}

View File

@@ -2,7 +2,6 @@
#+build netbsd
package os
import "core:c"
foreign import libc "system:c"
@(private)
@@ -10,7 +9,7 @@ foreign libc {
_lwp_self :: proc() -> i32 ---
@(link_name="sysctlbyname")
_sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
_sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> i32 ---
}
@(require_results)
@@ -28,4 +27,4 @@ _get_processor_core_count :: proc() -> int {
}
return 1
}
}

View File

@@ -22,4 +22,4 @@ _SC_NPROCESSORS_ONLN :: 503
@(private, require_results)
_get_processor_core_count :: proc() -> int {
return int(_sysconf(_SC_NPROCESSORS_ONLN))
}
}

View File

@@ -329,10 +329,6 @@ _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_stat
return
}
_process_close :: proc(process: Process) -> Error {
return nil
}
_process_kill :: proc(process: Process) -> (err: Error) {
_process_handle_still_valid(process) or_return
@@ -342,3 +338,13 @@ _process_kill :: proc(process: Process) -> (err: Error) {
return
}
_process_terminate :: proc(process: Process) -> (err: Error) {
_process_handle_still_valid(process) or_return
if posix.kill(posix.pid_t(process.pid), .SIGTERM) != .OK {
err = _get_platform_error()
}
return
}

View File

@@ -2,19 +2,16 @@
package os
import "base:runtime"
import "base:intrinsics"
import "core:bytes"
import "core:c"
import "core:sys/darwin"
import "core:sys/posix"
import "core:sys/unix"
import "core:time"
foreign import libc "system:System"
foreign import pthread "system:System"
foreign import libsystem "system:System"
foreign libc {
foreign libsystem {
sysctl :: proc "c" (
name: [^]i32, namelen: u32,
oldp: rawptr, oldlenp: ^uint,
@@ -22,23 +19,24 @@ foreign libc {
) -> posix.result ---
@(link_name="sysctlbyname")
_sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> c.int ---
_sysctlbyname :: proc(path: cstring, oldp: rawptr, oldlenp: rawptr, newp: rawptr, newlen: int) -> posix.result ---
// NOTE(Oskar): available from OSX 10.6 and iOS 3.2.
// For older versions there is `syscall(SYS_thread_selfid)`, but not really
// the same thing apparently.
pthread_threadid_np :: proc "c" (rawptr, ^u64) -> i32 ---
}
_get_current_thread_id :: proc "contextless" () -> int {
tid: u64
// NOTE(Oskar): available from OSX 10.6 and iOS 3.2.
// For older versions there is `syscall(SYS_thread_selfid)`, but not really
// the same thing apparently.
foreign pthread { pthread_threadid_np :: proc "c" (rawptr, ^u64) -> c.int --- }
pthread_threadid_np(nil, &tid)
return int(tid)
}
_get_processor_core_count :: proc() -> int {
count : int = 0
count: int = 0
count_size := size_of(count)
if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == 0 {
if _sysctlbyname("hw.logicalcpu", &count, &count_size, nil, 0) == .OK {
if count > 0 {
return count
}

View File

@@ -58,11 +58,11 @@ _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_stat
return
}
_process_close :: proc(process: Process) -> Error {
_process_kill :: proc(process: Process) -> (err: Error) {
return .Unsupported
}
_process_kill :: proc(process: Process) -> (err: Error) {
_process_terminate :: proc(process: Process) -> (err: Error) {
return .Unsupported
}

View File

@@ -539,6 +539,7 @@ _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_stat
exit_code: u32
if !win32.GetExitCodeProcess(handle, &exit_code) {
err =_get_platform_error()
_process_close(process)
return
}
time_created: win32.FILETIME
@@ -547,6 +548,7 @@ _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_stat
time_user: win32.FILETIME
if !win32.GetProcessTimes(handle, &time_created, &time_exited, &time_kernel, &time_user) {
err = _get_platform_error()
_process_close(process)
return
}
process_state = {
@@ -557,17 +559,18 @@ _process_wait :: proc(process: Process, timeout: time.Duration) -> (process_stat
system_time = _filetime_to_duration(time_kernel),
user_time = _filetime_to_duration(time_user),
}
_process_close(process)
return
case win32.WAIT_TIMEOUT:
err = General_Error.Timeout
return
case:
err = _get_platform_error()
_process_close(process)
return
}
}
@(private="package")
_process_close :: proc(process: Process) -> Error {
if !win32.CloseHandle(win32.HANDLE(process.handle)) {
return _get_platform_error()
@@ -587,6 +590,49 @@ _process_kill :: proc(process: Process) -> Error {
return nil
}
@(private="package")
_process_terminate :: proc(process: Process) -> Error {
Enum_Windows_State :: struct {
has_windows: bool,
pid: int,
}
state: Enum_Windows_State
state.pid = process.pid
ok := win32.EnumWindows(
proc "system" (hwnd: win32.HWND, lParam: win32.LPARAM) -> win32.BOOL {
#assert(size_of(win32.LPARAM) == size_of(^Enum_Windows_State))
state := (^Enum_Windows_State)(rawptr(uintptr(lParam)))
dwPid: win32.DWORD
win32.GetWindowThreadProcessId(hwnd, &dwPid)
if (dwPid == win32.DWORD(state.pid)) {
state.has_windows = true
win32.PostMessageW(hwnd, win32.WM_CLOSE, 0, 0)
}
return true
},
win32.LPARAM(uintptr(&state)),
)
if state.has_windows {
if ok {
return nil
}
err := _get_platform_error()
kill_err := _process_kill(process)
return kill_err == nil ? nil : err
}
if !win32.GenerateConsoleCtrlEvent(win32.CTRL_C_EVENT, win32.DWORD(process.pid)) {
err := _get_platform_error()
kill_err := _process_kill(process)
return kill_err == nil ? nil : err
}
return nil
}
_filetime_to_duration :: proc(filetime: win32.FILETIME) -> time.Duration {
ticks := u64(filetime.dwHighDateTime)<<32 | u64(filetime.dwLowDateTime)
return time.Duration(ticks * 100)