Skip to content

Commit e35b37a

Browse files
committed
kill process
1 parent 6ea72d1 commit e35b37a

File tree

4 files changed

+145
-3
lines changed

4 files changed

+145
-3
lines changed

src/stdlib_system.F90

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module stdlib_system
1313
public :: is_running
1414
public :: update
1515
public :: wait
16+
public :: kill
1617
public :: elapsed
1718
public :: has_win32
1819

@@ -116,6 +117,15 @@ module subroutine update_process_state(process)
116117
end subroutine update_process_state
117118
end interface update
118119

120+
! Kill a process
121+
interface kill
122+
module subroutine process_kill(process, success)
123+
type(process_type), intent(inout) :: process
124+
! Return a boolean flag for successful operation
125+
logical, intent(out) :: success
126+
end subroutine process_kill
127+
end interface kill
128+
119129
!! version: experimental
120130
!!
121131
interface sleep

src/stdlib_system_subprocess.F90

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,18 @@ subroutine process_create(cmd, stdin_stream, stdin_file, stdout_file, stderr_fil
4343
integer(process_ID), intent(out) :: pid
4444
end subroutine process_create
4545

46+
logical(c_bool) function process_system_kill(pid) bind(C, name='process_kill')
47+
import c_bool, process_ID
48+
implicit none
49+
integer(process_ID), intent(in), value :: pid
50+
end function process_system_kill
51+
4652
! System implementation of a wait function
4753
subroutine process_wait(seconds) bind(C,name='process_wait')
4854
import c_float
4955
implicit none
5056
real(c_float), intent(in) :: seconds
51-
end subroutine process_wait
57+
end subroutine process_wait
5258

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

281287
end subroutine wait_for_completion
282288

283-
!> Update a process's state, and
289+
!> Update a process's state, and save it to the process variable
284290
module subroutine update_process_state(process)
285291
type(process_type), intent(inout) :: process
286292

@@ -318,6 +324,39 @@ module subroutine update_process_state(process)
318324

319325
end subroutine update_process_state
320326

327+
! Kill a process
328+
module subroutine process_kill(process, success)
329+
type(process_type), intent(inout) :: process
330+
! Return a boolean flag for successful operation
331+
logical, intent(out) :: success
332+
333+
integer(c_int) :: exit_code
334+
logical(c_bool) :: running
335+
336+
success = .true.
337+
338+
! No need to
339+
if (process%completed) return
340+
if (process%id == FORKED_PROCESS) return
341+
342+
success = logical(process_system_kill(process%id))
343+
344+
if (success) then
345+
346+
call process_query_status(process%id, wait=C_TRUE, is_running=running, exit_code=exit_code)
347+
348+
process%completed = .not.running
349+
350+
if (process%completed) then
351+
! Process completed, may have returned an error code
352+
process%exit_code = exit_code
353+
call save_completed_state(process,delete_files=.true.)
354+
end if
355+
356+
end if
357+
358+
end subroutine process_kill
359+
321360
subroutine save_completed_state(process,delete_files)
322361
type(process_type), intent(inout) :: process
323362
logical, intent(in) :: delete_files

src/stdlib_system_subprocess.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <unistd.h>
1313
#include <time.h>
1414
#include <errno.h>
15+
#include <signal.h>
1516
#endif // _WIN32
1617

1718
// Typedefs
@@ -164,6 +165,33 @@ void process_query_status_windows(int pid, bool wait, bool* is_running, int* exi
164165
CloseHandle(hProcess);
165166
}
166167

168+
// Kill a process on Windows by sending a PROCESS_TERMINATE signal.
169+
// Return true if the operation succeeded, or false if it failed (process does not
170+
// exist anymore, or we may not have the rights to kill the process).
171+
bool process_kill_windows(int pid) {
172+
HANDLE hProcess;
173+
174+
// Open the process with terminate rights
175+
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
176+
177+
if (hProcess == NULL) {
178+
// Failed to open the process; return false
179+
return false;
180+
}
181+
182+
// Attempt to terminate the process
183+
if (!TerminateProcess(hProcess, 1)) {
184+
// Failed to terminate the process
185+
CloseHandle(hProcess);
186+
return false;
187+
}
188+
189+
// Successfully terminated the process
190+
CloseHandle(hProcess);
191+
return true;
192+
}
193+
194+
167195
#else // _WIN32
168196

169197
/////////////////////////////////////////////////////////////////////////////////////
@@ -208,6 +236,26 @@ void process_query_status_unix(int pid, bool wait, bool* is_running, int* exit_c
208236
}
209237
}
210238

