Skip to content

Commit 4f29c3a

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 4f29c3a

3 files changed

Lines changed: 271 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: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
// }
4141
//
4242
// func main() {
43-
// if reexec.Init() {
43+
// if reexec.Init(); ok {
44+
// fmt.Println("an error happened:", err)
45+
//
4446
// // Matched a reexec entrypoint; stop normal main execution.
4547
// return
4648
// }
@@ -80,15 +82,32 @@ import (
8082
"github.com/moby/sys/reexec/internal/reexecoverride"
8183
)
8284

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

8587
// Register associates name with an entrypoint function to be executed when
8688
// the current binary is invoked with argv[0] equal to name.
8789
//
8890
// Register is not safe for concurrent use; entrypoints must be registered
89-
// during program initialization, before calling [Init].
91+
// during program initialization, before calling [Dispatch] or [Init].
9092
// It panics if name contains a path component or is already registered.
9193
func Register(name string, entrypoint func()) {
94+
registerEntrypoint(name, func(context.Context) error {
95+
entrypoint()
96+
return nil
97+
})
98+
}
99+
100+
// RegisterContext is like [Register] but the entrypoint receives a context and
101+
// reports success or failure by returning an error.
102+
//
103+
// RegisterContext is not safe for concurrent use; entrypoints must be
104+
// registered during program initialization, before calling [Dispatch] or [Init].
105+
// It panics if name contains a path component or is already registered.
106+
func RegisterContext(name string, entrypoint func(context.Context) error) {
107+
registerEntrypoint(name, entrypoint)
108+
}
109+
110+
func registerEntrypoint(name string, entrypoint func(context.Context) error) {
92111
if filepath.Base(name) != name {
93112
panic(fmt.Sprintf("reexec func does not expect a path component: %q", name))
94113
}
@@ -99,18 +118,45 @@ func Register(name string, entrypoint func()) {
99118
entrypoints[name] = entrypoint
100119
}
101120

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

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

0 commit comments

Comments
 (0)