From dfeb6e527228d053d5c6cf9767a4f5111e51bee1 Mon Sep 17 00:00:00 2001 From: Sam May Date: Tue, 31 Oct 2023 15:10:49 -0700 Subject: [PATCH 1/5] Add file streams for teeing Job stdout/err to disc --- share/wake/lib/system/job.wake | 30 ++++++--- share/wake/lib/system/path.wake | 6 +- share/wake/lib/system/plan_scorer.wake | 10 +-- src/runtime/job.cpp | 84 +++++++++++++++++++++----- 4 files changed, 98 insertions(+), 32 deletions(-) diff --git a/share/wake/lib/system/job.wake b/share/wake/lib/system/job.wake index 075dc56b0..dda055545 100644 --- a/share/wake/lib/system/job.wake +++ b/share/wake/lib/system/job.wake @@ -112,8 +112,12 @@ export tuple Plan = export Stdin: String # How should standard output be displayed during a build export Stdout: LogLevel + # The workspace-relative paths to which the stdout will be appended + export TeeStdout: List String # How should standard error be displayed during a build export Stderr: LogLevel + # The workspace-relative paths to which the stderr will be appended + export TeeStderr: List String # Echo the command to this stream export Echo: LogLevel # See Persistence table above @@ -227,7 +231,7 @@ export def editPlanShare (f: Boolean => Boolean): Plan => Plan = # Get a unique hash-code for the job export def getPlanHash (plan: Plan): Integer = - def Plan _ cmd _ env dir stdin _ _ _ _ _ _ _ _ isAtty = plan + def Plan _ cmd _ env dir stdin _ _ _ _ _ _ _ _ _ _ isAtty = plan def isAttyStr = if isAtty then "true" else "false" def sig = @@ -275,7 +279,7 @@ def bToInt b = # Set reasonable defaults for all Plan arguments export def makeExecPlan (cmd: List String) (visible: List Path): Plan = - Plan "" cmd visible environment "." "" logInfo logWarning logEcho Share Nil defaultUsage id id False + Plan "" cmd visible environment "." "" logInfo Nil logWarning Nil logEcho Share Nil defaultUsage id id False export def makeShellPlan (script: String) (visible: List Path): Plan = makeExecPlan (which "dash", "-c", script, Nil) visible @@ -571,8 +575,8 @@ export def virtualRunner: Runner = def implode l = cat (foldr (_, "\0", _) Nil l) -def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo stdout stderr label isatty: Job = - def create label dir stdin env cmd signature visible keep echo stdout stderr isatty = +def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo stdout teeout stderr teeerr label isatty: Job = + def create label dir stdin env cmd signature visible keep echo stdout teeoutFlat stderr teeerrFlat isatty = prim "job_create" def finish job inputs outputs all_outputs status runtime cputime membytes ibytes obytes = @@ -582,6 +586,8 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo st def cache dir stdin env cmd signature visible isatty = prim "job_cache" def signature cmd res fni fno keep = prim "hash" def hash = signature cmd res finputs foutputs keep + def teeoutFlat = catWith "\0" teeout + def teeerrFlat = catWith "\0" teeerr def build Unit = def visStrings = map getPathName vis @@ -598,7 +604,9 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo st (bToInt keep) echo stdout + teeoutFlat stderr + teeerrFlat (bToInt isatty) def prefix = str (getJobId job) @@ -655,10 +663,10 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo st Pair Nil last -> confirm False last (build Unit) # Only run if the first four arguments differ -target runOnce cmd env dir stdin vis isatty run \ res usage finputs foutputs keep echo stdout stderr label = - runAlways cmd env dir stdin res usage finputs foutputs vis keep run echo stdout stderr label isatty +target runOnce cmd env dir stdin vis isatty run \ res usage finputs foutputs keep echo stdout teeout stderr teeerr label = + runAlways cmd env dir stdin res usage finputs foutputs vis keep run echo stdout teeout stderr teeerr label isatty -export def runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run (LogLevel echo) (LogLevel stdout) (LogLevel stderr) isatty = +export def runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run (LogLevel echo) (LogLevel stdout) teeout (LogLevel stderr) teeerr isatty = if isOnce pers then runOnce cmd @@ -675,7 +683,9 @@ export def runJobImp label cmd env dir stdin res usage finputs foutputs vis pers (isKeep pers) echo stdout + teeout stderr + teeerr label else runAlways @@ -692,12 +702,14 @@ export def runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run echo stdout + teeout stderr + teeerr label isatty -export def runJobWith (Runner _ _ run) (Plan label cmd vis env dir stdin stdout stderr echo pers res usage finputs foutputs isatty) = - runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run echo stdout stderr isatty +export def runJobWith (Runner _ _ run) (Plan label cmd vis env dir stdin stdout teeout stderr teeerr echo pers res usage finputs foutputs isatty) = + runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run echo stdout teeout stderr teeerr isatty # Set the value of a tag on a Job # This is useful for post-build reflection into the database diff --git a/share/wake/lib/system/path.wake b/share/wake/lib/system/path.wake index 66697dfb0..b2a1bc844 100644 --- a/share/wake/lib/system/path.wake +++ b/share/wake/lib/system/path.wake @@ -179,7 +179,7 @@ def computeHashes (prefix: String) (files: List String): List String = def add f h = prim "add_hash" def hashPlan cmd vis = - Plan "" cmd vis Nil "." "" logNever logError logDebug ReRun Nil hashUsage id id False + Plan "" cmd vis Nil "." "" logNever Nil logError Nil logDebug ReRun Nil hashUsage id id False def stdin_file_path = "to_hash.{prefix}.stdin" @@ -240,7 +240,7 @@ target hashcode (f: String): String = reuse else def hashPlan cmd = - Plan "" cmd Nil Nil "." "" logNever logError logDebug ReRun Nil hashUsage id id False + Plan "" cmd Nil Nil "." "" logNever Nil logError Nil logDebug ReRun Nil hashUsage id id False def job = hashPlan ("", f, Nil) @@ -259,7 +259,7 @@ target hashcode (f: String): String = # Allow an untracked file to be removed via `wake --clean` export target markFileCleanable (filepath: String): Result Unit Error = def hashPlan cmd = - Plan "" cmd Nil Nil "." "" logNever logError logDebug ReRun Nil hashUsage id id False + Plan "" cmd Nil Nil "." "" logNever Nil logError Nil logDebug ReRun Nil hashUsage id id False def job = hashPlan ("", filepath, Nil) diff --git a/share/wake/lib/system/plan_scorer.wake b/share/wake/lib/system/plan_scorer.wake index 517fda0a3..6f8d9fcec 100644 --- a/share/wake/lib/system/plan_scorer.wake +++ b/share/wake/lib/system/plan_scorer.wake @@ -25,7 +25,7 @@ publish runner = # Run a job, via a Runner chosen based on 'score' functions. export def runJob (p: Plan): Job = match p - Plan label cmd vis env dir stdin stdout stderr echo pers res usage finputs foutputs isatty -> + Plan label cmd vis env dir stdin stdout teeout stderr teeerr echo pers res usage finputs foutputs isatty -> def implode l = cat (foldr (_, "\0", _) Nil l) def bToInt b = if b then 1 else 0 @@ -50,16 +50,18 @@ export def runJob (p: Plan): Job = match p match (opts | foldl best (Pair 0.0 None) | getPairSecond) Some r -> - runJobImp label cmd env dir stdin res usage finputs foutputs vis pers r echo stdout stderr isatty + runJobImp label cmd env dir stdin res usage finputs foutputs vis pers r echo stdout teeout stderr teeerr isatty None -> - def create label dir stdin env cmd signature visible keep echo stdout stderr isatty = + def create label dir stdin env cmd signature visible keep echo stdout teeoutFlat stderr teeerrFlat isatty = prim "job_create" def badfinish job e = prim "job_fail_finish" def badlaunch job e = prim "job_fail_launch" + def teeoutFlat = catWith "\0" teeout + def teeerrFlat = catWith "\0" teeerr def job = - create label dir stdin env.implode cmd.implode 0 "" 0 "echo" "info" "error" (bToInt isatty) + create label dir stdin env.implode cmd.implode 0 "" 0 "echo" "info" teeoutFlat "error" teeerrFlat (bToInt isatty) def error = def pretty = match _ diff --git a/src/runtime/job.cpp b/src/runtime/job.cpp index 07ca26f7b..75a0b7ca6 100644 --- a/src/runtime/job.cpp +++ b/src/runtime/job.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -105,7 +106,9 @@ struct Job final : public GCObject { bool keep; std::string echo; std::string stream_out; + std::vector tee_out; std::string stream_err; + std::vector tee_err; HeapPointer bad_launch; HeapPointer bad_finish; double pathtime; @@ -124,8 +127,8 @@ struct Job final : public GCObject { HeapPointer q_report; // waken once job finished (inputs+outputs+report available) Job(Database *db_, String *label_, String *dir_, String *stdin_file_, String *environ, - String *cmdline_, bool keep, const char *echo, const char *stream_out, - const char *stream_err); + String *cmdline_, bool keep, const char *echo, const char *stream_out, std::vector tee_out, + const char *stream_err, std::vector tee_err); template T recurse(T arg); @@ -275,17 +278,21 @@ struct JobEntry { std::string echo_line; std::list::iterator status; std::unique_ptr stdout_linebuf; + std::vector> stdout_teefiles; std::unique_ptr stderr_linebuf; + std::vector> stderr_teefiles; JobEntry(JobTable::detail *imp_, RootPointer &&job_, std::unique_ptr stdout, - std::unique_ptr stderr) + std::vector> stdout_teefiles_, std::unique_ptr stderr, std::vector> stderr_teefiles_) : imp(imp_), job(std::move(job_)), pid(0), pipe_stdout(-1), pipe_stderr(-1), stdout_linebuf(std::move(stdout)), - stderr_linebuf(std::move(stderr)) {} + stdout_teefiles(std::move(stdout_teefiles_)), + stderr_linebuf(std::move(stderr)), + stderr_teefiles(std::move(stderr_teefiles_)) {} ~JobEntry(); double runtime(struct timespec now); @@ -783,7 +790,6 @@ static void launch(JobTable *jobtable) { // Make the raw output streams and the TermInfoBufs // that jobs will use. We make one TermInfoBuf per // file descriptor that we're outputting to. - // TODO: We could add file tee-ing here as well if (!jobtable->imp->fd_bufs.count(fd_out)) { std::unique_ptr fd_buf; if (fd_out != -1) { @@ -824,8 +830,18 @@ static void launch(JobTable *jobtable) { } else { err = std::make_unique(); } + + auto stdout_teefiles = std::vector>(); + for (auto tee_path : task.job->tee_out) { + stdout_teefiles.push_back(std::make_unique(tee_path, std::ios::out | std::ios::app)); + } + auto stderr_teefiles = std::vector>(); + for (auto tee_path : task.job->tee_err) { + stderr_teefiles.push_back(std::make_unique(tee_path, std::ios::out | std::ios::app)); + } + std::shared_ptr entry = std::make_shared( - jobtable->imp.get(), std::move(task.job), std::move(out), std::move(err)); + jobtable->imp.get(), std::move(task.job), std::move(out), std::move(stdout_teefiles), std::move(err), std::move(stderr_teefiles)); int stdout_stream[2]; int stderr_stream[2]; @@ -974,6 +990,9 @@ bool JobTable::wait(Runtime &runtime) { imp->poll.remove(fd); close(fd); entry->pipe_stdout = -1; + for (auto &tee_fd : entry->stdout_teefiles) { + tee_fd->close(); + } entry->status->wait_stdout = false; entry->job->state |= STATE_STDOUT; runtime.heap.guarantee(WJob::reserve()); @@ -983,6 +1002,9 @@ bool JobTable::wait(Runtime &runtime) { entry->job->db->save_output(entry->job->job, 1, buffer, got, entry->runtime(now)); if (!imp->batch) { entry->stdout_linebuf->sputn(buffer, got); + for (auto &tee_fd : entry->stdout_teefiles) { + tee_fd->write(buffer, got); + } } } } @@ -993,6 +1015,9 @@ bool JobTable::wait(Runtime &runtime) { imp->poll.remove(fd); close(fd); entry->pipe_stderr = -1; + for (auto &tee_fd : entry->stderr_teefiles) { + tee_fd->close(); + } entry->status->wait_stderr = false; entry->job->state |= STATE_STDERR; runtime.heap.guarantee(WJob::reserve()); @@ -1002,6 +1027,9 @@ bool JobTable::wait(Runtime &runtime) { entry->job->db->save_output(entry->job->job, 2, buffer, got, entry->runtime(now)); if (!imp->batch) { entry->stderr_linebuf->sputn(buffer, got); + for (auto &tee_fd : entry->stderr_teefiles) { + tee_fd->write(buffer, got); + } } } } @@ -1082,8 +1110,8 @@ bool JobTable::wait(Runtime &runtime) { } Job::Job(Database *db_, String *label_, String *dir_, String *stdin_file_, String *environ, - String *cmdline_, bool keep_, const char *echo_, const char *stream_out_, - const char *stream_err_) + String *cmdline_, bool keep_, const char *echo_, const char *stream_out_, std::vector tee_out_, + const char *stream_err_, std::vector tee_err_) : db(db_), label(label_), cmdline(cmdline_), @@ -1096,7 +1124,9 @@ Job::Job(Database *db_, String *label_, String *dir_, String *stdin_file_, Strin keep(keep_), echo(echo_), stream_out(stream_out_), - stream_err(stream_err_) { + tee_out(tee_out_), + stream_err(stream_err_), + tee_err(tee_err_) { start.tv_sec = stop.tv_sec = 0; start.tv_nsec = stop.tv_nsec = 0; @@ -1286,18 +1316,35 @@ static PRIMFN(prim_job_virtual) { } static PRIMTYPE(type_job_create) { - return args.size() == 12 && args[0]->unify(Data::typeString) && + return args.size() == 14 && args[0]->unify(Data::typeString) && args[1]->unify(Data::typeString) && args[2]->unify(Data::typeString) && args[3]->unify(Data::typeString) && args[4]->unify(Data::typeString) && args[5]->unify(Data::typeInteger) && args[6]->unify(Data::typeString) && args[7]->unify(Data::typeInteger) && args[8]->unify(Data::typeString) && args[9]->unify(Data::typeString) && args[10]->unify(Data::typeString) && - args[11]->unify(Data::typeInteger) && out->unify(Data::typeJob); + args[11]->unify(Data::typeString) && args[12]->unify(Data::typeString) && + args[13]->unify(Data::typeInteger) && out->unify(Data::typeJob); +} + +std::vector null_sep_list(const std::string &str) { + size_t pos = 0; + std::vector out = std::vector(); + do { + size_t next = str.find('\0', pos); + if (next == std::string::npos) { + out.push_back(str.substr(pos)); + break; + } else { + out.push_back(str.substr(pos, next - pos)); + } + pos = next + 1; + } while (pos < str.size()); + return out; } static PRIMFN(prim_job_create) { JobTable *jobtable = static_cast(data); - EXPECT(12); + EXPECT(14); STRING(label, 0); STRING(dir, 1); STRING(stdin_file, 2); @@ -1308,16 +1355,21 @@ static PRIMFN(prim_job_create) { INTEGER_MPZ(keep, 7); STRING(echo, 8); STRING(stream_out, 9); - STRING(stream_err, 10); - INTEGER_MPZ(is_atty, 11); + STRING(tee_out_flat, 10); + STRING(stream_err, 11); + STRING(tee_err_flat, 12); + INTEGER_MPZ(is_atty, 13); Hash hash; REQUIRE(mpz_sizeinbase(signature, 2) <= 8 * sizeof(hash.data)); mpz_export(&hash.data[0], 0, 1, sizeof(hash.data[0]), 0, 0, signature); + std::vector tee_out = null_sep_list(tee_out_flat->as_str()); + std::vector tee_err = null_sep_list(tee_err_flat->as_str()); + Job *out = Job::alloc(runtime.heap, jobtable->imp->db, label, dir, stdin_file, env, cmd, - mpz_cmp_si(keep, 0), echo->c_str(), stream_out->c_str(), stream_err->c_str()); + mpz_cmp_si(keep, 0), echo->c_str(), stream_out->c_str(), tee_out, stream_err->c_str(), tee_err); out->record = jobtable->imp->db->predict_job(out->code.data[0], &out->pathtime); @@ -1396,7 +1448,7 @@ static PRIMFN(prim_job_cache) { Value *joblist; if (reuse.found && !jobtable->imp->check) { Job *jobp = Job::claim(runtime.heap, jobtable->imp->db, dir, dir, stdin_file, env, cmd, true, - STREAM_ECHO, STREAM_INFO, STREAM_WARNING); + STREAM_ECHO, STREAM_INFO, std::vector(), STREAM_WARNING, std::vector()); jobp->state = STATE_FORKED | STATE_STDOUT | STATE_STDERR | STATE_MERGED | STATE_FINISHED; jobp->job = job; jobp->record = reuse; From 71d5c89063e9a80e8bc274f9e88ccbfc740c3be0 Mon Sep 17 00:00:00 2001 From: Sam May Date: Fri, 3 Nov 2023 16:08:28 -0700 Subject: [PATCH 2/5] Postpone teefile-list splitting; better names --- share/wake/lib/system/job.wake | 45 +++++++++-------- share/wake/lib/system/path.wake | 6 +-- share/wake/lib/system/plan_scorer.wake | 12 ++--- src/runtime/job.cpp | 68 ++++++++++---------------- 4 files changed, 59 insertions(+), 72 deletions(-) diff --git a/share/wake/lib/system/job.wake b/share/wake/lib/system/job.wake index dda055545..0a53d2d19 100644 --- a/share/wake/lib/system/job.wake +++ b/share/wake/lib/system/job.wake @@ -112,10 +112,10 @@ export tuple Plan = export Stdin: String # How should standard output be displayed during a build export Stdout: LogLevel - # The workspace-relative paths to which the stdout will be appended - export TeeStdout: List String # How should standard error be displayed during a build export Stderr: LogLevel + # The workspace-relative paths to which the stdout will be appended + export TeeStdout: List String # The workspace-relative paths to which the stderr will be appended export TeeStderr: List String # Echo the command to this stream @@ -279,7 +279,7 @@ def bToInt b = # Set reasonable defaults for all Plan arguments export def makeExecPlan (cmd: List String) (visible: List Path): Plan = - Plan "" cmd visible environment "." "" logInfo Nil logWarning Nil logEcho Share Nil defaultUsage id id False + Plan "" cmd visible environment "." "" logInfo logWarning Nil Nil logEcho Share Nil defaultUsage id id False export def makeShellPlan (script: String) (visible: List Path): Plan = makeExecPlan (which "dash", "-c", script, Nil) visible @@ -575,8 +575,8 @@ export def virtualRunner: Runner = def implode l = cat (foldr (_, "\0", _) Nil l) -def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo stdout teeout stderr teeerr label isatty: Job = - def create label dir stdin env cmd signature visible keep echo stdout teeoutFlat stderr teeerrFlat isatty = +def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echoLevel stdoutLevel stderrLevel stdoutFiles stderrFiles label isatty: Job = + def create label dir stdin env cmd signature visible keep echoLevel stdoutLevel stderrLevel stdoutFilesFlat stderrFilesFlat isatty = prim "job_create" def finish job inputs outputs all_outputs status runtime cputime membytes ibytes obytes = @@ -586,8 +586,11 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo st def cache dir stdin env cmd signature visible isatty = prim "job_cache" def signature cmd res fni fno keep = prim "hash" def hash = signature cmd res finputs foutputs keep - def teeoutFlat = catWith "\0" teeout - def teeerrFlat = catWith "\0" teeerr + + def catTeeFiles paths = catWith "\0" paths + + def stdoutFilesFlat = catTeeFiles stdoutFiles + def stderrFilesFlat = catTeeFiles stderrFiles def build Unit = def visStrings = map getPathName vis @@ -602,11 +605,11 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo st hash visStrings.implode (bToInt keep) - echo - stdout - teeoutFlat - stderr - teeerrFlat + echoLevel + stdoutLevel + stderrLevel + stdoutFilesFlat + stderrFilesFlat (bToInt isatty) def prefix = str (getJobId job) @@ -663,10 +666,10 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echo st Pair Nil last -> confirm False last (build Unit) # Only run if the first four arguments differ -target runOnce cmd env dir stdin vis isatty run \ res usage finputs foutputs keep echo stdout teeout stderr teeerr label = - runAlways cmd env dir stdin res usage finputs foutputs vis keep run echo stdout teeout stderr teeerr label isatty +target runOnce cmd env dir stdin vis isatty run \ res usage finputs foutputs keep echoLevel stdoutLevel stderrLevel stdoutFiles stderrFiles label = + runAlways cmd env dir stdin res usage finputs foutputs vis keep run echoLevel stdoutLevel stderrLevel stdoutFiles stderrFiles label isatty -export def runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run (LogLevel echo) (LogLevel stdout) teeout (LogLevel stderr) teeerr isatty = +export def runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run (LogLevel echo) (LogLevel stdout) (LogLevel stderr) stdoutFiles stderrFiles isatty = if isOnce pers then runOnce cmd @@ -683,9 +686,9 @@ export def runJobImp label cmd env dir stdin res usage finputs foutputs vis pers (isKeep pers) echo stdout - teeout stderr - teeerr + stdoutFiles + stderrFiles label else runAlways @@ -702,14 +705,14 @@ export def runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run echo stdout - teeout stderr - teeerr + stdoutFiles + stderrFiles label isatty -export def runJobWith (Runner _ _ run) (Plan label cmd vis env dir stdin stdout teeout stderr teeerr echo pers res usage finputs foutputs isatty) = - runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run echo stdout teeout stderr teeerr isatty +export def runJobWith (Runner _ _ run) (Plan label cmd vis env dir stdin stdoutLevel stderrLevel stdoutFiles stderrFiles echoLevel pers res usage finputs foutputs isatty) = + runJobImp label cmd env dir stdin res usage finputs foutputs vis pers run echoLevel stdoutLevel stderrLevel stdoutFiles stderrFiles isatty # Set the value of a tag on a Job # This is useful for post-build reflection into the database diff --git a/share/wake/lib/system/path.wake b/share/wake/lib/system/path.wake index b2a1bc844..d266182bf 100644 --- a/share/wake/lib/system/path.wake +++ b/share/wake/lib/system/path.wake @@ -179,7 +179,7 @@ def computeHashes (prefix: String) (files: List String): List String = def add f h = prim "add_hash" def hashPlan cmd vis = - Plan "" cmd vis Nil "." "" logNever Nil logError Nil logDebug ReRun Nil hashUsage id id False + Plan "" cmd vis Nil "." "" logNever logError Nil Nil logDebug ReRun Nil hashUsage id id False def stdin_file_path = "to_hash.{prefix}.stdin" @@ -240,7 +240,7 @@ target hashcode (f: String): String = reuse else def hashPlan cmd = - Plan "" cmd Nil Nil "." "" logNever Nil logError Nil logDebug ReRun Nil hashUsage id id False + Plan "" cmd Nil Nil "." "" logNever logError Nil Nil logDebug ReRun Nil hashUsage id id False def job = hashPlan ("", f, Nil) @@ -259,7 +259,7 @@ target hashcode (f: String): String = # Allow an untracked file to be removed via `wake --clean` export target markFileCleanable (filepath: String): Result Unit Error = def hashPlan cmd = - Plan "" cmd Nil Nil "." "" logNever Nil logError Nil logDebug ReRun Nil hashUsage id id False + Plan "" cmd Nil Nil "." "" logNever logError Nil Nil logDebug ReRun Nil hashUsage id id False def job = hashPlan ("", filepath, Nil) diff --git a/share/wake/lib/system/plan_scorer.wake b/share/wake/lib/system/plan_scorer.wake index 6f8d9fcec..8b460d7df 100644 --- a/share/wake/lib/system/plan_scorer.wake +++ b/share/wake/lib/system/plan_scorer.wake @@ -25,7 +25,7 @@ publish runner = # Run a job, via a Runner chosen based on 'score' functions. export def runJob (p: Plan): Job = match p - Plan label cmd vis env dir stdin stdout teeout stderr teeerr echo pers res usage finputs foutputs isatty -> + Plan label cmd vis env dir stdin stdoutLevel stderrLevel stdoutFiles stderrFiles echoLevel pers res usage finputs foutputs isatty -> def implode l = cat (foldr (_, "\0", _) Nil l) def bToInt b = if b then 1 else 0 @@ -50,18 +50,18 @@ export def runJob (p: Plan): Job = match p match (opts | foldl best (Pair 0.0 None) | getPairSecond) Some r -> - runJobImp label cmd env dir stdin res usage finputs foutputs vis pers r echo stdout teeout stderr teeerr isatty + runJobImp label cmd env dir stdin res usage finputs foutputs vis pers r echoLevel stdoutLevel stderrLevel stdoutFiles stderrFiles isatty None -> - def create label dir stdin env cmd signature visible keep echo stdout teeoutFlat stderr teeerrFlat isatty = + def create label dir stdin env cmd signature visible keep echoLevel stdoutLevel stderrLevel stdoutFilesFlat stderrFilesFlat isatty = prim "job_create" def badfinish job e = prim "job_fail_finish" def badlaunch job e = prim "job_fail_launch" - def teeoutFlat = catWith "\0" teeout - def teeerrFlat = catWith "\0" teeerr + def stdoutFilesFlat = catWith "\0" stdoutFiles + def stderrFilesFlat = catWith "\0" stderrFiles def job = - create label dir stdin env.implode cmd.implode 0 "" 0 "echo" "info" teeoutFlat "error" teeerrFlat (bToInt isatty) + create label dir stdin env.implode cmd.implode 0 "" 0 "echo" "info" stdoutFilesFlat "error" stderrFilesFlat (bToInt isatty) def error = def pretty = match _ diff --git a/src/runtime/job.cpp b/src/runtime/job.cpp index 75a0b7ca6..da475584c 100644 --- a/src/runtime/job.cpp +++ b/src/runtime/job.cpp @@ -106,9 +106,8 @@ struct Job final : public GCObject { bool keep; std::string echo; std::string stream_out; - std::vector tee_out; std::string stream_err; - std::vector tee_err; + std::string stdout_files, stderr_files; HeapPointer bad_launch; HeapPointer bad_finish; double pathtime; @@ -127,8 +126,8 @@ struct Job final : public GCObject { HeapPointer q_report; // waken once job finished (inputs+outputs+report available) Job(Database *db_, String *label_, String *dir_, String *stdin_file_, String *environ, - String *cmdline_, bool keep, const char *echo, const char *stream_out, std::vector tee_out, - const char *stream_err, std::vector tee_err); + String *cmdline_, bool keep, const char *echo, const char *stream_out, + const char *stream_err, std::string stdout_files, std::string stderr_files); template T recurse(T arg); @@ -278,20 +277,20 @@ struct JobEntry { std::string echo_line; std::list::iterator status; std::unique_ptr stdout_linebuf; - std::vector> stdout_teefiles; std::unique_ptr stderr_linebuf; + std::vector> stdout_teefiles; std::vector> stderr_teefiles; JobEntry(JobTable::detail *imp_, RootPointer &&job_, std::unique_ptr stdout, - std::vector> stdout_teefiles_, std::unique_ptr stderr, std::vector> stderr_teefiles_) + std::unique_ptr stderr, std::vector> stdout_teefiles_, std::vector> stderr_teefiles_) : imp(imp_), job(std::move(job_)), pid(0), pipe_stdout(-1), pipe_stderr(-1), stdout_linebuf(std::move(stdout)), - stdout_teefiles(std::move(stdout_teefiles_)), stderr_linebuf(std::move(stderr)), + stdout_teefiles(std::move(stdout_teefiles_)), stderr_teefiles(std::move(stderr_teefiles_)) {} ~JobEntry(); @@ -831,17 +830,21 @@ static void launch(JobTable *jobtable) { err = std::make_unique(); } - auto stdout_teefiles = std::vector>(); - for (auto tee_path : task.job->tee_out) { - stdout_teefiles.push_back(std::make_unique(tee_path, std::ios::out | std::ios::app)); + auto stdout_files = split_null(task.job->stdout_files); + auto stdout_tee_streams = std::vector>(); + for (int i = 0; stdout_files[i]; ++i) { + stdout_tee_streams.push_back(std::make_unique(stdout_files[i], std::ios::out | std::ios::app)); } - auto stderr_teefiles = std::vector>(); - for (auto tee_path : task.job->tee_err) { - stderr_teefiles.push_back(std::make_unique(tee_path, std::ios::out | std::ios::app)); + auto stderr_files = split_null(task.job->stdout_files); + auto stderr_tee_streams = std::vector>(); + for (int i = 0; stderr_files[i]; ++i) { + stderr_tee_streams.push_back(std::make_unique(stderr_files[i], std::ios::out | std::ios::app)); } + delete[] stdout_files; + delete[] stderr_files; std::shared_ptr entry = std::make_shared( - jobtable->imp.get(), std::move(task.job), std::move(out), std::move(stdout_teefiles), std::move(err), std::move(stderr_teefiles)); + jobtable->imp.get(), std::move(task.job), std::move(out), std::move(err), std::move(stdout_tee_streams), std::move(stderr_tee_streams)); int stdout_stream[2]; int stderr_stream[2]; @@ -1110,8 +1113,8 @@ bool JobTable::wait(Runtime &runtime) { } Job::Job(Database *db_, String *label_, String *dir_, String *stdin_file_, String *environ, - String *cmdline_, bool keep_, const char *echo_, const char *stream_out_, std::vector tee_out_, - const char *stream_err_, std::vector tee_err_) + String *cmdline_, bool keep_, const char *echo_, const char *stream_out_, + const char *stream_err_, std::string stdout_files_, std::string stderr_files_) : db(db_), label(label_), cmdline(cmdline_), @@ -1124,9 +1127,9 @@ Job::Job(Database *db_, String *label_, String *dir_, String *stdin_file_, Strin keep(keep_), echo(echo_), stream_out(stream_out_), - tee_out(tee_out_), stream_err(stream_err_), - tee_err(tee_err_) { + stdout_files(stdout_files_), + stderr_files(stderr_files_) { start.tv_sec = stop.tv_sec = 0; start.tv_nsec = stop.tv_nsec = 0; @@ -1326,22 +1329,6 @@ static PRIMTYPE(type_job_create) { args[13]->unify(Data::typeInteger) && out->unify(Data::typeJob); } -std::vector null_sep_list(const std::string &str) { - size_t pos = 0; - std::vector out = std::vector(); - do { - size_t next = str.find('\0', pos); - if (next == std::string::npos) { - out.push_back(str.substr(pos)); - break; - } else { - out.push_back(str.substr(pos, next - pos)); - } - pos = next + 1; - } while (pos < str.size()); - return out; -} - static PRIMFN(prim_job_create) { JobTable *jobtable = static_cast(data); EXPECT(14); @@ -1355,21 +1342,18 @@ static PRIMFN(prim_job_create) { INTEGER_MPZ(keep, 7); STRING(echo, 8); STRING(stream_out, 9); - STRING(tee_out_flat, 10); - STRING(stream_err, 11); - STRING(tee_err_flat, 12); + STRING(stream_err, 10); + STRING(stdout_files, 11); + STRING(stderr_files, 12); INTEGER_MPZ(is_atty, 13); Hash hash; REQUIRE(mpz_sizeinbase(signature, 2) <= 8 * sizeof(hash.data)); mpz_export(&hash.data[0], 0, 1, sizeof(hash.data[0]), 0, 0, signature); - std::vector tee_out = null_sep_list(tee_out_flat->as_str()); - std::vector tee_err = null_sep_list(tee_err_flat->as_str()); - Job *out = Job::alloc(runtime.heap, jobtable->imp->db, label, dir, stdin_file, env, cmd, - mpz_cmp_si(keep, 0), echo->c_str(), stream_out->c_str(), tee_out, stream_err->c_str(), tee_err); + mpz_cmp_si(keep, 0), echo->c_str(), stream_out->c_str(), stream_err->c_str(), stdout_files->as_str(), stderr_files->as_str()); out->record = jobtable->imp->db->predict_job(out->code.data[0], &out->pathtime); @@ -1448,7 +1432,7 @@ static PRIMFN(prim_job_cache) { Value *joblist; if (reuse.found && !jobtable->imp->check) { Job *jobp = Job::claim(runtime.heap, jobtable->imp->db, dir, dir, stdin_file, env, cmd, true, - STREAM_ECHO, STREAM_INFO, std::vector(), STREAM_WARNING, std::vector()); + STREAM_ECHO, STREAM_INFO, STREAM_WARNING, "", ""); jobp->state = STATE_FORKED | STATE_STDOUT | STATE_STDERR | STATE_MERGED | STATE_FINISHED; jobp->job = job; jobp->record = reuse; From 70436c715948e2ff183ff6b8f0779a50d9844b31 Mon Sep 17 00:00:00 2001 From: Sam May Date: Mon, 6 Nov 2023 10:53:37 -0800 Subject: [PATCH 3/5] Prevent code from depending on the logs --- share/wake/lib/system/job.wake | 1 + 1 file changed, 1 insertion(+) diff --git a/share/wake/lib/system/job.wake b/share/wake/lib/system/job.wake index 0a53d2d19..f0d74433e 100644 --- a/share/wake/lib/system/job.wake +++ b/share/wake/lib/system/job.wake @@ -631,6 +631,7 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echoLev def output = foutputs outputs + | filter (\f !exists (f ==* _) (stdoutFiles ++ stderrFiles)) | computeHashes prefix | implode From b7ddc4d3bc0d7f137e5c7d5c5b3d9e49f76542db Mon Sep 17 00:00:00 2001 From: Sam May Date: Mon, 6 Nov 2023 11:54:01 -0800 Subject: [PATCH 4/5] Track teefiles at the level of the JobTable --- share/wake/lib/system/job.wake | 14 ++++--- src/runtime/job.cpp | 67 +++++++++++++++++++--------------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/share/wake/lib/system/job.wake b/share/wake/lib/system/job.wake index f0d74433e..a5c7b2bee 100644 --- a/share/wake/lib/system/job.wake +++ b/share/wake/lib/system/job.wake @@ -587,10 +587,12 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echoLev def signature cmd res fni fno keep = prim "hash" def hash = signature cmd res finputs foutputs keep - def catTeeFiles paths = catWith "\0" paths - - def stdoutFilesFlat = catTeeFiles stdoutFiles - def stderrFilesFlat = catTeeFiles stderrFiles + def catTeeFiles paths = + # Null byte added here rather than via `catWith "\0"` in order to have a null byte after the + # *final* path as well, as required by `split_null`. + map ("{simplify _}\0") paths + | distinctBy scmp + | cat def build Unit = def visStrings = map getPathName vis @@ -608,8 +610,8 @@ def runAlways cmd env dir stdin res uusage finputs foutputs vis keep run echoLev echoLevel stdoutLevel stderrLevel - stdoutFilesFlat - stderrFilesFlat + (catTeeFiles stdoutFiles) + (catTeeFiles stderrFiles) (bToInt isatty) def prefix = str (getJobId job) diff --git a/src/runtime/job.cpp b/src/runtime/job.cpp index da475584c..5664a530e 100644 --- a/src/runtime/job.cpp +++ b/src/runtime/job.cpp @@ -107,7 +107,7 @@ struct Job final : public GCObject { std::string echo; std::string stream_out; std::string stream_err; - std::string stdout_files, stderr_files; + std::string stdout_teefiles, stderr_teefiles; HeapPointer bad_launch; HeapPointer bad_finish; double pathtime; @@ -127,7 +127,7 @@ struct Job final : public GCObject { Job(Database *db_, String *label_, String *dir_, String *stdin_file_, String *environ, String *cmdline_, bool keep, const char *echo, const char *stream_out, - const char *stream_err, std::string stdout_files, std::string stderr_files); + const char *stream_err, std::string stdout_teefiles, std::string stderr_teefiles); template T recurse(T arg); @@ -278,11 +278,11 @@ struct JobEntry { std::list::iterator status; std::unique_ptr stdout_linebuf; std::unique_ptr stderr_linebuf; - std::vector> stdout_teefiles; - std::vector> stderr_teefiles; + std::vector stdout_teefiles; + std::vector stderr_teefiles; JobEntry(JobTable::detail *imp_, RootPointer &&job_, std::unique_ptr stdout, - std::unique_ptr stderr, std::vector> stdout_teefiles_, std::vector> stderr_teefiles_) + std::unique_ptr stderr, std::vector stdout_teefiles_, std::vector stderr_teefiles_) : imp(imp_), job(std::move(job_)), pid(0), @@ -329,6 +329,8 @@ struct JobTable::detail { std::unordered_map> fd_bufs; std::unordered_map> term_bufs; + std::unordered_map> teefiles; + detail() {} ~detail() { @@ -345,6 +347,9 @@ struct JobTable::detail { for (auto &entry : term_bufs) { entry.second.release(); } + for (auto &entry : teefiles) { + entry.second->close(); + } } CriticalJob critJob(double nexttime) const; @@ -830,21 +835,27 @@ static void launch(JobTable *jobtable) { err = std::make_unique(); } - auto stdout_files = split_null(task.job->stdout_files); - auto stdout_tee_streams = std::vector>(); - for (int i = 0; stdout_files[i]; ++i) { - stdout_tee_streams.push_back(std::make_unique(stdout_files[i], std::ios::out | std::ios::app)); + auto stdout_teefiles = split_null(task.job->stdout_teefiles); + auto stdout_tee_names = std::vector(); + for (int i = 0; stdout_teefiles[i]; ++i) { + stdout_tee_names.push_back(stdout_teefiles[i]); + if (jobtable->imp->teefiles.find(stdout_teefiles[i]) == jobtable->imp->teefiles.end()) { + jobtable->imp->teefiles[stdout_teefiles[i]] = std::make_unique(stdout_teefiles[i], std::ios::out | std::ios::trunc); + } } - auto stderr_files = split_null(task.job->stdout_files); - auto stderr_tee_streams = std::vector>(); - for (int i = 0; stderr_files[i]; ++i) { - stderr_tee_streams.push_back(std::make_unique(stderr_files[i], std::ios::out | std::ios::app)); + auto stderr_teefiles = split_null(task.job->stderr_teefiles); + auto stderr_tee_names = std::vector(); + for (int i = 0; stderr_teefiles[i]; ++i) { + stderr_tee_names.push_back(stderr_teefiles[i]); + if (jobtable->imp->teefiles.find(stderr_teefiles[i]) == jobtable->imp->teefiles.end()) { + jobtable->imp->teefiles[stderr_teefiles[i]] = std::make_unique(stderr_teefiles[i], std::ios::out | std::ios::trunc); + } } - delete[] stdout_files; - delete[] stderr_files; + delete[] stdout_teefiles; + delete[] stderr_teefiles; std::shared_ptr entry = std::make_shared( - jobtable->imp.get(), std::move(task.job), std::move(out), std::move(err), std::move(stdout_tee_streams), std::move(stderr_tee_streams)); + jobtable->imp.get(), std::move(task.job), std::move(out), std::move(err), std::move(stdout_tee_names), std::move(stderr_tee_names)); int stdout_stream[2]; int stderr_stream[2]; @@ -993,9 +1004,6 @@ bool JobTable::wait(Runtime &runtime) { imp->poll.remove(fd); close(fd); entry->pipe_stdout = -1; - for (auto &tee_fd : entry->stdout_teefiles) { - tee_fd->close(); - } entry->status->wait_stdout = false; entry->job->state |= STATE_STDOUT; runtime.heap.guarantee(WJob::reserve()); @@ -1005,7 +1013,8 @@ bool JobTable::wait(Runtime &runtime) { entry->job->db->save_output(entry->job->job, 1, buffer, got, entry->runtime(now)); if (!imp->batch) { entry->stdout_linebuf->sputn(buffer, got); - for (auto &tee_fd : entry->stdout_teefiles) { + for (auto &teefile : entry->stdout_teefiles) { + auto &tee_fd = imp->teefiles.at(teefile); tee_fd->write(buffer, got); } } @@ -1018,9 +1027,6 @@ bool JobTable::wait(Runtime &runtime) { imp->poll.remove(fd); close(fd); entry->pipe_stderr = -1; - for (auto &tee_fd : entry->stderr_teefiles) { - tee_fd->close(); - } entry->status->wait_stderr = false; entry->job->state |= STATE_STDERR; runtime.heap.guarantee(WJob::reserve()); @@ -1030,7 +1036,8 @@ bool JobTable::wait(Runtime &runtime) { entry->job->db->save_output(entry->job->job, 2, buffer, got, entry->runtime(now)); if (!imp->batch) { entry->stderr_linebuf->sputn(buffer, got); - for (auto &tee_fd : entry->stderr_teefiles) { + for (auto &teefile : entry->stderr_teefiles) { + auto &tee_fd = imp->teefiles.at(teefile); tee_fd->write(buffer, got); } } @@ -1114,7 +1121,7 @@ bool JobTable::wait(Runtime &runtime) { Job::Job(Database *db_, String *label_, String *dir_, String *stdin_file_, String *environ, String *cmdline_, bool keep_, const char *echo_, const char *stream_out_, - const char *stream_err_, std::string stdout_files_, std::string stderr_files_) + const char *stream_err_, std::string stdout_teefiles_, std::string stderr_teefiles_) : db(db_), label(label_), cmdline(cmdline_), @@ -1128,8 +1135,8 @@ Job::Job(Database *db_, String *label_, String *dir_, String *stdin_file_, Strin echo(echo_), stream_out(stream_out_), stream_err(stream_err_), - stdout_files(stdout_files_), - stderr_files(stderr_files_) { + stdout_teefiles(stdout_teefiles_), + stderr_teefiles(stderr_teefiles_) { start.tv_sec = stop.tv_sec = 0; start.tv_nsec = stop.tv_nsec = 0; @@ -1343,8 +1350,8 @@ static PRIMFN(prim_job_create) { STRING(echo, 8); STRING(stream_out, 9); STRING(stream_err, 10); - STRING(stdout_files, 11); - STRING(stderr_files, 12); + STRING(stdout_teefiles, 11); + STRING(stderr_teefiles, 12); INTEGER_MPZ(is_atty, 13); Hash hash; @@ -1353,7 +1360,7 @@ static PRIMFN(prim_job_create) { Job *out = Job::alloc(runtime.heap, jobtable->imp->db, label, dir, stdin_file, env, cmd, - mpz_cmp_si(keep, 0), echo->c_str(), stream_out->c_str(), stream_err->c_str(), stdout_files->as_str(), stderr_files->as_str()); + mpz_cmp_si(keep, 0), echo->c_str(), stream_out->c_str(), stream_err->c_str(), stdout_teefiles->as_str(), stderr_teefiles->as_str()); out->record = jobtable->imp->db->predict_job(out->code.data[0], &out->pathtime); From 99bbf86588ee39671404251a384281861045f0b6 Mon Sep 17 00:00:00 2001 From: Sam May Date: Fri, 10 Nov 2023 11:08:58 -0800 Subject: [PATCH 5/5] FAILING: Replace null_split with modern code --- src/compat/{spawn.c => spawn.cpp} | 15 ++++++++-- src/compat/spawn.h | 12 ++------ src/runtime/job.cpp | 47 +++++++++++++------------------ 3 files changed, 35 insertions(+), 39 deletions(-) rename src/compat/{spawn.c => spawn.cpp} (63%) diff --git a/src/compat/spawn.c b/src/compat/spawn.cpp similarity index 63% rename from src/compat/spawn.c rename to src/compat/spawn.cpp index bd4e0be21..bbc2dbcb7 100644 --- a/src/compat/spawn.c +++ b/src/compat/spawn.cpp @@ -21,13 +21,24 @@ #include "spawn.h" +#include #include #include -pid_t wake_spawn(const char *cmd, char **cmdline, char **environ) { +pid_t wake_spawn(const std::string cmd, std::vector cmdline, std::vector environ) { pid_t pid = vfork(); if (pid == 0) { - execve(cmdline[0], cmdline, environ); + std::vector cmdline_c = std::vector(); + for (auto str : cmdline) { + cmdline_c.push_back(&str.front()); + } + cmdline_c.push_back(nullptr); + std::vector environ_c = std::vector(); + for (auto str : environ) { + environ_c.push_back(&str.front()); + } + environ_c.push_back(nullptr); + execve(cmd.c_str(), cmdline_c.data(), environ_c.data()); _exit(127); } return pid; diff --git a/src/compat/spawn.h b/src/compat/spawn.h index d84db5955..d41015365 100644 --- a/src/compat/spawn.h +++ b/src/compat/spawn.h @@ -18,16 +18,10 @@ #ifndef SPAWN_H #define SPAWN_H +#include #include +#include -#ifdef __cplusplus -extern "C" { -#endif - -pid_t wake_spawn(const char *cmd, char **cmdline, char **environ); - -#ifdef __cplusplus -}; -#endif +pid_t wake_spawn(const std::string cmd, std::vector cmdline, std::vector environ); #endif diff --git a/src/runtime/job.cpp b/src/runtime/job.cpp index 5664a530e..df253a145 100644 --- a/src/runtime/job.cpp +++ b/src/runtime/job.cpp @@ -678,22 +678,17 @@ JobTable::~JobTable() { } } -static char **split_null(std::string &str) { - int nulls = 0; - for (char c : str) nulls += (c == 0); - char **out = new char *[nulls + 1]; - - nulls = 0; - out[0] = const_cast(str.c_str()); - char *end = out[0] + str.size(); - for (char *scan = out[0]; scan != end; ++scan) { - if (*scan == 0) { - ++nulls; - out[nulls] = scan + 1; +static std::vector split_null(std::string &str) { + std::vector out; + std::string buf; + for (char c : str) { + if (c == '\0') { + out.emplace_back(std::move(buf)); + } else { + buf += c; } } - out[nulls] = 0; - + out.emplace_back(std::move(buf)); return out; } @@ -835,24 +830,22 @@ static void launch(JobTable *jobtable) { err = std::make_unique(); } - auto stdout_teefiles = split_null(task.job->stdout_teefiles); + const auto stdout_teefiles = split_null(task.job->stdout_teefiles); auto stdout_tee_names = std::vector(); - for (int i = 0; stdout_teefiles[i]; ++i) { - stdout_tee_names.push_back(stdout_teefiles[i]); - if (jobtable->imp->teefiles.find(stdout_teefiles[i]) == jobtable->imp->teefiles.end()) { - jobtable->imp->teefiles[stdout_teefiles[i]] = std::make_unique(stdout_teefiles[i], std::ios::out | std::ios::trunc); + for (const auto &file : stdout_teefiles) { + stdout_tee_names.push_back(file); + if (jobtable->imp->teefiles.find(file) == jobtable->imp->teefiles.end()) { + jobtable->imp->teefiles[file] = std::make_unique(file, std::ios::out | std::ios::trunc); } } - auto stderr_teefiles = split_null(task.job->stderr_teefiles); + const auto stderr_teefiles = split_null(task.job->stderr_teefiles); auto stderr_tee_names = std::vector(); - for (int i = 0; stderr_teefiles[i]; ++i) { - stderr_tee_names.push_back(stderr_teefiles[i]); - if (jobtable->imp->teefiles.find(stderr_teefiles[i]) == jobtable->imp->teefiles.end()) { - jobtable->imp->teefiles[stderr_teefiles[i]] = std::make_unique(stderr_teefiles[i], std::ios::out | std::ios::trunc); + for (const auto &file : stderr_teefiles) { + stderr_tee_names.push_back(file); + if (jobtable->imp->teefiles.find(file) == jobtable->imp->teefiles.end()) { + jobtable->imp->teefiles[file] = std::make_unique(file, std::ios::out | std::ios::trunc); } } - delete[] stdout_teefiles; - delete[] stderr_teefiles; std::shared_ptr entry = std::make_shared( jobtable->imp.get(), std::move(task.job), std::move(out), std::move(err), std::move(stdout_tee_names), std::move(stderr_tee_names)); @@ -893,8 +886,6 @@ static void launch(JobTable *jobtable) { pid_t pid = wake_spawn(cmdline[0], cmdline, environ); sigprocmask(SIG_BLOCK, &set, 0); - delete[] cmdline; - delete[] environ; ++jobtable->imp->num_running; jobtable->imp->pidmap[pid] = entry; entry->job->pid = entry->pid = pid;