239+
// Kill a process by sending a SIGKILL signal. Return .true. if succeeded, or false if not.
240+
// Killing process may fail due to unexistent process, or not enough rights to kill.
241+
bool process_kill_unix(int pid) {
242+
// Send the SIGKILL signal to the process
243+
if (kill(pid, SIGKILL) == 0) {
244+
// Successfully sent the signal
245+
return true;
246+
}
247+
248+
// If `kill` fails, check if the process no longer exists
249+
if (errno == ESRCH) {
250+
// Process does not exist
251+
return true; // Already "terminated"
252+
}
253+
254+
// Other errors occurred
255+
return false;
256+
}
257+
258+
211259
// On UNIX systems: just fork a new process. The command line will be executed from Fortran.
212260
void process_create_posix(stdlib_handle* handle, stdlib_pid* pid)
213261
{
@@ -243,6 +291,16 @@ void process_query_status(int pid, bool wait, bool* is_running, int* exit_code)
243291
#endif // _WIN32
244292
}
245293

294+
// Cross-platform interface: kill process by ID
295+
bool process_kill(int pid)
296+
{
297+
#ifdef _WIN32
298+
return process_kill_windows(pid);
299+
#else
300+
return process_kill_unix(pid);
301+
#endif // _WIN32
302+
}
303+
246304
// Cross-platform interface: sleep(seconds)
247305
void process_wait(float seconds)
248306
{

test/system/test_subprocess.f90

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module test_subprocess
22
use testdrive, only : new_unittest, unittest_type, error_type, check, skip_test
3-
use stdlib_system, only: process_type, run, is_running, wait, update, elapsed, has_win32
3+
use stdlib_system, only: process_type, run, is_running, wait, update, elapsed, has_win32, kill
44

55
implicit none
66

@@ -14,6 +14,7 @@ subroutine collect_suite(testsuite)
1414
testsuite = [ &
1515
new_unittest('test_run_synchronous', test_run_synchronous), &
1616
new_unittest('test_run_asynchronous', test_run_asynchronous), &
17+
new_unittest('test_process_kill', test_process_kill), &
1718
new_unittest('test_process_state', test_process_state) &
1819
]
1920
end subroutine collect_suite
@@ -59,6 +60,40 @@ subroutine test_run_asynchronous(error)
5960

6061
end subroutine test_run_asynchronous
6162

63+
!> Test killing an asynchronous process
64+
subroutine test_process_kill(error)
65+
type(error_type), allocatable, intent(out) :: error
66+
type(process_type) :: process
67+
logical :: running, success
68+
69+
! Start a long-running process asynchronously
70+
if (has_win32()) then
71+
process = run("ping -n 10 127.0.0.1", wait=.false.)
72+
else
73+
process = run("ping -c 10 127.0.0.1", wait=.false.)
74+
endif
75+
76+
! Ensure the process starts running
77+
call check(error, .not. process%completed, "Process should not be completed immediately after starting")
78+
if (allocated(error)) return
79+
80+
running = is_running(process)
81+
call check(error, running, "Process should be running immediately after starting")
82+
if (allocated(error)) return
83+
84+
! Kill the process
85+
call kill(process, success)
86+
call check(error, success, "Failed to kill the process")
87+
if (allocated(error)) return
88+
89+
! Verify the process is no longer running
90+
call check(error, .not. is_running(process), "Process should not be running after being killed")
91+
if (allocated(error)) return
92+
93+
! Ensure process state updates correctly after killing
94+
call check(error, process%completed, "Process should be marked as completed after being killed")
95+
end subroutine test_process_kill
96+
6297
!> Test updating and checking process state
6398
subroutine test_process_state(error)
6499
type(error_type), allocatable, intent(out) :: error

0 commit comments

Comments
 (0)