Merge branch 'nexecProcesses' of https://github.com/cheatfate/Nim into cheatfate-nexecProcesses

This commit is contained in:
Andreas Rumpf
2017-12-01 02:06:51 +01:00
2 changed files with 114 additions and 56 deletions

View File

@@ -167,8 +167,7 @@ proc waitForExit*(p: Process, timeout: int = -1): int {.rtl,
## On posix, if the process has exited because of a signal, 128 + signal
## number will be returned.
proc peekExitCode*(p: Process): int {.tags: [].}
proc peekExitCode*(p: Process): int {.rtl, extern: "nosp$1", tags: [].}
## return -1 if the process is still running. Otherwise the process' exit code
##
## On posix, if the process has exited because of a signal, 128 + signal
@@ -231,55 +230,79 @@ proc execProcesses*(cmds: openArray[string],
## executes the commands `cmds` in parallel. Creates `n` processes
## that execute in parallel. The highest return value of all processes
## is returned. Runs `beforeRunEvent` before running each command.
when false:
# poParentStreams causes problems on Posix, so we simply disable it:
var options = options - {poParentStreams}
assert n > 0
if n > 1:
var q: seq[Process]
newSeq(q, n)
var i = 0
var q = newSeq[Process](n)
var m = min(n, cmds.len)
for i in 0..m-1:
when defined(windows):
var w: WOHandleArray
var wcount = m
while i < m:
if beforeRunEvent != nil:
beforeRunEvent(i)
q[i] = startProcess(cmds[i], options=options + {poEvalCommand})
when defined(noBusyWaiting):
var r = 0
for i in m..high(cmds):
when defined(debugExecProcesses):
var err = ""
var outp = outputStream(q[r])
while running(q[r]) or not atEnd(outp):
err.add(outp.readLine())
err.add("\n")
echo(err)
result = max(waitForExit(q[r]), result)
if afterRunEvent != nil: afterRunEvent(r, q[r])
if q[r] != nil: close(q[r])
if beforeRunEvent != nil:
beforeRunEvent(i)
q[r] = startProcess(cmds[i], options=options + {poEvalCommand})
r = (r + 1) mod n
else:
var i = m
while i <= high(cmds):
sleep(50)
for r in 0..n-1:
q[i] = startProcess(cmds[i], options = options + {poEvalCommand})
when defined(windows):
w[i] = q[i].fProcessHandle
inc(i)
var ecount = len(cmds)
while ecount > 0:
when defined(windows):
# waiting for all children, get result if any child exits
var ret = waitForMultipleObjects(int32(wcount), addr(w), 0'i32,
INFINITE)
if ret == WAIT_TIMEOUT:
# must not be happen
discard
elif ret == WAIT_FAILED:
raiseOSError(osLastError())
else:
var status : cint = 1
# waiting for all children, get result if any child exits
let res = waitpid(-1, status, 0)
if res > 0:
for r in 0..m-1:
if not isNil(q[r]) and q[r].id == res:
# we updating `exitStatus` manually, so `running()` can work.
if WIFEXITED(status) or WIFSIGNALED(status):
q[r].exitStatus = status
break
else:
let err = osLastError()
if err == OSErrorCode(ECHILD):
# some child exits, we need to check our childs exit codes
discard
elif err == OSErrorCode(EINTR):
# signal interrupted our syscall, lets repeat it
continue
else:
# all other errors are exceptions
raiseOSError(err)
for r in 0..m-1:
if not isNil(q[r]):
if not running(q[r]):
#echo(outputStream(q[r]).readLine())
result = max(waitForExit(q[r]), result)
result = max(result, q[r].peekExitCode())
if afterRunEvent != nil: afterRunEvent(r, q[r])
if q[r] != nil: close(q[r])
if beforeRunEvent != nil:
beforeRunEvent(i)
q[r] = startProcess(cmds[i], options=options + {poEvalCommand})
inc(i)
if i > high(cmds): break
for j in 0..m-1:
result = max(waitForExit(q[j]), result)
if afterRunEvent != nil: afterRunEvent(j, q[j])
if q[j] != nil: close(q[j])
close(q[r])
if i < len(cmds):
if beforeRunEvent != nil: beforeRunEvent(i)
q[r] = startProcess(cmds[i],
options = options + {poEvalCommand})
when defined(windows):
w[r] = q[r].fProcessHandle
inc(i)
else:
q[r] = nil
when defined(windows):
for c in r..MAXIMUM_WAIT_OBJECTS - 2:
w[c] = w[c + 1]
dec(wcount)
dec(ecount)
else:
for i in 0..high(cmds):
if beforeRunEvent != nil:
@@ -944,19 +967,22 @@ elif not defined(useNimRtl):
if kill(p.id, SIGCONT) != 0'i32: raiseOsError(osLastError())
proc running(p: Process): bool =
var ret : int
var status : cint = 1
ret = waitpid(p.id, status, WNOHANG)
if ret == int(p.id):
if isExitStatus(status):
p.exitStatus = status
return false
else:
return true
elif ret == 0:
return true # Can't establish status. Assume running.
else:
if p.exitStatus != -3:
return false
else:
var ret : int
var status : cint = 1
ret = waitpid(p.id, status, WNOHANG)
if ret == int(p.id):
if isExitStatus(status):
p.exitStatus = status
return false
else:
return true
elif ret == 0:
return true # Can't establish status. Assume running.
else:
raiseOSError(osLastError())
proc terminate(p: Process) =
if kill(p.id, SIGTERM) != 0'i32:

32
tests/osproc/texecps.nim Normal file
View File

@@ -0,0 +1,32 @@
discard """
file: "texecps.nim"
output: ""
"""
import osproc, streams, strutils, os
const NumberOfProcesses = 13
var gResults {.threadvar.}: seq[string]
proc execCb(idx: int, p: Process) =
let exitCode = p.peekExitCode
if exitCode < len(gResults):
gResults[exitCode] = p.outputStream.readAll.strip
when isMainModule:
if paramCount() == 0:
gResults = newSeq[string](NumberOfProcesses)
var checks = newSeq[string](NumberOfProcesses)
var commands = newSeq[string](NumberOfProcesses)
for i in 0..len(commands) - 1:
commands[i] = getAppFileName() & " " & $i
checks[i] = $i
let cres = execProcesses(commands, options = {poStdErrToStdOut},
afterRunEvent = execCb)
doAssert(cres == len(commands) - 1)
doAssert(gResults == checks)
else:
echo paramStr(1)
programResult = parseInt(paramStr(1))