##// END OF EJS Templates
tests: teach `killdaemons` on Windows to use an exit code provided by a caller...
Matt Harbison -
r52888:23cc79e0 default
parent child Browse files
Show More
@@ -1,131 +1,136
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2
2
3 import os
3 import os
4 import signal
4 import signal
5 import sys
5 import sys
6 import time
6 import time
7
7
8 if os.name == 'nt':
8 if os.name == 'nt':
9 import ctypes
9 import ctypes
10
10
11 _BOOL = ctypes.c_long
11 _BOOL = ctypes.c_long
12 _DWORD = ctypes.c_ulong
12 _DWORD = ctypes.c_ulong
13 _UINT = ctypes.c_uint
13 _UINT = ctypes.c_uint
14 _HANDLE = ctypes.c_void_p
14 _HANDLE = ctypes.c_void_p
15
15
16 ctypes.windll.kernel32.CloseHandle.argtypes = [_HANDLE]
16 ctypes.windll.kernel32.CloseHandle.argtypes = [_HANDLE]
17 ctypes.windll.kernel32.CloseHandle.restype = _BOOL
17 ctypes.windll.kernel32.CloseHandle.restype = _BOOL
18
18
19 ctypes.windll.kernel32.GetLastError.argtypes = []
19 ctypes.windll.kernel32.GetLastError.argtypes = []
20 ctypes.windll.kernel32.GetLastError.restype = _DWORD
20 ctypes.windll.kernel32.GetLastError.restype = _DWORD
21
21
22 ctypes.windll.kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD]
22 ctypes.windll.kernel32.OpenProcess.argtypes = [_DWORD, _BOOL, _DWORD]
23 ctypes.windll.kernel32.OpenProcess.restype = _HANDLE
23 ctypes.windll.kernel32.OpenProcess.restype = _HANDLE
24
24
25 ctypes.windll.kernel32.TerminateProcess.argtypes = [_HANDLE, _UINT]
25 ctypes.windll.kernel32.TerminateProcess.argtypes = [_HANDLE, _UINT]
26 ctypes.windll.kernel32.TerminateProcess.restype = _BOOL
26 ctypes.windll.kernel32.TerminateProcess.restype = _BOOL
27
27
28 ctypes.windll.kernel32.WaitForSingleObject.argtypes = [_HANDLE, _DWORD]
28 ctypes.windll.kernel32.WaitForSingleObject.argtypes = [_HANDLE, _DWORD]
29 ctypes.windll.kernel32.WaitForSingleObject.restype = _DWORD
29 ctypes.windll.kernel32.WaitForSingleObject.restype = _DWORD
30
30
31 def _check(ret, expectederr=None):
31 def _check(ret, expectederr=None):
32 if ret == 0:
32 if ret == 0:
33 winerrno = ctypes.GetLastError()
33 winerrno = ctypes.GetLastError()
34 if winerrno == expectederr:
34 if winerrno == expectederr:
35 return True
35 return True
36 raise ctypes.WinError(winerrno)
36 raise ctypes.WinError(winerrno)
37
37
38 def kill(pid, logfn, tryhard=True):
38 def kill(pid, logfn, tryhard=True):
39 logfn('# Killing daemon process %d' % pid)
39 logfn('# Killing daemon process %d' % pid)
40 PROCESS_TERMINATE = 1
40 PROCESS_TERMINATE = 1
41 PROCESS_QUERY_INFORMATION = 0x400
41 PROCESS_QUERY_INFORMATION = 0x400
42 SYNCHRONIZE = 0x00100000
42 SYNCHRONIZE = 0x00100000
43 WAIT_OBJECT_0 = 0
43 WAIT_OBJECT_0 = 0
44 WAIT_TIMEOUT = 258
44 WAIT_TIMEOUT = 258
45 WAIT_FAILED = _DWORD(0xFFFFFFFF).value
45 WAIT_FAILED = _DWORD(0xFFFFFFFF).value
46 handle = ctypes.windll.kernel32.OpenProcess(
46 handle = ctypes.windll.kernel32.OpenProcess(
47 PROCESS_TERMINATE | SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
47 PROCESS_TERMINATE | SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
48 False,
48 False,
49 pid,
49 pid,
50 )
50 )
51 if handle is None:
51 if handle is None:
52 _check(0, 87) # err 87 when process not found
52 _check(0, 87) # err 87 when process not found
53 return # process not found, already finished
53 return # process not found, already finished
54 try:
54 try:
55 r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
55 r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
56 if r == WAIT_OBJECT_0:
56 if r == WAIT_OBJECT_0:
57 pass # terminated, but process handle still available
57 pass # terminated, but process handle still available
58 elif r == WAIT_TIMEOUT:
58 elif r == WAIT_TIMEOUT:
59 _check(ctypes.windll.kernel32.TerminateProcess(handle, -1))
59 # Allow the caller to optionally specify the exit code, to
60 # simulate killing with a signal.
61 exit_code = int(os.environ.get("DAEMON_EXITCODE", -1))
62 _check(
63 ctypes.windll.kernel32.TerminateProcess(handle, exit_code)
64 )
60 elif r == WAIT_FAILED:
65 elif r == WAIT_FAILED:
61 _check(0) # err stored in GetLastError()
66 _check(0) # err stored in GetLastError()
62
67
63 # TODO?: forcefully kill when timeout
68 # TODO?: forcefully kill when timeout
64 # and ?shorter waiting time? when tryhard==True
69 # and ?shorter waiting time? when tryhard==True
65 r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
70 r = ctypes.windll.kernel32.WaitForSingleObject(handle, 100)
66 # timeout = 100 ms
71 # timeout = 100 ms
67 if r == WAIT_OBJECT_0:
72 if r == WAIT_OBJECT_0:
68 pass # process is terminated
73 pass # process is terminated
69 elif r == WAIT_TIMEOUT:
74 elif r == WAIT_TIMEOUT:
70 logfn('# Daemon process %d is stuck')
75 logfn('# Daemon process %d is stuck')
71 elif r == WAIT_FAILED:
76 elif r == WAIT_FAILED:
72 _check(0) # err stored in GetLastError()
77 _check(0) # err stored in GetLastError()
73 except: # re-raises
78 except: # re-raises
74 ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error
79 ctypes.windll.kernel32.CloseHandle(handle) # no _check, keep error
75 raise
80 raise
76 _check(ctypes.windll.kernel32.CloseHandle(handle))
81 _check(ctypes.windll.kernel32.CloseHandle(handle))
77
82
78 else:
83 else:
79
84
80 def kill(pid, logfn, tryhard=True):
85 def kill(pid, logfn, tryhard=True):
81 try:
86 try:
82 os.kill(pid, 0)
87 os.kill(pid, 0)
83 logfn('# Killing daemon process %d' % pid)
88 logfn('# Killing daemon process %d' % pid)
84 os.kill(pid, signal.SIGTERM)
89 os.kill(pid, signal.SIGTERM)
85 if tryhard:
90 if tryhard:
86 for i in range(10):
91 for i in range(10):
87 time.sleep(0.05)
92 time.sleep(0.05)
88 os.kill(pid, 0)
93 os.kill(pid, 0)
89 else:
94 else:
90 time.sleep(0.1)
95 time.sleep(0.1)
91 os.kill(pid, 0)
96 os.kill(pid, 0)
92 logfn('# Daemon process %d is stuck - really killing it' % pid)
97 logfn('# Daemon process %d is stuck - really killing it' % pid)
93 os.kill(pid, signal.SIGKILL)
98 os.kill(pid, signal.SIGKILL)
94 except ProcessLookupError:
99 except ProcessLookupError:
95 pass
100 pass
96
101
97
102
98 def killdaemons(pidfile, tryhard=True, remove=False, logfn=None):
103 def killdaemons(pidfile, tryhard=True, remove=False, logfn=None):
99 if not logfn:
104 if not logfn:
100 logfn = lambda s: s
105 logfn = lambda s: s
101 # Kill off any leftover daemon processes
106 # Kill off any leftover daemon processes
102 try:
107 try:
103 pids = []
108 pids = []
104 with open(pidfile) as fp:
109 with open(pidfile) as fp:
105 for line in fp:
110 for line in fp:
106 try:
111 try:
107 pid = int(line)
112 pid = int(line)
108 if pid <= 0:
113 if pid <= 0:
109 raise ValueError
114 raise ValueError
110 except ValueError:
115 except ValueError:
111 logfn(
116 logfn(
112 '# Not killing daemon process %s - invalid pid'
117 '# Not killing daemon process %s - invalid pid'
113 % line.rstrip()
118 % line.rstrip()
114 )
119 )
115 continue
120 continue
116 pids.append(pid)
121 pids.append(pid)
117 for pid in pids:
122 for pid in pids:
118 kill(pid, logfn, tryhard)
123 kill(pid, logfn, tryhard)
119 if remove:
124 if remove:
120 os.unlink(pidfile)
125 os.unlink(pidfile)
121 except IOError:
126 except IOError:
122 pass
127 pass
123
128
124
129
125 if __name__ == '__main__':
130 if __name__ == '__main__':
126 if len(sys.argv) > 1:
131 if len(sys.argv) > 1:
127 (path,) = sys.argv[1:]
132 (path,) = sys.argv[1:]
128 else:
133 else:
129 path = os.environ["DAEMON_PIDS"]
134 path = os.environ["DAEMON_PIDS"]
130
135
131 killdaemons(path, remove=True)
136 killdaemons(path, remove=True)
General Comments 0
You need to be logged in to leave comments. Login now