Browse Source

os2 linux process_wait rework; add Sig_Child_Code to sys/linux bits

jason 1 year ago
parent
commit
c4d43bbab0
2 changed files with 167 additions and 90 deletions
  1. 153 90
      core/os/os2/process_linux.odin
  2. 14 0
      core/sys/linux/bits.odin

+ 153 - 90
core/os/os2/process_linux.odin

@@ -6,7 +6,6 @@ import "base:runtime"
 import "base:intrinsics"
 
 import "core:fmt"
-import "core:mem"
 import "core:time"
 import "core:slice"
 import "core:strings"
@@ -504,11 +503,11 @@ _process_start :: proc(desc: Process_Desc) -> (process: Process, err: Error) {
 	return
 }
 
-_process_state_update_times :: proc(p: Process, state: ^Process_State) -> (err: Error) {
+_process_state_update_times :: proc(state: ^Process_State) -> (err: Error) {
 	TEMP_ALLOCATOR_GUARD()
 
 	stat_path_buf: [32]u8
-	_ = fmt.bprintf(stat_path_buf[:], "/proc/%d/stat", p.pid)
+	_ = fmt.bprintf(stat_path_buf[:], "/proc/%d/stat", state.pid)
 	stat_buf: []u8
 	stat_buf, err = _read_entire_pseudo_file(cstring(&stat_path_buf[0]), temp_allocator())
 	if err != nil {
@@ -547,115 +546,179 @@ _process_state_update_times :: proc(p: Process, state: ^Process_State) -> (err:
 	return
 }
 
-@(private="package")
-_process_wait :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
+_reap_terminated :: proc(process: Process) -> (state: Process_State, err: Error) {
+	state.pid = process.pid
+	_process_state_update_times(&state)
+
+	info: linux.Sig_Info
+	errno := linux.Errno.EINTR
+	for errno == .EINTR {
+		errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WEXITED}, nil)
+	}
+	err = _get_platform_error(errno)
+
+	switch linux.Sig_Child_Code(info.code) {
+	case .NONE, .CONTINUED, .STOPPED:
+	case .EXITED:
+		state.exited = true
+		state.exit_code = int(info.status)
+		state.success = state.exit_code == 0
+	case .KILLED, .DUMPED, .TRAPPED:
+		state.exited = true
+		state.exit_code = int(info.status)
+		state.success = false
+	}
+	return
+}
+
+_timed_wait_on_handle :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
+	timeout := timeout
+
 	process_state.pid = process.pid
+	pidfd := linux.Fd(process.handle)
+	pollfd: [1]linux.Poll_Fd = {
+		{
+			fd = pidfd,
+			events = {.IN},
+		},
+	}
+
+	start_tick := time.tick_now()
+
+	mask: bit_set[0..<64; u64]
+	mask += { int(linux.Signal.SIGCHLD) - 1 }
+	sigchld_set := transmute(linux.Sig_Set)(mask)
+
+	info: linux.Sig_Info
+	for {
+		if timeout <= 0 {
+			_process_state_update_times(&process_state)
+			return
+		}
 
-	errno: linux.Errno
-	options: linux.Wait_Options
-	big_if: if timeout == 0 {
-		options += {.WNOHANG}
-	} else if timeout > 0 {
 		ts: linux.Time_Spec = {
 			time_sec  = uint(timeout / time.Second),
 			time_nsec = uint(timeout % time.Second),
 		}
 
-		if false {//process.handle != PIDFD_UNASSIGNED {
-			pollfd: [1]linux.Poll_Fd = {
-				{
-					fd = linux.Fd(process.handle),
-					events = {.IN},
-				},
+		n, errno := linux.ppoll(pollfd[:], &ts, &sigchld_set)
+		if errno != .NONE {
+			if errno == .EINTR {
+				new_tick := time.tick_now()
+				timeout -= time.tick_diff(start_tick, new_tick)
+				start_tick = new_tick
+				continue
 			}
+			return process_state, _get_platform_error(errno)
+		}
 
-			for {
-				n, e := linux.ppoll(pollfd[:], &ts, nil)
-				if e == .EINTR {
-					continue
-				}
-				if e != .NONE {
-					return process_state, _get_platform_error(errno)
-				}
-				if n == 0 {
-					_process_state_update_times(process, &process_state)
-					return
-				}
-				break
-			}
-		} else {
-			mask: bit_set[0..<64; u64]
-			mask += { int(linux.Signal.SIGCHLD) - 1 }
+		if n == 0 {  // timeout with no events
+			_process_state_update_times(&process_state)
+			return
+		}
 
-			org_sigset: linux.Sig_Set
-			sigset: linux.Sig_Set
-			mem.copy(&sigset, &mask, size_of(mask))
-			errno = linux.rt_sigprocmask(.SIG_BLOCK, &sigset, &org_sigset)
-			if errno != .NONE {
-				return process_state, _get_platform_error(errno)
-			}
-			defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil)
-
-			// In case there was a signal handler on SIGCHLD, avoid race
-			// condition by checking wait first.
-			options += {.WNOHANG}
-			waitid_options := options + {.WNOWAIT, .WEXITED}
-			info: linux.Sig_Info
-			errno = linux.waitid(.PID, linux.Id(process.pid), &info, waitid_options, nil)
-			if errno == .NONE && info.code != 0 {
-				break big_if
-			}
+		// This throws EBADF with pidfd
+		if errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WEXITED, .WNOHANG, .WNOWAIT}, nil); errno != .NONE {
+			return process_state, _get_platform_error(errno)
+		}
 
-			loop: for {
-				sigset = {}
-				mem.copy(&sigset, &mask, size_of(mask))
-
-				_, errno = linux.rt_sigtimedwait(&sigset, &info, &ts)
-				#partial switch errno {
-				case .EAGAIN: // timeout
-					_process_state_update_times(process, &process_state)
-					return
-				case .EINVAL:
-					return process_state, _get_platform_error(errno)
-				case .EINTR:
-					continue
-				case:
-					if info.pid == linux.Pid(process.pid) {
-						break loop
-					}
-				}
-			}
+		if info.signo == .SIGCHLD {
+			break
 		}
+
+		new_tick := time.tick_now()
+		timeout -= time.tick_diff(start_tick, new_tick)
+		start_tick = new_tick
 	}
 
