osproc: use clone with CLONE_VM on Linux for faster process spawning

This commit is contained in:
Michał Zieliński 2014-02-14 15:08:02 +01:00
commit cd2bd7fa7b
2 changed files with 146 additions and 72 deletions

View file

@ -20,6 +20,11 @@ when defined(windows):
else:
import posix
when defined(linux):
import linux
when not defined(useFork):
const useClone = true
type
TProcess = object of TObject
when defined(windows):
@ -590,6 +595,19 @@ elif not defined(useNimRtl):
copyMem(result[i], addr(x[0]), x.len+1)
inc(i)
type TStartProcessData = object
sysCommand: cstring
sysArgs: cstringArray
sysEnv: cstringArray
workingDir: cstring
pStdin, pStdout, pStderr: array[0..1, cint]
optionPoUsePath: bool
optionPoParentStreams: bool
optionPoStdErrToStdOut: bool
proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].}
proc startProcessAfterFork(data: ptr TStartProcessData) {.tags: [FExecIO, FReadEnv], cdecl.}
proc startProcess(command: string,
workingDir: string = "",
args: openArray[string] = [],
@ -616,88 +634,48 @@ elif not defined(useNimRtl):
for arg in args.items:
sysArgsRaw.add arg
var pid: TPid
var sysArgs = allocCStringArray(sysArgsRaw)
finally: deallocCStringArray(sysArgs)
var pid: TPid
when defined(posix_spawn) and not defined(useFork):
var attr: Tposix_spawnattr
var fops: Tposix_spawn_file_actions
template chck(e: expr) =
if e != 0'i32: osError(osLastError())
chck posix_spawn_file_actions_init(fops)
chck posix_spawnattr_init(attr)
var mask: Tsigset
chck sigemptyset(mask)
chck posix_spawnattr_setsigmask(attr, mask)
chck posix_spawnattr_setpgroup(attr, 0'i32)
chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or
POSIX_SPAWN_SETSIGMASK or
POSIX_SPAWN_SETPGROUP)
if poParentStreams notin options:
chck posix_spawn_file_actions_addclose(fops, pStdin[writeIdx])
chck posix_spawn_file_actions_adddup2(fops, pStdin[readIdx], readIdx)
chck posix_spawn_file_actions_addclose(fops, pStdout[readIdx])
chck posix_spawn_file_actions_adddup2(fops, pStdout[writeIdx], writeIdx)
chck posix_spawn_file_actions_addclose(fops, pStderr[readIdx])
if poStdErrToStdOut in options:
chck posix_spawn_file_actions_adddup2(fops, pStdout[writeIdx], 2)
else:
chck posix_spawn_file_actions_adddup2(fops, p_stderr[writeIdx], 2)
var sysEnv = if env == nil: envToCStringArray() else: envToCStringArray(env)
var res: cint
# FIXME: chdir is global to process
if workingDir.len > 0: os.setCurrentDir(workingDir)
if poUsePath in options:
res = posix_spawnp(pid, sysCommand, fops, attr, sysArgs, sysEnv)
var sysEnv = if env == nil:
envToCStringArray()
else:
res = posix_spawn(pid, sysCommand, fops, attr, sysArgs, sysEnv)
deallocCStringArray(sysEnv)
discard posix_spawn_file_actions_destroy(fops)
discard posix_spawnattr_destroy(attr)
chck res
envToCStringArray(env)
finally: deallocCStringArray(sysEnv)
var data: TStartProcessData
data.sysCommand = sysCommand
data.sysArgs = sysArgs
data.sysEnv = sysEnv
data.pStdin = pStdin
data.pStdout = pStdout
data.pStderr = pStderr
data.optionPoParentStreams = poParentStreams in options
data.optionPoUsePath = poUsePath in options
data.optionPoStdErrToStdOut = poStdErrToStdOut in options
data.workingDir = workingDir
when defined(useClone):
const stackSize = 8096
let stackEnd = cast[clong](alloc(stackSize))
let stack = cast[pointer](stackEnd + stackSize)
let fn: pointer = startProcessAfterFork
pid = clone(fn, stack,
cint(CLONE_VM or CLONE_VFORK or SIGCHLD),
pointer(addr data), nil, nil, nil)
if pid < 0: osError(osLastError())
elif defined(posix_spawn) and not defined(useFork):
pid = startProcessAuxSpawn(data)
else:
pid = fork()
if pid < 0: osError(osLastError())
if pid == 0:
## child process:
startProcessAfterFork(addr(data))
if poParentStreams notin options:
discard close(p_stdin[writeIdx])
if dup2(p_stdin[readIdx], readIdx) < 0: osError(osLastError())
discard close(p_stdout[readIdx])
if dup2(p_stdout[writeIdx], writeIdx) < 0: osError(osLastError())
discard close(p_stderr[readIdx])
if poStdErrToStdOut in options:
if dup2(p_stdout[writeIdx], 2) < 0: osError(osLastError())
else:
if dup2(p_stderr[writeIdx], 2) < 0: osError(osLastError())
# Create a new process group
if setpgid(0, 0) == -1: quit("setpgid call failed: " & $strerror(errno))
if workingDir.len > 0: os.setCurrentDir(workingDir)
if env == nil:
if poUsePath in options:
discard execvp(sysCommand, sysArgs)
else:
discard execv(sysCommand, sysArgs)
else:
var cEnv = envToCStringArray(env)
if poUsePath in options:
discard execvpe(sysCommand, sysArgs, cEnv)
else:
discard execve(sysCommand, sysArgs, cEnv)
# too risky to raise an exception here:
quit("execve call failed: " & $strerror(errno))
# Parent process. Copy process information.
if poEchoCmd in options:
echo(command, " ", join(args, " "))
@ -723,6 +701,77 @@ elif not defined(useNimRtl):
discard close(pStdin[readIdx])
discard close(pStdout[writeIdx])
proc startProcessAuxSpawn(data: TStartProcessData): TPid =
var attr: Tposix_spawnattr
var fops: Tposix_spawn_file_actions
template chck(e: expr) =
if e != 0'i32: osError(osLastError())
chck posix_spawn_file_actions_init(fops)
chck posix_spawnattr_init(attr)
var mask: Tsigset
chck sigemptyset(mask)
chck posix_spawnattr_setsigmask(attr, mask)
chck posix_spawnattr_setpgroup(attr, 0'i32)
chck posix_spawnattr_setflags(attr, POSIX_SPAWN_USEVFORK or
POSIX_SPAWN_SETSIGMASK or
POSIX_SPAWN_SETPGROUP)
if not data.optionPoParentStreams:
chck posix_spawn_file_actions_addclose(fops, data.pStdin[writeIdx])
chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx)
chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx])
chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx)
chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx])
if data.optionPoStdErrToStdOut:
chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2)
else:
chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2)
var res: cint
# FIXME: chdir is global to process
if data.workingDir.len > 0:
setCurrentDir($data.workingDir)
var pid: TPid
if data.optionPoUsePath:
res = posix_spawnp(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
else:
res = posix_spawn(pid, data.sysCommand, fops, attr, data.sysArgs, data.sysEnv)
discard posix_spawn_file_actions_destroy(fops)
discard posix_spawnattr_destroy(attr)
chck res
return pid
proc startProcessAfterFork(data: ptr TStartProcessData) =
# Warning: no GC here!
if not data.optionPoParentStreams:
discard close(data.pStdin[writeIdx])
if dup2(data.pStdin[readIdx], readIdx) < 0: osError(osLastError())
discard close(data.pStdout[readIdx])
if dup2(data.pStdout[writeIdx], writeIdx) < 0: osError(osLastError())
discard close(data.pStderr[readIdx])
if data.optionPoStdErrToStdOut:
if dup2(data.pStdout[writeIdx], 2) < 0: osError(osLastError())
else:
if dup2(data.pStderr[writeIdx], 2) < 0: osError(osLastError())
if data.workingDir.len > 0:
if chdir(data.workingDir) < 0:
quit("chdir failed")
if data.optionPoUsePath:
discard execvpe(data.sysCommand, data.sysArgs, data.sysEnv)
else:
discard execve(data.sysCommand, data.sysArgs, data.sysEnv)
# too risky to raise an exception here:
quit("execve call failed: " & $strerror(errno))
proc close(p: PProcess) =
if p.inStream != nil: close(p.inStream)
if p.outStream != nil: close(p.outStream)