Merge branch 'cheatfate-nosproc3' into devel

This commit is contained in:
Araq 2017-12-13 02:50:26 +01:00
commit d550417f8b
2 changed files with 107 additions and 61 deletions

View file

@ -47,6 +47,7 @@ type
ProcessObj = object of RootObj ProcessObj = object of RootObj
when defined(windows): when defined(windows):
fProcessHandle: Handle fProcessHandle: Handle
fThreadHandle: Handle
inHandle, outHandle, errHandle: FileHandle inHandle, outHandle, errHandle: FileHandle
id: Handle id: Handle
else: else:
@ -54,6 +55,7 @@ type
inStream, outStream, errStream: Stream inStream, outStream, errStream: Stream
id: Pid id: Pid
exitStatus: cint exitStatus: cint
exitFlag: bool
options: set[ProcessOption] options: set[ProcessOption]
Process* = ref ProcessObj ## represents an operating system process Process* = ref ProcessObj ## represents an operating system process
@ -237,11 +239,13 @@ proc execProcesses*(cmds: openArray[string],
if n > 1: if n > 1:
var i = 0 var i = 0
var q = newSeq[Process](n) var q = newSeq[Process](n)
var m = min(n, cmds.len)
when defined(windows): when defined(windows):
var w: WOHandleArray var w: WOHandleArray
var m = min(min(n, MAXIMUM_WAIT_OBJECTS), cmds.len)
var wcount = m var wcount = m
else:
var m = min(n, cmds.len)
while i < m: while i < m:
if beforeRunEvent != nil: if beforeRunEvent != nil:
@ -253,6 +257,7 @@ proc execProcesses*(cmds: openArray[string],
var ecount = len(cmds) var ecount = len(cmds)
while ecount > 0: while ecount > 0:
var rexit = -1
when defined(windows): when defined(windows):
# waiting for all children, get result if any child exits # waiting for all children, get result if any child exits
var ret = waitForMultipleObjects(int32(wcount), addr(w), 0'i32, var ret = waitForMultipleObjects(int32(wcount), addr(w), 0'i32,
@ -263,21 +268,36 @@ proc execProcesses*(cmds: openArray[string],
elif ret == WAIT_FAILED: elif ret == WAIT_FAILED:
raiseOSError(osLastError()) raiseOSError(osLastError())
else: else:
var status : cint = 1 var status: int32
for r in 0..m-1:
if not isNil(q[r]) and q[r].fProcessHandle == w[ret]:
discard getExitCodeProcess(q[r].fProcessHandle, status)
q[r].exitFlag = true
q[r].exitStatus = status
rexit = r
break
else:
var status: cint = 1
# waiting for all children, get result if any child exits # waiting for all children, get result if any child exits
let res = waitpid(-1, status, 0) let res = waitpid(-1, status, 0)
if res > 0: if res > 0:
for r in 0..m-1: for r in 0..m-1:
if not isNil(q[r]) and q[r].id == res: if not isNil(q[r]) and q[r].id == res:
# we updating `exitStatus` manually, so `running()` can work.
if WIFEXITED(status) or WIFSIGNALED(status): if WIFEXITED(status) or WIFSIGNALED(status):
q[r].exitFlag = true
q[r].exitStatus = status q[r].exitStatus = status
rexit = r
break break
else: else:
let err = osLastError() let err = osLastError()
if err == OSErrorCode(ECHILD): if err == OSErrorCode(ECHILD):
# some child exits, we need to check our childs exit codes # some child exits, we need to check our childs exit codes
discard for r in 0..m-1:
if (not isNil(q[r])) and (not running(q[r])):
q[r].exitFlag = true
q[r].exitStatus = status
rexit = r
break
elif err == OSErrorCode(EINTR): elif err == OSErrorCode(EINTR):
# signal interrupted our syscall, lets repeat it # signal interrupted our syscall, lets repeat it
continue continue
@ -285,25 +305,26 @@ proc execProcesses*(cmds: openArray[string],
# all other errors are exceptions # all other errors are exceptions
raiseOSError(err) raiseOSError(err)
for r in 0..m-1: if rexit >= 0:
if not isNil(q[r]): result = max(result, q[rexit].peekExitCode())
if not running(q[r]): if afterRunEvent != nil: afterRunEvent(rexit, q[rexit])
result = max(result, q[r].peekExitCode()) close(q[rexit])
if afterRunEvent != nil: afterRunEvent(r, q[r])
close(q[r])
if i < len(cmds): if i < len(cmds):
if beforeRunEvent != nil: beforeRunEvent(i) if beforeRunEvent != nil: beforeRunEvent(i)
q[r] = startProcess(cmds[i], q[rexit] = startProcess(cmds[i],
options = options + {poEvalCommand}) options = options + {poEvalCommand})
when defined(windows): when defined(windows):
w[r] = q[r].fProcessHandle w[rexit] = q[rexit].fProcessHandle
inc(i) inc(i)
else: else:
q[r] = nil
when defined(windows): when defined(windows):
for c in r..MAXIMUM_WAIT_OBJECTS - 2: for k in 0..wcount - 1:
w[c] = w[c + 1] if w[k] == q[rexit].fProcessHandle:
w[k] = w[wcount - 1]
w[wcount - 1] = 0
dec(wcount) dec(wcount)
break
q[rexit] = nil
dec(ecount) dec(ecount)
else: else:
for i in 0..high(cmds): for i in 0..high(cmds):
@ -491,6 +512,7 @@ when defined(Windows) and not defined(useNimRtl):
hi, ho, he: Handle hi, ho, he: Handle
new(result) new(result)
result.options = options result.options = options
result.exitFlag = true
si.cb = sizeof(si).cint si.cb = sizeof(si).cint
if poParentStreams notin options: if poParentStreams notin options:
si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or si.dwFlags = STARTF_USESTDHANDLES # STARTF_USESHOWWINDOW or
@ -559,27 +581,30 @@ when defined(Windows) and not defined(useNimRtl):
"Requested command not found: '$1'. OS error:" % command) "Requested command not found: '$1'. OS error:" % command)
else: else:
raiseOSError(lastError, command) raiseOSError(lastError, command)
# Close the handle now so anyone waiting is woken:
discard closeHandle(procInfo.hThread)
result.fProcessHandle = procInfo.hProcess result.fProcessHandle = procInfo.hProcess
result.fThreadHandle = procInfo.hThread
result.id = procInfo.dwProcessId result.id = procInfo.dwProcessId
result.exitFlag = false
proc close(p: Process) = proc close(p: Process) =
if poInteractive in p.options: if poParentStreams notin p.options:
# somehow this is not always required on Windows:
discard closeHandle(p.inHandle) discard closeHandle(p.inHandle)
discard closeHandle(p.outHandle) discard closeHandle(p.outHandle)
discard closeHandle(p.errHandle) discard closeHandle(p.errHandle)
#discard closeHandle(p.FProcessHandle) discard closeHandle(p.fThreadHandle)
discard closeHandle(p.fProcessHandle)
proc suspend(p: Process) = proc suspend(p: Process) =
discard suspendThread(p.fProcessHandle) discard suspendThread(p.fThreadHandle)
proc resume(p: Process) = proc resume(p: Process) =
discard resumeThread(p.fProcessHandle) discard resumeThread(p.fThreadHandle)
proc running(p: Process): bool = proc running(p: Process): bool =
var x = waitForSingleObject(p.fProcessHandle, 50) if p.exitFlag:
return false
else:
var x = waitForSingleObject(p.fProcessHandle, 0)
return x == WAIT_TIMEOUT return x == WAIT_TIMEOUT
proc terminate(p: Process) = proc terminate(p: Process) =
@ -590,22 +615,37 @@ when defined(Windows) and not defined(useNimRtl):
terminate(p) terminate(p)
proc waitForExit(p: Process, timeout: int = -1): int = proc waitForExit(p: Process, timeout: int = -1): int =
discard waitForSingleObject(p.fProcessHandle, timeout.int32) if p.exitFlag:
return p.exitStatus
var res: int32 let res = waitForSingleObject(p.fProcessHandle, timeout.int32)
discard getExitCodeProcess(p.fProcessHandle, res) if res == WAIT_TIMEOUT:
result = res terminate(p)
p.exitStatus = res var status: int32
discard getExitCodeProcess(p.fProcessHandle, status)
if status != STILL_ACTIVE:
p.exitFlag = true
p.exitStatus = status
discard closeHandle(p.fThreadHandle)
discard closeHandle(p.fProcessHandle) discard closeHandle(p.fProcessHandle)
result = status
else:
result = -1
proc peekExitCode(p: Process): int = proc peekExitCode(p: Process): int =
var b = waitForSingleObject(p.fProcessHandle, 50) == WAIT_TIMEOUT if p.exitFlag:
if b: result = -1 return p.exitStatus
else:
var res: int32 result = -1
discard getExitCodeProcess(p.fProcessHandle, res) var b = waitForSingleObject(p.fProcessHandle, 0) == WAIT_TIMEOUT
if res == 0: return p.exitStatus if not b:
return res var status: int32
discard getExitCodeProcess(p.fProcessHandle, status)
p.exitFlag = true
p.exitStatus = status
discard closeHandle(p.fThreadHandle)
discard closeHandle(p.fProcessHandle)
result = status
proc inputStream(p: Process): Stream = proc inputStream(p: Process): Stream =
streamAccess(p) streamAccess(p)
@ -737,7 +777,8 @@ elif not defined(useNimRtl):
pStdin, pStdout, pStderr: array[0..1, cint] pStdin, pStdout, pStderr: array[0..1, cint]
new(result) new(result)
result.options = options result.options = options
result.exitStatus = -3 # for ``waitForExit`` result.exitFlag = true
if poParentStreams notin options: if poParentStreams notin options:
if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or if pipe(pStdin) != 0'i32 or pipe(pStdout) != 0'i32 or
pipe(pStderr) != 0'i32: pipe(pStderr) != 0'i32:
@ -792,6 +833,7 @@ elif not defined(useNimRtl):
if poEchoCmd in options: if poEchoCmd in options:
echo(command, " ", join(args, " ")) echo(command, " ", join(args, " "))
result.id = pid result.id = pid
result.exitFlag = false
if poParentStreams in options: if poParentStreams in options:
# does not make much sense, but better than nothing: # does not make much sense, but better than nothing:
@ -968,14 +1010,14 @@ elif not defined(useNimRtl):
if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError()) if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError())
proc running(p: Process): bool = proc running(p: Process): bool =
if p.exitStatus != -3: if p.exitFlag:
return false return false
else: else:
var ret : int var status: cint = 1
var status : cint = 1 let ret = waitpid(p.id, status, WNOHANG)
ret = waitpid(p.id, status, WNOHANG)
if ret == int(p.id): if ret == int(p.id):
if isExitStatus(status): if isExitStatus(status):
p.exitFlag = true
p.exitStatus = status p.exitStatus = status
return false return false
else: else:
@ -998,13 +1040,14 @@ elif not defined(useNimRtl):
import kqueue, times import kqueue, times
proc waitForExit(p: Process, timeout: int = -1): int = proc waitForExit(p: Process, timeout: int = -1): int =
if p.exitStatus != -3: if p.exitFlag:
return exitStatus(p.exitStatus) return exitStatus(p.exitStatus)
if timeout == -1: if timeout == -1:
var status : cint = 1 var status: cint = 1
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError()) raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status p.exitStatus = status
else: else:
var kqFD = kqueue() var kqFD = kqueue()
@ -1025,7 +1068,7 @@ elif not defined(useNimRtl):
try: try:
while true: while true:
var status : cint = 1 var status: cint = 1
var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1, var count = kevent(kqFD, addr(kevIn), 1, addr(kevOut), 1,
addr(tmspec)) addr(tmspec))
if count < 0: if count < 0:
@ -1038,12 +1081,14 @@ elif not defined(useNimRtl):
raiseOSError(osLastError()) raiseOSError(osLastError())
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError()) raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status p.exitStatus = status
break break
else: else:
if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC: if kevOut.ident == p.id.uint and kevOut.filter == EVFILT_PROC:
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError()) raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status p.exitStatus = status
break break
else: else:
@ -1083,17 +1128,14 @@ elif not defined(useNimRtl):
s.tv_sec = b.tv_sec s.tv_sec = b.tv_sec
s.tv_nsec = b.tv_nsec s.tv_nsec = b.tv_nsec
#if waitPid(p.id, p.exitStatus, 0) == int(p.id): if p.exitFlag:
# ``waitPid`` fails if the process is not running anymore. But then
# ``running`` probably set ``p.exitStatus`` for us. Since ``p.exitStatus`` is
# initialized with -3, wrong success exit codes are prevented.
if p.exitStatus != -3:
return exitStatus(p.exitStatus) return exitStatus(p.exitStatus)
if timeout == -1: if timeout == -1:
var status : cint = 1 var status: cint = 1
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError()) raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status p.exitStatus = status
else: else:
var nmask, omask: Sigset var nmask, omask: Sigset
@ -1125,9 +1167,10 @@ elif not defined(useNimRtl):
let res = sigtimedwait(nmask, sinfo, tmspec) let res = sigtimedwait(nmask, sinfo, tmspec)
if res == SIGCHLD: if res == SIGCHLD:
if sinfo.si_pid == p.id: if sinfo.si_pid == p.id:
var status : cint = 1 var status: cint = 1
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError()) raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status p.exitStatus = status
break break
else: else:
@ -1148,9 +1191,10 @@ elif not defined(useNimRtl):
# timeout expired, so we trying to kill process # timeout expired, so we trying to kill process
if posix.kill(p.id, SIGKILL) == -1: if posix.kill(p.id, SIGKILL) == -1:
raiseOSError(osLastError()) raiseOSError(osLastError())
var status : cint = 1 var status: cint = 1
if waitpid(p.id, status, 0) < 0: if waitpid(p.id, status, 0) < 0:
raiseOSError(osLastError()) raiseOSError(osLastError())
p.exitFlag = true
p.exitStatus = status p.exitStatus = status
break break
else: else:
@ -1168,12 +1212,13 @@ elif not defined(useNimRtl):
proc peekExitCode(p: Process): int = proc peekExitCode(p: Process): int =
var status = cint(0) var status = cint(0)
result = -1 result = -1
if p.exitStatus != -3: if p.exitFlag:
return exitStatus(p.exitStatus) return exitStatus(p.exitStatus)
var ret = waitpid(p.id, status, WNOHANG) var ret = waitpid(p.id, status, WNOHANG)
if ret > 0: if ret > 0:
if isExitStatus(status): if isExitStatus(status):
p.exitFlag = true
p.exitStatus = status p.exitStatus = status
result = exitStatus(status) result = exitStatus(status)

View file

@ -111,6 +111,7 @@ const
WAIT_TIMEOUT* = 0x00000102'i32 WAIT_TIMEOUT* = 0x00000102'i32
WAIT_FAILED* = 0xFFFFFFFF'i32 WAIT_FAILED* = 0xFFFFFFFF'i32
INFINITE* = -1'i32 INFINITE* = -1'i32
STILL_ACTIVE* = 0x00000103'i32
STD_INPUT_HANDLE* = -10'i32 STD_INPUT_HANDLE* = -10'i32
STD_OUTPUT_HANDLE* = -11'i32 STD_OUTPUT_HANDLE* = -11'i32