Skip to content

system: subprocessing interface #911

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 60 commits into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
979ba84
add c source
perazz Dec 24, 2024
79ddfc4
add subprocess module
perazz Dec 24, 2024
adacbcf
`to_c_string`: move to strings, document
perazz Dec 24, 2024
5b543a2
use temporary `getfile` and `linalg_state_type` f
perazz Dec 24, 2024
519d53d
implement `join`
perazz Dec 24, 2024
1449b8d
fixes to build
perazz Dec 24, 2024
cf35194
create submodule
perazz Dec 24, 2024
e8451b2
unify `sleep` interface
perazz Dec 24, 2024
48da380
add single-command `run` API
perazz Dec 24, 2024
1f4de32
add tests
perazz Dec 24, 2024
6fbc2e6
getfile: remove trailing new line characters
perazz Dec 24, 2024
f9bf304
fix tests to be cross-platform
perazz Dec 24, 2024
71facb3
use `nanosleep` rather than `usleep`
perazz Dec 24, 2024
6ea72d1
add examples
perazz Dec 24, 2024
e35b37a
`kill` process
perazz Dec 24, 2024
237e9ff
add process killing example
perazz Dec 24, 2024
2c58fca
on Windows, redirect to `NUL` if output not requested
perazz Dec 24, 2024
136b5b8
remove unused process handle
perazz Dec 25, 2024
d8df028
document `run` interface
perazz Dec 25, 2024
94f2bdf
document `is_running`, `is_completed`, `elapsed`
perazz Dec 25, 2024
3fb88e4
add `system` page
perazz Dec 25, 2024
53fc8e5
document `wait`
perazz Dec 25, 2024
b30cae4
document `update`
perazz Dec 25, 2024
122fbc6
document `kill`
perazz Dec 25, 2024
56ed7c8
document `sleep`
perazz Dec 25, 2024
c617048
document `has_win32`
perazz Dec 25, 2024
ed0565c
fix
perazz Dec 25, 2024
c03655a
Merge branch 'subprocess' of github.com:perazz/stdlib into subprocess
perazz Dec 26, 2024
eb77455
Merge branch 'fortran-lang:master' into subprocess
perazz Dec 26, 2024
74b6ebe
change syntax for `ifx` fix
perazz Dec 26, 2024
9873bc9
fix `sleep` us -> ns
perazz Dec 26, 2024
34732ff
fix `pid` size
perazz Dec 26, 2024
53b03b0
full-cmd: do not use stack
perazz Dec 26, 2024
5a1bd54
fix `sleep`
perazz Dec 26, 2024
9b74bea
process example 2: set max_wait_time
perazz Dec 26, 2024
bdb2840
sleep: fix `bind(C)` interface
perazz Dec 26, 2024
a1aaf2f
split `run` vs `runasync`
perazz Jan 28, 2025
4d5eb32
`run/runasync` docs
perazz Jan 28, 2025
56f02ab
`has_win32` -> `is_windows`
perazz Jan 28, 2025
15689bc
Update example_process_1.f90
perazz Jan 28, 2025
060dec7
Merge branch 'master' into subprocess
perazz Jan 28, 2025
3560a6f
missing `is_windows` tests
perazz Jan 28, 2025
e75bbc9
Merge branch 'subprocess' of github.com:perazz/stdlib into subprocess
perazz Jan 28, 2025
d1a4715
Update example_process_4.f90
perazz Jan 28, 2025
68dca8d
Merge branch 'fortran-lang:master' into subprocess
perazz Jan 29, 2025
7653cc4
add object oriented interface
perazz Feb 4, 2025
06c7136
add oop example
perazz Feb 4, 2025
f40a547
process ID (`pid`) getter interface
perazz Feb 4, 2025
d2ee2f2
implement callback
perazz Feb 4, 2025
3f08a8b
add callback example
perazz Feb 4, 2025
d694dcf
fix submodule
perazz Feb 4, 2025
80a2d0a
intel fix: no inline type
perazz Feb 4, 2025
20c045d
document callback and payload functionality
perazz Feb 4, 2025
bb98188
Merge branch 'master' into subprocess
perazz Feb 17, 2025
33f81a3
`to_c_string` -> `to_c_char`
perazz Feb 17, 2025
d8f8be7
Merge branch 'subprocess' of https://github.com/perazz/stdlib into su…
perazz Feb 17, 2025
deabd0c
Update doc/specs/stdlib_system.md
perazz Feb 17, 2025
f55ddb7
Update doc/specs/stdlib_system.md
perazz Feb 17, 2025
d4422cf
move all examples to separate files
perazz Feb 17, 2025
0675b8c
Merge branch 'subprocess' of https://github.com/perazz/stdlib into su…
perazz Feb 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
kill process
  • Loading branch information