-	status: u32
-	errno = .EINTR
-	for errno == .EINTR {
-		_, errno = linux.wait4(linux.Pid(process.pid), &status, options, nil)
-		if errno != .NONE {
-			_process_state_update_times(process, &process_state)
+	return _reap_terminated(process)
+}
+
+_timed_wait_on_pid :: proc(process: Process, timeout: time.Duration) -> (process_state: Process_State, err: Error) {
+	timeout := timeout
+	process_state.pid = process.pid
+
+	mask: bit_set[0..<64; u64]
+	mask += { int(linux.Signal.SIGCHLD) - 1 }
+	sigchld_set := transmute(linux.Sig_Set)(mask)
+
+	start_tick := time.tick_now()
+
+	org_sigset: linux.Sig_Set
+	errno := linux.rt_sigprocmask(.SIG_BLOCK, &sigchld_set, &org_sigset)
+	if errno != .NONE {
+		return process_state, _get_platform_error(errno)
+	}
+	defer linux.rt_sigprocmask(.SIG_SETMASK, &org_sigset, nil)
+
+	// In case there was a signal handler on SIGCHLD, avoid race
+	// condition by checking wait first.
+	info: linux.Sig_Info
+	errno = linux.waitid(.PID, linux.Id(process.pid), &info, {.WNOWAIT, .WEXITED, .WNOHANG}, nil)
+
+	for errno != .NONE || info.code == 0 || info.pid != linux.Pid(process.pid) {
+		if timeout <= 0 {
+			_process_state_update_times(&process_state)
+			return
+		}
+
+		ts: linux.Time_Spec = {
+			time_sec  = uint(timeout / time.Second),
+			time_nsec = uint(timeout % time.Second),
+		}
+
+		_, errno = linux.rt_sigtimedwait(&sigchld_set, &info, &ts)
+		#partial switch errno {
+		case .EAGAIN:   // timeout
+			_process_state_update_times(&process_state)
+			return
+		case .EINTR:
+			new_tick := time.tick_now()
+			timeout -= time.tick_diff(start_tick, new_tick)
+			start_tick = new_tick
+		case .EINVAL:
 			return process_state, _get_platform_error(errno)
 		}
 	}
 
-	_process_state_update_times(process, &process_state)
+	return _reap_terminated(process)
+}
 
-	// terminated by exit
-	if linux.WIFEXITED(status) {
-		process_state.exited = true
-		process_state.exit_code = int(linux.WEXITSTATUS(status))
-		process_state.success = process_state.exit_code == 0
-		return
+@(private="package")
+_process_wait :: proc(process: Process, timeout: time.Duration) -> (Process_State, Error) {
+	if timeout > 0 {
+		if process.handle == PIDFD_UNASSIGNED {
+			return _timed_wait_on_pid(process, timeout)
+		} else {
+			return _timed_wait_on_handle(process, timeout)
+		}
 	}
 
-	// terminated by signal
-	if linux.WIFSIGNALED(status) {
-		process_state.exited = false
-		process_state.exit_code = int(linux.WTERMSIG(status))
-		process_state.success = false
-		return
+	process_state: Process_State = {
+		pid = process.pid,
 	}
-	return
+
+	errno: linux.Errno
+	options: linux.Wait_Options = {.WEXITED}
+	if timeout == 0 {
+		options += {.WNOHANG}
+	}
+
+	info: linux.Sig_Info
+
+	errno = .EINTR
+	for errno == .EINTR {
+		errno = linux.waitid(.PID, linux.Id(process.pid), &info, options + {.WNOWAIT}, nil)
+	}
+	if errno == .EAGAIN || (errno == .NONE && info.signo != .SIGCHLD) {
+		_process_state_update_times(&process_state)
+		return process_state, nil
+	}
+	if errno != .NONE {
+		return process_state, _get_platform_error(errno)
+	}
+
+	return _reap_terminated(process)
 }
 
 @(private="package")

+ 14 - 0
core/sys/linux/bits.odin

@@ -983,6 +983,20 @@ Sig_Action_Flag :: enum u32 {
 	RESETHAND      = 31,
 }
 
+/*
+	Translation of code in Sig_Info for when signo is SIGCHLD
+*/
+Sig_Child_Code :: enum {
+	NONE,
+	EXITED,
+	KILLED,
+	DUMPED,
+	TRAPPED,
+	STOPPED,
+	CONTINUED,
+}
+
+
 /*
 	Type of socket to create
 	- For TCP you want to use SOCK_STREAM