Skip to content

Commit 1e02613

Browse files
committed
implement --hooks-async
1 parent 534976a commit 1e02613

File tree

3 files changed

+74
-28
lines changed

3 files changed

+74
-28
lines changed

main.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ func main() {
207207
flGroupWrite := pflag.Bool("group-write",
208208
envBool(false, "GITSYNC_GROUP_WRITE", "GIT_SYNC_GROUP_WRITE"),
209209
"ensure that all data (repo, worktrees, etc.) is group writable")
210-
flStaleWorktreeTimeout := pflag.Duration("stale-worktree-timeout", envDuration(0, "GITSYNC_STALE_WORKTREE_TIMEOUT"),
210+
flStaleWorktreeTimeout := pflag.Duration("stale-worktree-timeout",
211+
envDuration(0, "GITSYNC_STALE_WORKTREE_TIMEOUT"),
211212
"how long to retain non-current worktrees")
212213

213214
flExechookCommand := pflag.String("exechook-command",
@@ -236,6 +237,10 @@ func main() {
236237
envDuration(3*time.Second, "GITSYNC_WEBHOOK_BACKOFF", "GIT_SYNC_WEBHOOK_BACKOFF"),
237238
"the time to wait before retrying a failed webhook")
238239

240+
flHooksAsync := pflag.Bool("hooks-async",
241+
envBool(true, "GITSYNC_HOOKS_ASYNC", "GIT_SYNC_HOOKS_ASYNC"),
242+
"run hooks asynchronously")
243+
239244
flUsername := pflag.String("username",
240245
envString("", "GITSYNC_USERNAME", "GIT_SYNC_USERNAME"),
241246
"the username to use for git auth")
@@ -844,6 +849,7 @@ func main() {
844849
hook.NewHookData(),
845850
log,
846851
*flOneTime,
852+
*flHooksAsync,
847853
)
848854
go webhookRunner.Run(context.Background())
849855
}
@@ -868,6 +874,7 @@ func main() {
868874
hook.NewHookData(),
869875
log,
870876
*flOneTime,
877+
*flHooksAsync,
871878
)
872879
go exechookRunner.Run(context.Background())
873880
}

pkg/hook/hook.go

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ func (d *hookData) send(newHash string) {
8989
}
9090

9191
// NewHookRunner returns a new HookRunner.
92-
func NewHookRunner(hook Hook, backoff time.Duration, data *hookData, log logintf, oneTime bool) *HookRunner {
93-
hr := &HookRunner{hook: hook, backoff: backoff, data: data, log: log}
94-
if oneTime {
95-
hr.oneTimeResult = make(chan bool, 1)
92+
func NewHookRunner(hook Hook, backoff time.Duration, data *hookData, log logintf, oneTime bool, async bool) *HookRunner {
93+
hr := &HookRunner{hook: hook, backoff: backoff, data: data, log: log, oneTime: oneTime, async: async}
94+
if oneTime || !async {
95+
hr.result = make(chan bool, 1)
9696
}
9797
return hr
9898
}
@@ -107,9 +107,13 @@ type HookRunner struct {
107107
data *hookData
108108
// Logger
109109
log logintf
110-
// Used to send a status result when running in one-time mode.
110+
// Used to send a status result when running in one-time or non-async mode.
111111
// Should be initialised to a buffered channel of size 1.
112-
oneTimeResult chan bool
112+
result chan bool
113+
// Bool for whether this is a one-time hook or not.
114+
oneTime bool
115+
// Bool for whether this is an async hook or not.
116+
async bool
113117
}
114118

115119
// Just the logr methods we need in this package.
@@ -120,8 +124,17 @@ type logintf interface {
120124
}
121125

122126
// Send sends hash to hookdata.
123-
func (r *HookRunner) Send(hash string) {
127+
func (r *HookRunner) Send(hash string) error {
124128
r.data.send(hash)
129+
if !r.async {
130+
r.log.V(1).Info("waiting for completion", "hash", hash, "name", r.hook.Name())
131+
err := r.WaitForCompletion()
132+
r.log.V(1).Info("hook completed", "hash", hash, "err", err, "name", r.hook.Name())
133+
if err != nil {
134+
return err
135+
}
136+
}
137+
return nil
125138
}
126139

127140
// Run waits for trigger events from the channel, and run hook when triggered.
@@ -144,45 +157,49 @@ func (r *HookRunner) Run(ctx context.Context) {
144157
r.log.Error(err, "hook failed", "hash", hash, "retry", r.backoff)
145158
updateHookRunCountMetric(r.hook.Name(), "error")
146159
// don't want to sleep unnecessarily terminating anyways
147-
r.sendOneTimeResultAndTerminate(false)
160+
r.sendResult(false)
148161
time.Sleep(r.backoff)
149162
} else {
150163
updateHookRunCountMetric(r.hook.Name(), "success")
151164
lastHash = hash
152-
r.sendOneTimeResultAndTerminate(true)
165+
r.sendResult(true)
153166
break
154167
}
155168
}
156169
}
157170
}
158171

159-
// If oneTimeResult is nil, does nothing. Otherwise, forwards the caller
160-
// provided success status (as a boolean) of HookRunner to receivers of
161-
// oneTimeResult, closes said chanel, and terminates this goroutine.
162-
// Using this function to write to oneTimeResult ensures it is only ever
163-
// written to once.
164-
func (r *HookRunner) sendOneTimeResultAndTerminate(completedSuccessfully bool) {
165-
if r.oneTimeResult != nil {
166-
r.oneTimeResult <- completedSuccessfully
167-
close(r.oneTimeResult)
172+
func (r *HookRunner) sendResult(completedSuccessfully bool) {
173+
// if onetime is true, we send the result then exit
174+
if r.oneTime {
175+
r.result <- completedSuccessfully
176+
close(r.result)
168177
runtime.Goexit()
178+
} else if !r.async {
179+
// if onetime is false, and we've set non-async we send but don't exit.
180+
r.result <- completedSuccessfully
169181
}
182+
// if neither oneTime nor !async, we do nothing here.
170183
}
171184

172185
// WaitForCompletion waits for HookRunner to send completion message to
173186
// calling thread and returns either true if HookRunner executed successfully
174187
// and some error otherwise.
175-
// Assumes that r.oneTimeResult is not nil, but if it is, returns an error.
188+
// Assumes that either r.oneTime or !r.async, otherwise returns an error.
176189
func (r *HookRunner) WaitForCompletion() error {
177190
// Make sure function should be called
178-
if r.oneTimeResult == nil {
191+
if r.result == nil {
179192
return fmt.Errorf("HookRunner.WaitForCompletion called on async runner")
180193
}
181194

182-
// Perform wait on HookRunner
183-
hookRunnerFinishedSuccessfully := <-r.oneTimeResult
184-
if !hookRunnerFinishedSuccessfully {
185-
return fmt.Errorf("hook completed with error")
195+
// If oneTimeResult is not nil, we wait for its result.
196+
if r.result != nil {
197+
hookRunnerFinishedSuccessfully := <-r.result
198+
r.log.V(1).Info("hook completed", "success", hookRunnerFinishedSuccessfully,
199+
"oneTime", r.oneTime, "async", r.async, "name", r.hook.Name())
200+
if !hookRunnerFinishedSuccessfully {
201+
return fmt.Errorf("hook completed with error")
202+
}
186203
}
187204

188205
return nil

test_e2e.sh

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2406,7 +2406,7 @@ function e2e::exechook_fail_retry() {
24062406
--exechook-command="/$EXECHOOK_COMMAND_FAIL" \
24072407
--exechook-backoff=1s \
24082408
&
2409-
sleep 3 # give it time to retry
2409+
sleep 4 # give it time to retry
24102410

24112411
# Check that exechook was called
24122412
assert_file_lines_ge "$RUNLOG" 2
@@ -2486,6 +2486,29 @@ function e2e::exechook_startup_after_crash() {
24862486
assert_file_lines_eq "$RUNLOG" 1
24872487
}
24882488

2489+
##############################################
2490+
# Test exechook-success with --hooks-async=false
2491+
##############################################
2492+
function e2e::exechook_success_hooks_non_async() {
2493+
cat /dev/null > "$RUNLOG"
2494+
2495+
GIT_SYNC \
2496+
--hooks-async=false \
2497+
--period=100ms \
2498+
--repo="file://$REPO" \
2499+
--root="$ROOT" \
2500+
--link="link" \
2501+
--exechook-command="/$EXECHOOK_COMMAND_SLEEPY" \
2502+
&
2503+
sleep 5 # give it time to run
2504+
assert_link_exists "$ROOT/link"
2505+
assert_file_exists "$ROOT/link/file"
2506+
assert_file_exists "$ROOT/link/exechook"
2507+
assert_file_eq "$ROOT/link/file" "${FUNCNAME[0]}"
2508+
assert_file_eq "$ROOT/link/exechook" "${FUNCNAME[0]}"
2509+
assert_file_eq "$ROOT/link/exechook-env" "$EXECHOOK_ENVKEY=$EXECHOOK_ENVVAL"
2510+
}
2511+
24892512
##############################################
24902513
# Test webhook success
24912514
##############################################
@@ -3797,8 +3820,7 @@ for t; do
37973820
fi
37983821
remove_containers || true
37993822
run=$((run+1))
3800-
done
3801-
if [[ "$test_ret" != 0 ]]; then
3823+
done if [[ "$test_ret" != 0 ]]; then
38023824
final_ret=1
38033825
failures+=("$t (log: ${log}.*)")
38043826
fi

0 commit comments

Comments
 (0)