perazz committed Dec 24, 2024
commit e35b37aee4c2834f1658f35226057de31d90102e
10 changes: 10 additions & 0 deletions src/stdlib_system.F90
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module stdlib_system
public :: is_running
public :: update
public :: wait
public :: kill
public :: elapsed
public :: has_win32

Expand Down Expand Up @@ -116,6 +117,15 @@ module subroutine update_process_state(process)
end subroutine update_process_state
end interface update

! Kill a process
interface kill
module subroutine process_kill(process, success)
type(process_type), intent(inout) :: process
! Return a boolean flag for successful operation
logical, intent(out) :: success
end subroutine process_kill
end interface kill

!! version: experimental
!!
interface sleep
Expand Down
43 changes: 41 additions & 2 deletions src/stdlib_system_subprocess.F90
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,18 @@ subroutine process_create(cmd, stdin_stream, stdin_file, stdout_file, stderr_fil
integer(process_ID), intent(out) :: pid
end subroutine process_create

logical(c_bool) function process_system_kill(pid) bind(C, name='process_kill')
import c_bool, process_ID
implicit none
integer(process_ID), intent(in), value :: pid
end function process_system_kill

! System implementation of a wait function
subroutine process_wait(seconds) bind(C,name='process_wait')
import c_float
implicit none
real(c_float), intent(in) :: seconds
end subroutine process_wait
end subroutine process_wait

! Return path to the null device
type(c_ptr) function process_null_device(len) bind(C,name='process_null_device')
Expand Down Expand Up @@ -280,7 +286,7 @@ module subroutine wait_for_completion(process, max_wait_time)

end subroutine wait_for_completion

!> Update a process's state, and
!> Update a process's state, and save it to the process variable
module subroutine update_process_state(process)
type(process_type), intent(inout) :: process

Expand Down Expand Up @@ -318,6 +324,39 @@ module subroutine update_process_state(process)

end subroutine update_process_state

! Kill a process
module subroutine process_kill(process, success)
type(process_type), intent(inout) :: process
! Return a boolean flag for successful operation
logical, intent(out) :: success

integer(c_int) :: exit_code
logical(c_bool) :: running

success = .true.

! No need to
if (process%completed) return
if (process%id == FORKED_PROCESS) return

success = logical(process_system_kill(process%id))

if (success) then

call process_query_status(process%id, wait=C_TRUE, is_running=running, exit_code=exit_code)

process%completed = .not.running

if (process%completed) then
! Process completed, may have returned an error code
process%exit_code = exit_code
call save_completed_state(process,delete_files=.true.)
end if

end if

end subroutine process_kill

subroutine save_completed_state(process,delete_files)
type(process_type), intent(inout) :: process
logical, intent(in) :: delete_files
Expand Down
58 changes: 58 additions & 0 deletions src/stdlib_system_subprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#endif // _WIN32

// Typedefs
Expand Down Expand Up @@ -164,6 +165,33 @@ void process_query_status_windows(int pid, bool wait, bool* is_running, int* exi
CloseHandle(hProcess);
}

// Kill a process on Windows by sending a PROCESS_TERMINATE signal.
// Return true if the operation succeeded, or false if it failed (process does not
// exist anymore, or we may not have the rights to kill the process).
bool process_kill_windows(int pid) {
HANDLE hProcess;

// Open the process with terminate rights
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);

if (hProcess == NULL) {
// Failed to open the process; return false
return false;
}

// Attempt to terminate the process
if (!TerminateProcess(hProcess, 1)) {
// Failed to terminate the process
CloseHandle(hProcess);
return false;
}

// Successfully terminated the process
CloseHandle(hProcess);
return true;
}


