From bc7701e7a4f95cee680e0736ec3e68a8b0b5c09f Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Fri, 20 Nov 2020 23:56:36 +0300 Subject: [PATCH] Fix JS deadlock and various optimizations --- README | 1 + ifchange.go | 76 +++++++++++++++++ js.go | 13 +-- log.go | 14 ++-- main.go | 62 ++------------ ood.go | 234 +++++++++++++++++++++++++++++----------------------- run.go | 203 ++++++++++++++++++++++++++------------------- usage.go | 17 ++++ 8 files changed, 368 insertions(+), 252 deletions(-) create mode 100644 ifchange.go diff --git a/README b/README index db2222e..a918bae 100644 --- a/README +++ b/README @@ -33,6 +33,7 @@ NOTES *goredo-notes* REDO_TOP_DIR environment variable, or by having .redo/top file in it * executable .do is run as is * shebangless .do is run with /bin/sh -e[x] +* target's completion messages are written after they finish FEATURES *goredo-features* diff --git a/ifchange.go b/ifchange.go new file mode 100644 index 0000000..972da4d --- /dev/null +++ b/ifchange.go @@ -0,0 +1,76 @@ +/* +goredo -- redo implementation on pure Go +Copyright (C) 2020 Sergey Matveev + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package main + +func isOkRun(err error) bool { + if err == nil { + return true + } + if err, ok := err.(RunErr); ok && err.Err == nil { + trace(CRedo, "%s", err.Name()) + return true + } + trace(CErr, "%s", err) + return false +} + +func ifchange(tgts []string) (bool, error) { + jsInit() + defer jsAcquire("ifchange exiting") + defer Jobs.Wait() + errs := make(chan error, len(tgts)) + jobs := 0 + ok := true + var err error + for _, tgt := range tgts { + var ood bool + if Force { + ood = true + } else { + ood, err = isOOD(Cwd, tgt, 0) + if err != nil { + return false, err + } + } + if !ood { + continue + } + if isSrc(Cwd, tgt) { + trace(CDebug, "%s is source, not redoing", tgt) + continue + } + if err = runScript(tgt, errs); err != nil { + return false, err + } + if Force { + // Sequentially run jobs + err = <-errs + Jobs.Wait() + if isOkRun(err) { + continue + } + return false, nil + } + jobs++ + } + for i := 0; i < jobs; i++ { + err = <-errs + ok = ok && isOkRun(err) + } + return ok, nil +} diff --git a/js.go b/js.go index ab1190e..d3ed7bb 100644 --- a/js.go +++ b/js.go @@ -39,7 +39,7 @@ func jsInit() { if jsrRaw != "" { JSR = mustParseFd(jsrRaw, "JSR") JSW = mustParseFd(jswRaw, "JSW") - jsRelease() + jsRelease("ifchange entered") return } if *JobsN == 0 { @@ -52,26 +52,27 @@ func jsInit() { panic(err) } for i := uint(0); i < *JobsN; i++ { - jsRelease() + jsRelease("initial fill") } } -func jsRelease() { +func jsRelease(ctx string) { if JSW == nil { return } - trace(CJS, "release") + trace(CJS, "release from %s", ctx) if n, err := JSW.Write([]byte{0}); err != nil || n != 1 { panic("can not write JSW") } } -func jsAcquire() { +func jsAcquire(ctx string) { if JSR == nil { return } - trace(CJS, "acquire") + trace(CJS, "acquire for %s", ctx) if n, err := JSR.Read([]byte{0}); err != nil || n != 1 { panic("can not read JSR") } + trace(CJS, "acquired for %s", ctx) } diff --git a/log.go b/log.go index 8669a64..335ca53 100644 --- a/log.go +++ b/log.go @@ -39,7 +39,7 @@ const ( CDebug = CYellow CRedo = CGreen - CDone = CBlue + CWait = CBlue CLock = CCyan CErr = CRed CWarn = CMagenta @@ -50,7 +50,7 @@ const ( var ( NoColor bool Debug bool - LogDone bool + LogWait bool LogLock bool LogJS bool MyPid int @@ -70,13 +70,13 @@ func trace(level, format string, args ...interface{}) { return } p += "dbg " - case CRedo: - p += "redo " - case CDone: - if !(LogDone || Debug) { + case CWait: + if !(LogWait || Debug) { return } - p += "done " + p += "wait " + case CRedo: + p += "redo " case CLock: if !(LogLock || Debug) { return diff --git a/main.go b/main.go index df9296f..d0d3ab2 100644 --- a/main.go +++ b/main.go @@ -81,61 +81,12 @@ func mustParseFd(v, name string) *os.File { return fd } -func ifchange(tgts []string) (bool, error) { - jsInit() - defer Jobs.Wait() - defer jsAcquire() - errs := make(chan error, len(tgts)) - jobs := 0 - ok := true - var err error - for _, tgt := range tgts { - var ood bool - if Force { - ood = true - } else { - ood, err = isOOD(Cwd, tgt, 0) - if err != nil { - return false, err - } - } - if !ood { - continue - } - if isSrc(Cwd, tgt) { - trace(CDebug, "%s is source", tgt) - continue - } - if err = runScript(tgt, errs); err != nil { - return false, err - } - if Force { - // Sequentially run jobs - err = <-errs - Jobs.Wait() - if err != nil { - trace(CErr, "%s", err) - return false, nil - } - continue - } - jobs++ - } - for i := 0; i < jobs; i++ { - if err = <-errs; err != nil { - ok = false - trace(CErr, "%s", err) - } - } - return ok, nil -} - func main() { xflag := flag.Bool("x", false, "trace current target (sh -x) (set REDO_TRACE=1 for others too)") stderrKeep := flag.Bool("stderr-keep", false, "keep job's stderr (REDO_STDERR_KEEP=1)") stderrSilent := flag.Bool("stderr-silent", false, "do not print job's stderr (REDO_STDERR_SILENT=1)") debug := flag.Bool("debug", false, "enable debug logging (REDO_DEBUG=1)") - logDone := flag.Bool("log-done", false, "enable done messages logging (REDO_LOG_DONE=1)") + logWait := flag.Bool("log-wait", false, "enable wait messages logging (REDO_LOG_WAIT=1)") logLock := flag.Bool("log-lock", false, "enable lock messages logging (REDO_LOG_LOCK=1)") logPid := flag.Bool("log-pid", false, "append PIDs (REDO_LOG_PID=1)") logJS := flag.Bool("log-js", false, "enable jobserver messages logging (REDO_LOG_JS=1)") @@ -199,8 +150,8 @@ func main() { if *debug { mustSetenv("REDO_DEBUG", "1") } - if *logDone { - mustSetenv("REDO_LOG_DONE", "1") + if *logWait { + mustSetenv("REDO_LOG_WAIT", "1") } if *logLock { mustSetenv("REDO_LOG_LOCK", "1") @@ -214,7 +165,7 @@ func main() { StderrKeep = os.Getenv("REDO_STDERR_KEEP") == "1" StderrSilent = os.Getenv("REDO_STDERR_SILENT") == "1" Debug = os.Getenv("REDO_DEBUG") == "1" - LogDone = os.Getenv("REDO_LOG_DONE") == "1" + LogWait = os.Getenv("REDO_LOG_WAIT") == "1" LogLock = os.Getenv("REDO_LOG_LOCK") == "1" LogJS = os.Getenv("REDO_LOG_JS") == "1" if Debug || os.Getenv("REDO_LOG_PID") == "1" { @@ -347,7 +298,10 @@ CmdSwitch: if err != nil { log.Println(err) } + rc := 0 if !ok || err != nil { - os.Exit(1) + rc = 1 } + trace(CDebug, "[%s] finished: %s %s", BuildUUID, cmdName, tgts) + os.Exit(rc) } diff --git a/ood.go b/ood.go index a7c2d22..4189ed4 100644 --- a/ood.go +++ b/ood.go @@ -31,6 +31,17 @@ import ( "go.cypherpunks.ru/recfile" ) +type TgtErr struct { + Tgt string + Err error +} + +func (e TgtErr) Unwrap() error { return e.Err } + +func (e TgtErr) Error() string { + return fmt.Sprintf("%s: %s", e.Tgt, e.Err) +} + func cwdAndTgt(tgt string) (string, string) { cwd, tgt := path.Split(tgt) cwd, err := filepath.Abs(cwd) @@ -58,11 +69,52 @@ func isBuiltNow(fdDep *os.File) (bool, *recfile.Reader, error) { return false, nil, err } if m["Build"] == "" { - return false, r, errors.New("dep. missing Build:") + return false, r, errors.New(".dep missing Build:") } return m["Build"] == BuildUUID, r, nil } +func rebuildStamped(cwd, tgt, depPath string) (string, error) { + relTgt, err := filepath.Rel(Cwd, path.Join(cwd, tgt)) + if err != nil { + panic(err) + } + errs := make(chan error, 1) + if err = runScript(relTgt, errs); err != nil { + return "", err + } + if err = <-errs; !isOkRun(err) { + return "", errors.New("build failed") + } + fdDep, err := os.Open(depPath) + if err != nil { + return "", err + } + defer fdDep.Close() + builtNow, r, err := isBuiltNow(fdDep) + if err != nil { + return "", err + } + if !builtNow { + return "", errors.New("is not built") + } + var stampTheir string + for { + m, err := r.NextMap() + if err != nil { + if err == io.EOF { + break + } + return "", err + } + if m["Type"] == "stamp" { + stampTheir = m["Hash"] + break + } + } + return stampTheir, nil +} + func isOOD(cwd, tgt string, level int) (bool, error) { tgtOrig := tgt indent := strings.Repeat(". ", level) @@ -79,7 +131,7 @@ func isOOD(cwd, tgt string, level int) (bool, error) { builtNow, r, err := isBuiltNow(fdDep) if err != nil { - return true, err + return true, TgtErr{tgtOrig, err} } if builtNow { trace(CDebug, "ood: %s%s -> already built", indent, tgtOrig) @@ -87,134 +139,114 @@ func isOOD(cwd, tgt string, level int) (bool, error) { } var stampOur string + ifcreates := []map[string]string{} + ifchanges := []map[string]string{} for { m, err := r.NextMap() if err != nil { if err == io.EOF { break } - return true, err + return true, TgtErr{tgtOrig, err} } switch m["Type"] { case "always": trace(CDebug, "ood: %s%s -> always", indent, tgtOrig) ood = true case "ifcreate": - theirTgt := m["Target"] - if theirTgt == "" { - return ood, errors.New("invalid format of dep." + tgtOrig) - } - if _, err = os.Stat(path.Join(cwd, theirTgt)); err == nil { - trace(CDebug, "ood: %s%s -> created", indent, tgtOrig) - ood = true - } + ifcreates = append(ifcreates, m) case "ifchange": - dep := m["Target"] - theirTs := m["Ctime"] - theirHsh := m["Hash"] - if dep == "" || theirTs == "" { - return ood, errors.New("invalid format of dep." + tgtOrig) - } - trace(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep) - fd, err := os.Open(path.Join(cwd, dep)) - if err != nil { - if os.IsNotExist(err) { - trace(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep) - ood = true - continue - } - return ood, err - } - defer fd.Close() - ts, err := fileCtime(fd) - if err != nil { - return ood, err - } - if theirTs == ts { - trace(CDebug, "ood: %s%s -> %s: same ctime", indent, tgtOrig, dep) - } else if NoHash || theirHsh == "" { - trace(CDebug, "ood: %s%s -> %s: ctime differs", indent, tgtOrig, dep) - ood = true - } else { - hsh, err := fileHash(fd) - if err != nil { - return ood, err - } - if theirHsh == hsh { - trace(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep) - } else { - trace(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep) - ood = true - } - } - fd.Close() - if ood { - continue - } - if dep == tgt { - trace(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep) - continue - } - if isSrc(cwd, dep) { - trace(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep) - continue - } else { - depOod, err := isOOD(cwd, dep, level+1) - if depOod { - ood = true - trace(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep) - } else { - trace(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep) - } - if err != nil { - return ood, err - } - } + ifchanges = append(ifchanges, m) case "stamp": stampOur = m["Hash"] trace(CDebug, "ood: %s%s -> stamped: %s", indent, tgtOrig, stampOur) default: - return ood, errors.New("invalid format of dep." + tgtOrig) + return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")} } } - if ood && stampOur != "" { - errs := make(chan error, 0) - trace(CDebug, "ood: %s%s running, because stamped", indent, tgtOrig) - relTgt, err := filepath.Rel(Cwd, path.Join(cwd, tgt)) - if err != nil { - return true, err + if ood { + goto StampCheck + } + + for _, m := range ifcreates { + theirTgt := m["Target"] + if theirTgt == "" { + return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")} } - if err = runScript(relTgt, errs); err != nil { - return true, err + if _, err = os.Stat(path.Join(cwd, theirTgt)); err == nil { + trace(CDebug, "ood: %s%s -> created", indent, tgtOrig) + ood = true + goto StampCheck } - if err = <-errs; err != nil { - return true, err + } + + for _, m := range ifchanges { + dep := m["Target"] + theirTs := m["Ctime"] + theirHsh := m["Hash"] + if dep == "" || theirTs == "" { + return ood, TgtErr{tgtOrig, errors.New("invalid format of .dep")} } - fdDep, err := os.Open(depPath) + trace(CDebug, "ood: %s%s -> %s: checking", indent, tgtOrig, dep) + fd, err := os.Open(path.Join(cwd, dep)) if err != nil { - return true, err + if os.IsNotExist(err) { + trace(CDebug, "ood: %s%s -> %s: not exists", indent, tgtOrig, dep) + ood = true + goto StampCheck + } + return ood, TgtErr{tgtOrig, err} } - defer fdDep.Close() - builtNow, r, err := isBuiltNow(fdDep) + defer fd.Close() + ts, err := fileCtime(fd) if err != nil { - return true, err - } - if !builtNow { - return true, fmt.Errorf("%s is not built", tgtOrig) + return ood, TgtErr{tgtOrig, err} } - var stampTheir string - for { - m, err := r.NextMap() + if theirTs == ts { + trace(CDebug, "ood: %s%s -> %s: same ctime", indent, tgtOrig, dep) + } else if NoHash || theirHsh == "" { + trace(CDebug, "ood: %s%s -> %s: ctime differs", indent, tgtOrig, dep) + ood = true + goto StampCheck + } else { + hsh, err := fileHash(fd) if err != nil { - if err == io.EOF { - break - } - return true, err + return ood, TgtErr{tgtOrig, err} } - if m["Type"] == "stamp" { - stampTheir = m["Hash"] - break + if theirHsh != hsh { + trace(CDebug, "ood: %s%s -> %s: hash differs", indent, tgtOrig, dep) + ood = true + goto StampCheck } + trace(CDebug, "ood: %s%s -> %s: same hash", indent, tgtOrig, dep) + } + fd.Close() + if dep == tgt { + trace(CDebug, "ood: %s%s -> %s: same target", indent, tgtOrig, dep) + continue + } + if isSrc(cwd, dep) { + trace(CDebug, "ood: %s%s -> %s: is source", indent, tgtOrig, dep) + continue + } + depOod, err := isOOD(cwd, dep, level+1) + if err != nil { + return ood, TgtErr{tgtOrig, err} + } + if depOod { + trace(CDebug, "ood: %s%s -> %s: ood", indent, tgtOrig, dep) + ood = true + goto StampCheck + } + trace(CDebug, "ood: %s%s -> %s: !ood", indent, tgtOrig, dep) + } + +StampCheck: + if ood && stampOur != "" { + trace(CDebug, "ood: %s%s run, because stamped", indent, tgtOrig) + stampTheir, err := rebuildStamped(cwd, tgt, depPath) + if err != nil { + return true, TgtErr{tgtOrig, err} } trace(CDebug, "ood: %s%s -> stamp: %s %s", indent, tgtOrig, stampOur, stampTheir) if stampOur == stampTheir { diff --git a/run.go b/run.go index 36e18d2..58b9f6d 100644 --- a/run.go +++ b/run.go @@ -103,25 +103,59 @@ func isModified(cwd, redoDir, tgt string) (bool, error) { return false, nil } +type RunErr struct { + Tgt string + DoFile string + Started *time.Time + Finished *time.Time + Err error +} + +func (e RunErr) Unwrap() error { return e.Err } + +func (e *RunErr) Name() string { + var name string + if e.DoFile == "" { + name = e.Tgt + } else { + name = fmt.Sprintf("%s (%s)", e.Tgt, e.DoFile) + } + if e.Finished == nil { + return name + } + return fmt.Sprintf("%s (%fsec)", name, e.Finished.Sub(*e.Started).Seconds()) +} + +func (e RunErr) Error() string { + return fmt.Sprintf("%s: %s", e.Name(), e.Err) +} + +func syncDir(dir string) error { + fd, err := os.Open(dir) + if err != nil { + return err + } + err = fd.Sync() + fd.Close() + return err +} + func runScript(tgt string, errs chan error) error { tgtOrig := tgt cwd, tgt := cwdAndTgt(tgt) redoDir := path.Join(cwd, RedoDir) - errf := func(err error) error { - return fmt.Errorf("%s: %s", tgtOrig, err) - } if err := mkdirs(redoDir); err != nil { - return errf(err) + return TgtErr{tgtOrig, err} } // Acquire lock fdLock, err := os.OpenFile( - path.Join(redoDir, "lock."+tgt), + path.Join(redoDir, tgt+LockSuffix), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.FileMode(0666), ) if err != nil { - return errf(err) + return TgtErr{tgtOrig, err} } lockRelease := func() { trace(CLock, "LOCK_UN: %s", fdLock.Name()) @@ -129,10 +163,12 @@ func runScript(tgt string, errs chan error) error { fdLock.Close() } trace(CLock, "LOCK_NB: %s", fdLock.Name()) + + // Waiting for job completion, already taken by someone else if err = unix.Flock(int(fdLock.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil { if uintptr(err.(syscall.Errno)) != uintptr(unix.EWOULDBLOCK) { fdLock.Close() - return errf(err) + return TgtErr{tgtOrig, err} } trace(CDebug, "waiting: %s", tgtOrig) Jobs.Add(1) @@ -146,7 +182,7 @@ func runScript(tgt string, errs chan error) error { fdDep, err := os.Open(path.Join(redoDir, tgt+DepSuffix)) if err != nil { if os.IsNotExist(err) { - err = fmt.Errorf("%s is not built", tgtOrig) + err = errors.New("was not built") } goto Finish } @@ -155,11 +191,11 @@ func runScript(tgt string, errs chan error) error { goto Finish } if !builtNow { - err = fmt.Errorf("%s is not built", tgtOrig) + err = errors.New("was not built") } Finish: if err != nil { - err = errf(err) + err = TgtErr{tgtOrig, err} } errs <- err }() @@ -170,24 +206,20 @@ func runScript(tgt string, errs chan error) error { modified, err := isModified(cwd, redoDir, tgt) if err != nil { lockRelease() - return errf(err) + return TgtErr{tgtOrig, err} } if modified { trace(CWarn, "%s externally modified: not redoing", tgtOrig) lockRelease() - Jobs.Add(1) - go func() { - errs <- nil - Jobs.Done() - }() - return nil + errs <- nil + return TgtErr{tgtOrig, err} } - // Start preparing dep. + // Start preparing .dep fdDep, err := tempfile(redoDir, tgt+DepSuffix) if err != nil { lockRelease() - return errf(err) + return TgtErr{tgtOrig, err} } cleanup := func() { lockRelease() @@ -198,22 +230,22 @@ func runScript(tgt string, errs chan error) error { recfile.Field{Name: "Build", Value: BuildUUID}, ); err != nil { cleanup() - return errf(err) + return TgtErr{tgtOrig, err} } // Find .do doFile, upLevels, err := findDo(fdDep, cwd, tgt) if err != nil { cleanup() - return errf(err) + return TgtErr{tgtOrig, err} } if doFile == "" { cleanup() - return errf(errors.New("no .do found")) + return TgtErr{tgtOrig, errors.New("no .do found")} } if err = writeDep(fdDep, cwd, doFile); err != nil { cleanup() - return errf(err) + return TgtErr{tgtOrig, err} } // Determine basename and DIRPREFIX @@ -226,12 +258,12 @@ func runScript(tgt string, errs chan error) error { } cwd = path.Clean(cwd) basename := tgt + runErr := RunErr{Tgt: tgtOrig} if strings.HasPrefix(doFile, "default.") { basename = tgt[:len(tgt)-(len(doFile)-len("default.")-len(".do"))-1] - trace(CRedo, "%s (%s)", tgtOrig, doFile) - } else { - trace(CRedo, "%s", tgtOrig) + runErr.DoFile = doFile } + trace(CWait, "%s", runErr.Name()) doFile = path.Base(doFile) // Prepare command line @@ -245,13 +277,13 @@ func runScript(tgt string, errs chan error) error { fd, err := os.Open(path.Join(cwd, doFile)) if err != nil { cleanup() - return errf(err) + return TgtErr{tgtOrig, err} } buf := make([]byte, 512) n, err := fd.Read(buf) if err != nil { cleanup() - return errf(err) + return TgtErr{tgtOrig, err} } if n > 3 && string(buf[:3]) == "#!/" { // Shebanged @@ -259,7 +291,7 @@ func runScript(tgt string, errs chan error) error { nlIdx := strings.Index(t, "\n") if nlIdx == -1 { cleanup() - return errf(errors.New("not fully read shebang")) + return TgtErr{tgtOrig, errors.New("not fully read shebang")} } args = strings.Split(t[:nlIdx], " ") cmdName, args = args[0], args[1:] @@ -279,7 +311,7 @@ func runScript(tgt string, errs chan error) error { fdStdout, err := tempfile(cwd, tgt) if err != nil { cleanup() - return errf(err) + return TgtErr{tgtOrig, err} } tmpPath := fdStdout.Name() + ".3" // and for $3 args = append(args, tgt, basename, path.Base(tmpPath)) @@ -291,9 +323,14 @@ func runScript(tgt string, errs chan error) error { cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", RedoDirPrefixEnv, dirPrefix)) cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", RedoBuildUUIDEnv, BuildUUID)) childStderrPrefix := tempsuffix() - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", RedoStderrPrefixEnv, childStderrPrefix)) + cmd.Env = append(cmd.Env, fmt.Sprintf( + "%s=%s", RedoStderrPrefixEnv, childStderrPrefix, + )) + cmd.ExtraFiles = append(cmd.ExtraFiles, fdDep) - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoDepFdEnv, 3+0)) + fdNum := 0 + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoDepFdEnv, 3+fdNum)) + fdNum++ if JSR == nil { // infinite jobs cmd.Env = append(cmd.Env, fmt.Sprintf("%s=NO", RedoJSRFdEnv)) @@ -301,8 +338,10 @@ func runScript(tgt string, errs chan error) error { } else { cmd.ExtraFiles = append(cmd.ExtraFiles, JSR) cmd.ExtraFiles = append(cmd.ExtraFiles, JSW) - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoJSRFdEnv, 3+1)) - cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoJSWFdEnv, 3+2)) + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoJSRFdEnv, 3+fdNum)) + fdNum++ + cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%d", RedoJSWFdEnv, 3+fdNum)) + fdNum++ } // Preparing stderr @@ -319,18 +358,18 @@ func runScript(tgt string, errs chan error) error { ) if err != nil { cleanup() - return errf(err) + return TgtErr{tgtOrig, err} } fdStderr.Truncate(0) } - trace(CDebug, "sh: %s: %s %s [%s]", tgtOrig, cmdName, args, cwd) + shCtx := fmt.Sprintf("sh: %s: %s %s [%s]", tgtOrig, cmdName, args, cwd) + trace(CDebug, "%s", shCtx) Jobs.Add(1) go func() { - jsAcquire() - started := time.Now() + jsAcquire(shCtx) defer func() { - jsRelease() + jsRelease(shCtx) lockRelease() fdDep.Close() fdStdout.Close() @@ -341,13 +380,14 @@ func runScript(tgt string, errs chan error) error { os.Remove(fdStdout.Name()) os.Remove(tmpPath) os.Remove(fdLock.Name()) - finished := time.Now() - trace(CDone, "%s (%fsec)", tgtOrig, finished.Sub(started).Seconds()) Jobs.Done() }() + started := time.Now() + runErr.Started = &started err := cmd.Start() if err != nil { - errs <- errf(err) + runErr.Err = err + errs <- runErr return } pid := fmt.Sprintf("[%d]", cmd.Process.Pid) @@ -384,27 +424,33 @@ func runScript(tgt string, errs chan error) error { // Wait for job completion <-stderrTerm err = cmd.Wait() + finished := time.Now() + runErr.Finished = &finished if err != nil { - errs <- errf(err) + runErr.Err = err + errs <- runErr return } // Does it produce both stdout and tmp? fiStdout, err := os.Stat(fdStdout.Name()) if err != nil { - errs <- errf(err) + runErr.Err = err + errs <- runErr return } tmpExists := false _, err = os.Stat(tmpPath) if err == nil { if fiStdout.Size() > 0 { - errs <- errf(fmt.Errorf("%s created both tmp and stdout", tgtOrig)) + runErr.Err = errors.New("created both tmp and stdout") + errs <- runErr return } tmpExists = true } else if !os.IsNotExist(err) { - errs <- errf(err) + runErr.Err = err + errs <- runErr return } @@ -413,8 +459,7 @@ func runScript(tgt string, errs chan error) error { if tmpExists { fd, err = os.Open(tmpPath) if err != nil { - errs <- errf(err) - return + goto Finish } defer fd.Close() } else if fiStdout.Size() > 0 { @@ -423,63 +468,53 @@ func runScript(tgt string, errs chan error) error { // Do we need to ifcreate it, of ifchange with renaming? if fd == nil { - if err = ifcreate(fdDep, tgt); err != nil { - errs <- errf(err) - return + err = ifcreate(fdDep, tgt) + if err != nil { + goto Finish } } else { if !NoSync { - if err = fd.Sync(); err != nil { - errs <- errf(err) - return + err = fd.Sync() + if err != nil { + goto Finish } } - if err = os.Rename(fd.Name(), path.Join(cwdOrig, tgt)); err != nil { - errs <- errf(err) - return + err = os.Rename(fd.Name(), path.Join(cwdOrig, tgt)) + if err != nil { + goto Finish } if !NoSync { - fdDir, err := os.Open(cwdOrig) + err = syncDir(cwdOrig) if err != nil { - errs <- errf(err) - return - } - defer fdDir.Close() - if err = fdDir.Sync(); err != nil { - errs <- errf(err) - return + goto Finish } } - if err = writeDep(fdDep, cwdOrig, tgt); err != nil { - errs <- errf(err) - return + err = writeDep(fdDep, cwdOrig, tgt) + if err != nil { + goto Finish } } - // Commit dep. + // Commit .dep if !NoSync { - if err = fdDep.Sync(); err != nil { - errs <- errf(err) - return + err = fdDep.Sync() + if err != nil { + goto Finish } } - if err = os.Rename(fdDep.Name(), path.Join(redoDir, tgt+DepSuffix)); err != nil { - errs <- errf(err) - return + err = os.Rename(fdDep.Name(), path.Join(redoDir, tgt+DepSuffix)) + if err != nil { + goto Finish } if !NoSync { - fdDir, err := os.Open(redoDir) + err = syncDir(redoDir) if err != nil { - errs <- errf(err) - return - } - defer fdDir.Close() - if err = fdDir.Sync(); err != nil { - errs <- errf(err) - return + goto Finish } } - errs <- nil + Finish: + runErr.Err = err + errs <- runErr }() return nil } diff --git a/usage.go b/usage.go index 704873c..d5dae5e 100644 --- a/usage.go +++ b/usage.go @@ -1,3 +1,20 @@ +/* +goredo -- redo implementation on pure Go +Copyright (C) 2020 Sergey Matveev + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + package main import ( -- 2.44.0