1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
|
{-# LANGUAGE CPP, ForeignFunctionInterface #-}
module WinCBindings where
#if defined(mingw32_HOST_OS)
##if defined(i386_HOST_ARCH)
## define WINDOWS_CCONV stdcall
##elif defined(x86_64_HOST_ARCH)
## define WINDOWS_CCONV ccall
##else
## error Unknown mingw32 arch
##endif
import Foreign
import Foreign.C.Types
import System.Win32.File
import System.Win32.Types
#include <windows.h>
type LPPROCESS_INFORMATION = Ptr PROCESS_INFORMATION
data PROCESS_INFORMATION = PROCESS_INFORMATION
{ piProcess :: HANDLE
, piThread :: HANDLE
, piProcessId :: DWORD
, piThreadId :: DWORD
} deriving Show
instance Storable PROCESS_INFORMATION where
sizeOf = const #size PROCESS_INFORMATION
alignment = sizeOf
poke buf pinfo = do
(#poke PROCESS_INFORMATION, hProcess) buf (piProcess pinfo)
(#poke PROCESS_INFORMATION, hThread) buf (piThread pinfo)
(#poke PROCESS_INFORMATION, dwProcessId) buf (piProcessId pinfo)
(#poke PROCESS_INFORMATION, dwThreadId) buf (piThreadId pinfo)
peek buf = do
vhProcess <- (#peek PROCESS_INFORMATION, hProcess) buf
vhThread <- (#peek PROCESS_INFORMATION, hThread) buf
vdwProcessId <- (#peek PROCESS_INFORMATION, dwProcessId) buf
vdwThreadId <- (#peek PROCESS_INFORMATION, dwThreadId) buf
return $ PROCESS_INFORMATION {
piProcess = vhProcess,
piThread = vhThread,
piProcessId = vdwProcessId,
piThreadId = vdwThreadId}
type LPSTARTUPINFO = Ptr STARTUPINFO
data STARTUPINFO = STARTUPINFO
{ siCb :: DWORD
, siDesktop :: LPTSTR
, siTitle :: LPTSTR
, siX :: DWORD
, siY :: DWORD
, siXSize :: DWORD
, siYSize :: DWORD
, siXCountChars :: DWORD
, siYCountChars :: DWORD
, siFillAttribute :: DWORD
, siFlags :: DWORD
, siShowWindow :: WORD
, siStdInput :: HANDLE
, siStdOutput :: HANDLE
, siStdError :: HANDLE
} deriving Show
instance Storable STARTUPINFO where
sizeOf = const #size STARTUPINFO
alignment = sizeOf
poke buf si = do
(#poke STARTUPINFO, cb) buf (siCb si)
(#poke STARTUPINFO, lpDesktop) buf (siDesktop si)
(#poke STARTUPINFO, lpTitle) buf (siTitle si)
(#poke STARTUPINFO, dwX) buf (siX si)
(#poke STARTUPINFO, dwY) buf (siY si)
(#poke STARTUPINFO, dwXSize) buf (siXSize si)
(#poke STARTUPINFO, dwYSize) buf (siYSize si)
(#poke STARTUPINFO, dwXCountChars) buf (siXCountChars si)
(#poke STARTUPINFO, dwYCountChars) buf (siYCountChars si)
(#poke STARTUPINFO, dwFillAttribute) buf (siFillAttribute si)
(#poke STARTUPINFO, dwFlags) buf (siFlags si)
(#poke STARTUPINFO, wShowWindow) buf (siShowWindow si)
(#poke STARTUPINFO, hStdInput) buf (siStdInput si)
(#poke STARTUPINFO, hStdOutput) buf (siStdOutput si)
(#poke STARTUPINFO, hStdError) buf (siStdError si)
peek buf = do
vcb <- (#peek STARTUPINFO, cb) buf
vlpDesktop <- (#peek STARTUPINFO, lpDesktop) buf
vlpTitle <- (#peek STARTUPINFO, lpTitle) buf
vdwX <- (#peek STARTUPINFO, dwX) buf
vdwY <- (#peek STARTUPINFO, dwY) buf
vdwXSize <- (#peek STARTUPINFO, dwXSize) buf
vdwYSize <- (#peek STARTUPINFO, dwYSize) buf
vdwXCountChars <- (#peek STARTUPINFO, dwXCountChars) buf
vdwYCountChars <- (#peek STARTUPINFO, dwYCountChars) buf
vdwFillAttribute <- (#peek STARTUPINFO, dwFillAttribute) buf
vdwFlags <- (#peek STARTUPINFO, dwFlags) buf
vwShowWindow <- (#peek STARTUPINFO, wShowWindow) buf
vhStdInput <- (#peek STARTUPINFO, hStdInput) buf
vhStdOutput <- (#peek STARTUPINFO, hStdOutput) buf
vhStdError <- (#peek STARTUPINFO, hStdError) buf
return $ STARTUPINFO {
siCb = vcb,
siDesktop = vlpDesktop,
siTitle = vlpTitle,
siX = vdwX,
siY = vdwY,
siXSize = vdwXSize,
siYSize = vdwYSize,
siXCountChars = vdwXCountChars,
siYCountChars = vdwYCountChars,
siFillAttribute = vdwFillAttribute,
siFlags = vdwFlags,
siShowWindow = vwShowWindow,
siStdInput = vhStdInput,
siStdOutput = vhStdOutput,
siStdError = vhStdError}
data JOBOBJECT_EXTENDED_LIMIT_INFORMATION = JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{ jeliBasicLimitInformation :: JOBOBJECT_BASIC_LIMIT_INFORMATION
, jeliIoInfo :: IO_COUNTERS
, jeliProcessMemoryLimit :: SIZE_T
, jeliJobMemoryLimit :: SIZE_T
, jeliPeakProcessMemoryUsed :: SIZE_T
, jeliPeakJobMemoryUsed :: SIZE_T
} deriving Show
instance Storable JOBOBJECT_EXTENDED_LIMIT_INFORMATION where
sizeOf = const #size JOBOBJECT_EXTENDED_LIMIT_INFORMATION
alignment = const #alignment JOBOBJECT_EXTENDED_LIMIT_INFORMATION
poke buf jeli = do
(#poke JOBOBJECT_EXTENDED_LIMIT_INFORMATION, BasicLimitInformation) buf (jeliBasicLimitInformation jeli)
(#poke JOBOBJECT_EXTENDED_LIMIT_INFORMATION, IoInfo) buf (jeliIoInfo jeli)
(#poke JOBOBJECT_EXTENDED_LIMIT_INFORMATION, ProcessMemoryLimit) buf (jeliProcessMemoryLimit jeli)
(#poke JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobMemoryLimit) buf (jeliJobMemoryLimit jeli)
(#poke JOBOBJECT_EXTENDED_LIMIT_INFORMATION, PeakProcessMemoryUsed) buf (jeliPeakProcessMemoryUsed jeli)
(#poke JOBOBJECT_EXTENDED_LIMIT_INFORMATION, PeakJobMemoryUsed) buf (jeliPeakJobMemoryUsed jeli)
peek buf = do
vBasicLimitInformation <- (#peek JOBOBJECT_EXTENDED_LIMIT_INFORMATION, BasicLimitInformation) buf
vIoInfo <- (#peek JOBOBJECT_EXTENDED_LIMIT_INFORMATION, IoInfo) buf
vProcessMemoryLimit <- (#peek JOBOBJECT_EXTENDED_LIMIT_INFORMATION, ProcessMemoryLimit) buf
vJobMemoryLimit <- (#peek JOBOBJECT_EXTENDED_LIMIT_INFORMATION, JobMemoryLimit) buf
vPeakProcessMemoryUsed <- (#peek JOBOBJECT_EXTENDED_LIMIT_INFORMATION, PeakProcessMemoryUsed) buf
vPeakJobMemoryUsed <- (#peek JOBOBJECT_EXTENDED_LIMIT_INFORMATION, PeakJobMemoryUsed) buf
return $ JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
jeliBasicLimitInformation = vBasicLimitInformation,
jeliIoInfo = vIoInfo,
jeliProcessMemoryLimit = vProcessMemoryLimit,
jeliJobMemoryLimit = vJobMemoryLimit,
jeliPeakProcessMemoryUsed = vPeakProcessMemoryUsed,
jeliPeakJobMemoryUsed = vPeakJobMemoryUsed}
type ULONGLONG = #type ULONGLONG
data IO_COUNTERS = IO_COUNTERS
{ icReadOperationCount :: ULONGLONG
, icWriteOperationCount :: ULONGLONG
, icOtherOperationCount :: ULONGLONG
, icReadTransferCount :: ULONGLONG
, icWriteTransferCount :: ULONGLONG
, icOtherTransferCount :: ULONGLONG
} deriving Show
instance Storable IO_COUNTERS where
sizeOf = const #size IO_COUNTERS
alignment = const #alignment IO_COUNTERS
poke buf ic = do
(#poke IO_COUNTERS, ReadOperationCount) buf (icReadOperationCount ic)
(#poke IO_COUNTERS, WriteOperationCount) buf (icWriteOperationCount ic)
(#poke IO_COUNTERS, OtherOperationCount) buf (icOtherOperationCount ic)
(#poke IO_COUNTERS, ReadTransferCount) buf (icReadTransferCount ic)
(#poke IO_COUNTERS, WriteTransferCount) buf (icWriteTransferCount ic)
(#poke IO_COUNTERS, OtherTransferCount) buf (icOtherTransferCount ic)
peek buf = do
vReadOperationCount <- (#peek IO_COUNTERS, ReadOperationCount) buf
vWriteOperationCount <- (#peek IO_COUNTERS, WriteOperationCount) buf
vOtherOperationCount <- (#peek IO_COUNTERS, OtherOperationCount) buf
vReadTransferCount <- (#peek IO_COUNTERS, ReadTransferCount) buf
vWriteTransferCount <- (#peek IO_COUNTERS, WriteTransferCount) buf
vOtherTransferCount <- (#peek IO_COUNTERS, OtherTransferCount) buf
return $ IO_COUNTERS {
icReadOperationCount = vReadOperationCount,
icWriteOperationCount = vWriteOperationCount,
icOtherOperationCount = vOtherOperationCount,
icReadTransferCount = vReadTransferCount,
icWriteTransferCount = vWriteTransferCount,
icOtherTransferCount = vOtherTransferCount}
data JOBOBJECT_BASIC_LIMIT_INFORMATION = JOBOBJECT_BASIC_LIMIT_INFORMATION
{ jbliPerProcessUserTimeLimit :: LARGE_INTEGER
, jbliPerJobUserTimeLimit :: LARGE_INTEGER
, jbliLimitFlags :: DWORD
, jbliMinimumWorkingSetSize :: SIZE_T
, jbliMaximumWorkingSetSize :: SIZE_T
, jbliActiveProcessLimit :: DWORD
, jbliAffinity :: ULONG_PTR
, jbliPriorityClass :: DWORD
, jbliSchedulingClass :: DWORD
} deriving Show
instance Storable JOBOBJECT_BASIC_LIMIT_INFORMATION where
sizeOf = const #size JOBOBJECT_BASIC_LIMIT_INFORMATION
alignment = const #alignment JOBOBJECT_BASIC_LIMIT_INFORMATION
poke buf jbli = do
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, PerProcessUserTimeLimit) buf (jbliPerProcessUserTimeLimit jbli)
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, PerJobUserTimeLimit) buf (jbliPerJobUserTimeLimit jbli)
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, LimitFlags) buf (jbliLimitFlags jbli)
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, MinimumWorkingSetSize) buf (jbliMinimumWorkingSetSize jbli)
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, MaximumWorkingSetSize) buf (jbliMaximumWorkingSetSize jbli)
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, ActiveProcessLimit) buf (jbliActiveProcessLimit jbli)
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, Affinity) buf (jbliAffinity jbli)
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, PriorityClass) buf (jbliPriorityClass jbli)
(#poke JOBOBJECT_BASIC_LIMIT_INFORMATION, SchedulingClass) buf (jbliSchedulingClass jbli)
peek buf = do
vPerProcessUserTimeLimit <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, PerProcessUserTimeLimit) buf
vPerJobUserTimeLimit <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, PerJobUserTimeLimit) buf
vLimitFlags <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, LimitFlags) buf
vMinimumWorkingSetSize <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, MinimumWorkingSetSize) buf
vMaximumWorkingSetSize <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, MaximumWorkingSetSize) buf
vActiveProcessLimit <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, ActiveProcessLimit) buf
vAffinity <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, Affinity) buf
vPriorityClass <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, PriorityClass) buf
vSchedulingClass <- (#peek JOBOBJECT_BASIC_LIMIT_INFORMATION, SchedulingClass) buf
return $ JOBOBJECT_BASIC_LIMIT_INFORMATION {
jbliPerProcessUserTimeLimit = vPerProcessUserTimeLimit,
jbliPerJobUserTimeLimit = vPerJobUserTimeLimit,
jbliLimitFlags = vLimitFlags,
jbliMinimumWorkingSetSize = vMinimumWorkingSetSize,
jbliMaximumWorkingSetSize = vMaximumWorkingSetSize,
jbliActiveProcessLimit = vActiveProcessLimit,
jbliAffinity = vAffinity,
jbliPriorityClass = vPriorityClass,
jbliSchedulingClass = vSchedulingClass}
data JOBOBJECT_ASSOCIATE_COMPLETION_PORT = JOBOBJECT_ASSOCIATE_COMPLETION_PORT
{ jacpCompletionKey :: PVOID
, jacpCompletionPort :: HANDLE
} deriving Show
instance Storable JOBOBJECT_ASSOCIATE_COMPLETION_PORT where
sizeOf = const #size JOBOBJECT_ASSOCIATE_COMPLETION_PORT
alignment = const #alignment JOBOBJECT_ASSOCIATE_COMPLETION_PORT
poke buf jacp = do
(#poke JOBOBJECT_ASSOCIATE_COMPLETION_PORT, CompletionKey) buf (jacpCompletionKey jacp)
(#poke JOBOBJECT_ASSOCIATE_COMPLETION_PORT, CompletionPort) buf (jacpCompletionPort jacp)
peek buf = do
vCompletionKey <- (#peek JOBOBJECT_ASSOCIATE_COMPLETION_PORT, CompletionKey) buf
vCompletionPort <- (#peek JOBOBJECT_ASSOCIATE_COMPLETION_PORT, CompletionPort) buf
return $ JOBOBJECT_ASSOCIATE_COMPLETION_PORT {
jacpCompletionKey = vCompletionKey,
jacpCompletionPort = vCompletionPort}
foreign import WINDOWS_CCONV unsafe "windows.h WaitForSingleObject"
waitForSingleObject :: HANDLE -> DWORD -> IO DWORD
type JOBOBJECTINFOCLASS = CInt
type PVOID = Ptr ()
type PULONG_PTR = Ptr ULONG_PTR
jobObjectExtendedLimitInformation :: JOBOBJECTINFOCLASS
jobObjectExtendedLimitInformation = #const JobObjectExtendedLimitInformation
jobObjectAssociateCompletionPortInformation :: JOBOBJECTINFOCLASS
jobObjectAssociateCompletionPortInformation = #const JobObjectAssociateCompletionPortInformation
cJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE :: DWORD
cJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = #const JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
cJOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO :: DWORD
cJOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO = #const JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO
cJOB_OBJECT_MSG_EXIT_PROCESS :: DWORD
cJOB_OBJECT_MSG_EXIT_PROCESS = #const JOB_OBJECT_MSG_EXIT_PROCESS
cJOB_OBJECT_MSG_NEW_PROCESS :: DWORD
cJOB_OBJECT_MSG_NEW_PROCESS = #const JOB_OBJECT_MSG_NEW_PROCESS
cWAIT_ABANDONED :: DWORD
cWAIT_ABANDONED = #const WAIT_ABANDONED
cWAIT_OBJECT_0 :: DWORD
cWAIT_OBJECT_0 = #const WAIT_OBJECT_0
cWAIT_TIMEOUT :: DWORD
cWAIT_TIMEOUT = #const WAIT_TIMEOUT
cCREATE_SUSPENDED :: DWORD
cCREATE_SUSPENDED = #const CREATE_SUSPENDED
cHANDLE_FLAG_INHERIT :: DWORD
cHANDLE_FLAG_INHERIT = #const HANDLE_FLAG_INHERIT
foreign import WINDOWS_CCONV unsafe "windows.h GetExitCodeProcess"
getExitCodeProcess :: HANDLE -> LPDWORD -> IO BOOL
foreign import WINDOWS_CCONV unsafe "windows.h CloseHandle"
closeHandle :: HANDLE -> IO BOOL
foreign import WINDOWS_CCONV unsafe "windows.h TerminateJobObject"
terminateJobObject :: HANDLE -> UINT -> IO BOOL
foreign import WINDOWS_CCONV unsafe "windows.h AssignProcessToJobObject"
assignProcessToJobObject :: HANDLE -> HANDLE -> IO BOOL
foreign import WINDOWS_CCONV unsafe "windows.h CreateJobObjectW"
createJobObjectW :: LPSECURITY_ATTRIBUTES -> LPCTSTR -> IO HANDLE
foreign import WINDOWS_CCONV unsafe "windows.h CreateProcessW"
createProcessW :: LPCTSTR -> LPTSTR
-> LPSECURITY_ATTRIBUTES -> LPSECURITY_ATTRIBUTES
-> BOOL -> DWORD -> LPVOID -> LPCTSTR -> LPSTARTUPINFO
-> LPPROCESS_INFORMATION -> IO BOOL
foreign import ccall unsafe "string.h" memset :: Ptr a -> CInt -> CSize -> IO (Ptr a)
foreign import WINDOWS_CCONV unsafe "windows.h SetInformationJobObject"
setInformationJobObject :: HANDLE -> JOBOBJECTINFOCLASS -> LPVOID -> DWORD -> IO BOOL
foreign import WINDOWS_CCONV unsafe "windows.h CreateIoCompletionPort"
createIoCompletionPort :: HANDLE -> HANDLE -> ULONG_PTR -> DWORD -> IO HANDLE
foreign import WINDOWS_CCONV unsafe "windows.h GetQueuedCompletionStatus"
getQueuedCompletionStatus :: HANDLE -> LPDWORD -> PULONG_PTR -> Ptr LPOVERLAPPED -> DWORD -> IO BOOL
foreign import WINDOWS_CCONV unsafe "windows.h SetHandleInformation"
setHandleInformation :: HANDLE -> DWORD -> DWORD -> IO BOOL
setJobParameters :: HANDLE -> IO BOOL
setJobParameters hJob = alloca $ \p_jeli -> do
let jeliSize = sizeOf (undefined :: JOBOBJECT_EXTENDED_LIMIT_INFORMATION)
_ <- memset p_jeli 0 $ fromIntegral jeliSize
-- Configure all child processes associated with the job to terminate when the
-- last handle to the job is closed. This prevent half dead processes and that
-- hanging ghc-iserv.exe process that happens when you interrupt the testsuite.
(#poke JOBOBJECT_EXTENDED_LIMIT_INFORMATION, BasicLimitInformation.LimitFlags)
p_jeli cJOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
setInformationJobObject hJob jobObjectExtendedLimitInformation
p_jeli (fromIntegral jeliSize)
createCompletionPort :: HANDLE -> IO HANDLE
createCompletionPort hJob = do
ioPort <- createIoCompletionPort iNVALID_HANDLE_VALUE nullPtr 0 1
if ioPort == nullPtr
then do err_code <- getLastError
putStrLn $ "CreateIoCompletionPort error: " ++ show err_code
return nullPtr
else with (JOBOBJECT_ASSOCIATE_COMPLETION_PORT {
jacpCompletionKey = hJob,
jacpCompletionPort = ioPort}) $ \p_Port -> do
res <- setInformationJobObject hJob jobObjectAssociateCompletionPortInformation
(castPtr p_Port) (fromIntegral (sizeOf (undefined :: JOBOBJECT_ASSOCIATE_COMPLETION_PORT)))
if res
then return ioPort
else do err_code <- getLastError
putStrLn $ "SetInformation, error: " ++ show err_code
return nullPtr
waitForJobCompletion :: HANDLE -> HANDLE -> DWORD -> IO BOOL
waitForJobCompletion _hJob ioPort timeout
= alloca $ \p_CompletionCode ->
alloca $ \p_CompletionKey ->
alloca $ \p_Overlapped -> do
-- getQueuedCompletionStatus is a blocking call,
-- it will wake up for each completion event. So if it's
-- not the one we want, sleep again.
let loop :: IO ()
loop = do
res <- getQueuedCompletionStatus ioPort p_CompletionCode p_CompletionKey
p_Overlapped timeout
case res of
False -> return ()
True -> do
completionCode <- peek p_CompletionCode
if completionCode == cJOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO
then return ()
else if completionCode == cJOB_OBJECT_MSG_EXIT_PROCESS
then loop -- Debug point, do nothing for now
else if completionCode == cJOB_OBJECT_MSG_NEW_PROCESS
then loop -- Debug point, do nothing for now
else loop
loop -- Kick it all off
overlapped <- peek p_Overlapped
code <- peek $ p_CompletionCode
return $ if overlapped == nullPtr && code /= cJOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO
then False -- Timeout occurred. *dark voice* YOU HAVE FAILED THIS TEST!.
else True
#endif
|