diff options
author | Michał Zieliński <michal@zielinscy.org.pl> | 2014-02-14 15:08:02 +0100 |
---|---|---|
committer | Michał Zieliński <michal@zielinscy.org.pl> | 2014-02-14 15:50:28 +0100 |
commit | cd2bd7fa7b1048956c72ad7665b70b2eabecd549 (patch) | |
tree | 021ee3010af6c043d08d44277ff414df79f29cab /lib | |
parent | 87abc22cc36d094ece8900d069cdc2dbca094e8b (diff) | |
download | Nim-cd2bd7fa7b1048956c72ad7665b70b2eabecd549.tar.gz |
osproc: use clone with CLONE_VM on Linux for faster process spawning
Diffstat (limited to 'lib')
-rw-r--r-- | lib/posix/linux.nim | 25 | ||||
-rw-r--r-- | lib/pure/osproc.nim | 195 |
2 files changed, 147 insertions, 73 deletions
diff --git a/lib/posix/linux.nim b/lib/posix/linux.nim new file mode 100644 index 000000000..1ed1af3b6 --- /dev/null +++ b/lib/posix/linux.nim @@ -0,0 +1,25 @@ +import posix + +const + CSIGNAL* = 0x000000FF + CLONE_VM* = 0x00000100 + CLONE_FS* = 0x00000200 + CLONE_FILES* = 0x00000400 + CLONE_SIGHAND* = 0x00000800 + CLONE_PTRACE* = 0x00002000 + CLONE_VFORK* = 0x00004000 + CLONE_PARENT* = 0x00008000 + CLONE_THREAD* = 0x00010000 + CLONE_NEWNS* = 0x00020000 + CLONE_SYSVSEM* = 0x00040000 + CLONE_SETTLS* = 0x00080000 + CLONE_PARENT_SETTID* = 0x00100000 + CLONE_CHILD_CLEARTID* = 0x00200000 + CLONE_DETACHED* = 0x00400000 + CLONE_UNTRACED* = 0x00800000 + CLONE_CHILD_SETTID* = 0x01000000 + CLONE_STOPPED* = 0x02000000 + +# fn should be of type proc (a2: pointer): void {.cdecl.} +proc clone*(fn: pointer; child_stack: pointer; flags: cint; + arg: pointer; ptid: ptr TPid; tls: pointer; ctid: ptr TPid): cint {.importc, header: "<sched.h>".} diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index c0c7f46d7..40877f638 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -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: - - 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)) + startProcessAfterFork(addr(data)) + # 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) |