Skip to content

Commit ce69b51

Browse files
committed
reexec: add context-aware entrypoints and Dispatch
Follow-up to 7aa8514; allow entrypoints to be invoked with a context and to return an error; - Add RegisterContext to register context-aware entrypoints. - Add Dispatch and DispatchContext, both returning (handled, error). - Init is kept for backward compatibility or cases where context or errors don't have to be handled. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 7f6a0a1 commit ce69b51

3 files changed

Lines changed: 269 additions & 32 deletions

File tree

reexec/example_programmatic_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,20 @@ import (
1010
)
1111

1212
func init() {
13-
reexec.Register("example-child", func() {
13+
reexec.RegisterContext("example-child", func(ctx context.Context) error {
1414
fmt.Println("Hello from example-child entrypoint")
15+
return nil
1516
})
1617
}
1718

1819
// Example_programmatic demonstrates using reexec to programmatically
1920
// re-execute the current binary.
2021
func Example_programmatic() {
21-
if reexec.Init() {
22+
if ok, err := reexec.DispatchContext(context.Background()); ok {
23+
if err != nil {
24+
fmt.Fprintln(os.Stderr, "entrypoint failed:", err)
25+
os.Exit(1)
26+
}
2227
// Matched a reexec entrypoint; stop normal main execution.
2328
return
2429
}

reexec/reexec.go

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
// }
4141
//
4242
// func main() {
43-
// if reexec.Init() {
43+
// if ok, _ := reexec.Dispatch(); ok {
4444
// // Matched a reexec entrypoint; stop normal main execution.
4545
// return
4646
// }
@@ -80,15 +80,32 @@ import (
8080
"github.com/moby/sys/reexec/internal/reexecoverride"
8181
)
8282

83-
var entrypoints = make(map[string]func())
83+
var entrypoints = make(map[string]func(context.Context) error)
8484

8585
// Register associates name with an entrypoint function to be executed when
8686
// the current binary is invoked with argv[0] equal to name.
8787
//
8888
// Register is not safe for concurrent use; entrypoints must be registered
89-
// during program initialization, before calling [Init].
89+
// during program initialization, before calling [Dispatch] or [Init].
9090
// It panics if name contains a path component or is already registered.
9191
func Register(name string, entrypoint func()) {
92+
registerEntrypoint(name, func(context.Context) error {
93+
entrypoint()
94+
return nil
95+
})
96+
}
97+
98+
// RegisterContext is like [Register] but the entrypoint receives a context and
99+
// reports success or failure by returning an error.
100+
//
101+
// RegisterContext is not safe for concurrent use; entrypoints must be
102+
// registered during program initialization, before calling [Dispatch] or [Init].
103+
// It panics if name contains a path component or is already registered.
104+
func RegisterContext(name string, entrypoint func(context.Context) error) {
105+
registerEntrypoint(name, entrypoint)
106+
}
107+
108+
func registerEntrypoint(name string, entrypoint func(context.Context) error) {
92109
if filepath.Base(name) != name {
93110
panic(fmt.Sprintf("reexec func does not expect a path component: %q", name))
94111
}
@@ -99,18 +116,45 @@ func Register(name string, entrypoint func()) {
99116
entrypoints[name] = entrypoint
100117
}
101118

119+
// Dispatch checks whether the current process was invoked under a registered
120+
// name (based on filepath.Base(os.Args[0])).
121+
//
122+
// If a matching entrypoint is found, it is executed and Dispatch reports a
123+
// match with ok. If ok is false, err is always nil. If ok is true, err is the
124+
// entrypoint's return value.
125+
//
126+
// In the ok=true case, the caller should stop normal main execution (typically
127+
// by returning from main or calling os.Exit).
128+
func Dispatch() (bool, error) {
129+
return dispatch(context.Background())
130+
}
131+
132+
// DispatchContext is like [Dispatch] but includes a context.
133+
func DispatchContext(ctx context.Context) (bool, error) {
134+
return dispatch(ctx)
135+
}
136+
102137
// Init checks whether the current process was invoked under a registered name
103138
// (based on filepath.Base(os.Args[0])).
104139
//
105140
// If a matching entrypoint is found, it is executed and Init returns true. In
106141
// that case, the caller should stop normal main execution. If no match is found,
107142
// Init returns false and normal execution should continue.
143+
//
144+
// Init is provided for compatibility with older callers. New code should
145+
// prefer [Dispatch] or [DispatchContext], which also return the entrypoint's
146+
// error and allow the caller to decide how to report failures.
108147
func Init() bool {
109-
if entrypoint, ok := entrypoints[filepath.Base(os.Args[0])]; ok {
110-
entrypoint()
111-
return true
148+
ok, _ := dispatch(context.Background())
149+
return ok
150+
}
151+
152+
func dispatch(ctx context.Context) (bool, error) {
153+
entrypoint, ok := entrypoints[filepath.Base(os.Args[0])]
154+
if !ok {
155+
return false, nil
112156
}
113-
return false
157+
return true, entrypoint(ctx)
114158
}
115159

116160
// Command returns an [*exec.Cmd] configured to re-execute the current binary,

0 commit comments

Comments
 (0)