Unbuffer callbacks so they are executed in order

This commit is contained in:
IamTheFij 2024-10-23 13:34:44 -07:00
parent 86a55d16ea
commit 9f32c6c43f
2 changed files with 34 additions and 12 deletions

View File

@ -45,7 +45,7 @@ func NewShellRunnerWithShell(shell string) *ShellRunner {
cmdQueue: make(chan func() *CommandResult), cmdQueue: make(chan func() *CommandResult),
results: make(chan *CommandResult, MAX_RESULTS), results: make(chan *CommandResult, MAX_RESULTS),
stopChan: make(chan struct{}), stopChan: make(chan struct{}),
callbacks: make(chan func(), MAX_RESULTS), callbacks: make(chan func()),
shell: shell, shell: shell,
activeCmds: make(map[*exec.Cmd]struct{}), activeCmds: make(map[*exec.Cmd]struct{}),
isStopped: true, isStopped: true,
@ -82,6 +82,8 @@ func (sr *ShellRunner) Start() {
// AddCommand adds a shell command to be executed with an optional callback. // AddCommand adds a shell command to be executed with an optional callback.
// No commands can be added if the runner has been stopped or not yet started. // No commands can be added if the runner has been stopped or not yet started.
// The callback is executed asynchronously after the command has completed.
// The order of command execution and callback invocation can be expected to be preserved.
func (sr *ShellRunner) AddCommand(command string, callback func(*CommandResult)) error { func (sr *ShellRunner) AddCommand(command string, callback func(*CommandResult)) error {
sr.mu.Lock() sr.mu.Lock()
defer sr.mu.Unlock() defer sr.mu.Unlock()

View File

@ -1,6 +1,7 @@
package tortoise_test package tortoise_test
import ( import (
"sync"
"testing" "testing"
"time" "time"
@ -49,29 +50,48 @@ func TestShellRunnerCallback(t *testing.T) {
runner.Start() runner.Start()
// Test command with callback // Test command with callback
done := make(chan struct{}) outputString := ""
callbackWait := sync.WaitGroup{}
callbackReached := false callbackWait.Add(1)
if err := runner.AddCommand("echo callback test", func(result *tortoise.CommandResult) { if err := runner.AddCommand("echo callback a", func(result *tortoise.CommandResult) {
callbackReached = true if result.Output != "callback a\n" {
if result.Output != "callback test\n" { t.Fatalf("expected 'callback a', got '%s'", result.Output)
t.Fatalf("expected 'callback test', got '%s'", result.Output)
} }
close(done) outputString = outputString + "a"
callbackWait.Done()
}); err != nil { }); err != nil {
t.Fatalf("unexpected error adding command: %v", err) t.Fatalf("unexpected error adding command: %v", err)
} }
// Timeout waiting for callback callbackWait.Add(1)
if err := runner.AddCommand("echo callback b", func(result *tortoise.CommandResult) {
if result.Output != "callback b\n" {
t.Fatalf("expected 'callback b', got '%s'", result.Output)
}
outputString = outputString + "b"
callbackWait.Done()
}); err != nil {
t.Fatalf("unexpected error adding command: %v", err)
}
// Timeout waiting for callbacks
done := make(chan struct{})
go func() {
callbackWait.Wait()
close(done)
}()
select { select {
case <-done: case <-done:
case <-time.After(2 * time.Second): case <-time.After(2 * time.Second):
t.Fatal("callback timed out") t.Fatal("callbacks timed out")
} }
if !callbackReached { if outputString != "ab" {
t.Fatal("callback was not reached") t.Fatal("callbacks was not reached in order:", outputString)
} }
runner.Stop() runner.Stop()