#else // _WIN32

/////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -208,6 +236,26 @@ void process_query_status_unix(int pid, bool wait, bool* is_running, int* exit_c
}
}

// Kill a process by sending a SIGKILL signal. Return .true. if succeeded, or false if not.
// Killing process may fail due to unexistent process, or not enough rights to kill.
bool process_kill_unix(int pid) {
// Send the SIGKILL signal to the process
if (kill(pid, SIGKILL) == 0) {
// Successfully sent the signal
return true;
}

// If `kill` fails, check if the process no longer exists
if (errno == ESRCH) {
// Process does not exist
return true; // Already "terminated"
}

// Other errors occurred
return false;
}


// On UNIX systems: just fork a new process. The command line will be executed from Fortran.
void process_create_posix(stdlib_handle* handle, stdlib_pid* pid)
{
Expand Down Expand Up @@ -243,6 +291,16 @@ void process_query_status(int pid, bool wait, bool* is_running, int* exit_code)
#endif // _WIN32
}

// Cross-platform interface: kill process by ID
bool process_kill(int pid)
{
#ifdef _WIN32
return process_kill_windows(pid);
#else
return process_kill_unix(pid);
#endif // _WIN32
}

// Cross-platform interface: sleep(seconds)
void process_wait(float seconds)
{
Expand Down
37 changes: 36 additions & 1 deletion test/system/test_subprocess.f90
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module test_subprocess
use testdrive, only : new_unittest, unittest_type, error_type, check, skip_test
use stdlib_system, only: process_type, run, is_running, wait, update, elapsed, has_win32
use stdlib_system, only: process_type, run, is_running, wait, update, elapsed, has_win32, kill

implicit none

Expand All @@ -14,6 +14,7 @@ subroutine collect_suite(testsuite)
testsuite = [ &
new_unittest('test_run_synchronous', test_run_synchronous), &
new_unittest('test_run_asynchronous', test_run_asynchronous), &
new_unittest('test_process_kill', test_process_kill), &
new_unittest('test_process_state', test_process_state) &
]
end subroutine collect_suite
Expand Down Expand Up @@ -59,6 +60,40 @@ subroutine test_run_asynchronous(error)

end subroutine test_run_asynchronous

!> Test killing an asynchronous process
subroutine test_process_kill(error)
type(error_type), allocatable, intent(out) :: error
type(process_type) :: process
logical :: running, success

! Start a long-running process asynchronously
if (has_win32()) then
process = run("ping -n 10 127.0.0.1", wait=.false.)
else
process = run("ping -c 10 127.0.0.1", wait=.false.)
endif

! Ensure the process starts running
call check(error, .not. process%completed, "Process should not be completed immediately after starting")
if (allocated(error)) return

running = is_running(process)
call check(error, running, "Process should be running immediately after starting")
if (allocated(error)) return

! Kill the process
call kill(process, success)
call check(error, success, "Failed to kill the process")
if (allocated(error)) return

! Verify the process is no longer running
call check(error, .not. is_running(process), "Process should not be running after being killed")
if (allocated(error)) return

! Ensure process state updates correctly after killing
call check(error, process%completed, "Process should be marked as completed after being killed")
end subroutine test_process_kill

!> Test updating and checking process state
subroutine test_process_state(error)
type(error_type), allocatable, intent(out) :: error
Expand Down