Run a verb from the controller's own running image, and refuse what it cannot run as a handover (hq issue 289)
The witness moves a running build aside into a directory the controller's user cannot enter, then deletes it; a verb exec'd from os.Executable() in that window failed with permission denied. /proc/self/exe stays valid while the process lives. A verb that still cannot start, or arrives while the controller stops, is refused with link.ErrHandingOver and marked retry: handing-over.
This commit is contained in:
@@ -740,12 +740,18 @@ func isWhyFlag(word string) bool {
|
||||
}
|
||||
|
||||
// runVerb runs this binary with the given command line and gathers what it said.
|
||||
//
|
||||
// **This binary is the image this process runs, never the file at the path it started from**
|
||||
// (novox/hq issue 289): the node-engine's witness moves a running build aside when it places the next
|
||||
// one, into a directory only it may enter, and deletes it once the next is proved — while this process
|
||||
// may still be serving. A verb run from the path then failed with "permission denied" for the seconds
|
||||
// the old controller still answered. selfCommand runs what this process is running, wherever its file
|
||||
// went; a verb it still cannot start is refused as a handover, which the caller asks again.
|
||||
func runVerb(ctx context.Context, argv []string) (verbAnswer, error) {
|
||||
self, err := os.Executable()
|
||||
if err != nil {
|
||||
return verbAnswer{}, err
|
||||
if handingOver.Load() {
|
||||
return verbAnswer{}, fmt.Errorf("%w: this controller is stopping and runs no new command", link.ErrHandingOver)
|
||||
}
|
||||
cmd := exec.CommandContext(ctx, self, argv...)
|
||||
cmd := selfCommand(ctx, argv)
|
||||
// The same environment: the stores' credentials, the bus, the broker — everything a command run
|
||||
// from a shell in this container would have, because it is that.
|
||||
cmd.Env = os.Environ()
|
||||
@@ -773,6 +779,12 @@ func runVerb(ctx context.Context, argv []string) (verbAnswer, error) {
|
||||
var exit *exec.ExitError
|
||||
if runErr != nil && !errors.As(runErr, &exit) {
|
||||
// Not the command refusing — the command not running at all, which is this process's fault.
|
||||
if errors.Is(runErr, os.ErrPermission) || errors.Is(runErr, os.ErrNotExist) {
|
||||
// Its own image unreachable: a build replaced under a process that has not yet stopped.
|
||||
// Refused as a handover, so the caller asks the controller that follows.
|
||||
return answer, fmt.Errorf("%w: could not run %s from this controller's own build: %v",
|
||||
link.ErrHandingOver, strings.Join(argv, " "), runErr)
|
||||
}
|
||||
return answer, fmt.Errorf("could not run %s: %w", strings.Join(argv, " "), runErr)
|
||||
}
|
||||
return answer, nil
|
||||
|
||||
Reference in New Issue
Block a user