diff --git a/internal/nomad/nomad.go b/internal/nomad/nomad.go index 096175e..5232542 100644 --- a/internal/nomad/nomad.go +++ b/internal/nomad/nomad.go @@ -374,9 +374,12 @@ func (a *APIClient) executeCommandInteractivelyWithStderr(allocationID string, c stderrExitChan := make(chan int) go func() { + readingContext, cancel := context.WithCancel(ctx) + defer cancel() + // Catch stderr in separate execution. exit, err := a.Execute(allocationID, ctx, stderrFifoCommand(currentNanoTime), true, - nullio.Reader{}, stderr, io.Discard) + nullio.Reader{Ctx: readingContext}, stderr, io.Discard) if err != nil { log.WithError(err).WithField("runner", allocationID).Warn("Stderr task finished with error") } diff --git a/pkg/nullio/nullio.go b/pkg/nullio/nullio.go index e006ccb..1c39c6a 100644 --- a/pkg/nullio/nullio.go +++ b/pkg/nullio/nullio.go @@ -1,16 +1,31 @@ package nullio import ( + "context" "fmt" "io" ) -// Reader is a struct that implements the io.Reader interface. Read does not return when called. -type Reader struct{} +// Reader is a struct that implements the io.Reader interface. +// Read does not return when called until the context is done. It is used to avoid reading anything and returning io.EOF +// before the context has finished. +// For example the reader is used by the execution that fetches the stderr stream from Nomad. We do not have a stdin +// that we want to send to Nomad. But we still have to pass Nomad a reader. +// Normally readers send an io.EOF as soon as they have nothing more to read. But we want to avoid this, because in that +// case Nomad will abort (not the execution but) the transmission. +// Why should the reader not just always return 0, nil? Because Nomad reads in an endless loop and thus a busy waiting +// is avoided. +type Reader struct { + Ctx context.Context +} func (r Reader) Read(_ []byte) (int, error) { - // An empty select blocks forever. - select {} + if r.Ctx.Err() != nil { + return 0, io.EOF + } + + <-r.Ctx.Done() + return 0, io.EOF } // ReadWriter implements io.ReadWriter. It does not return from Read and discards everything on Write. diff --git a/pkg/nullio/nullio_test.go b/pkg/nullio/nullio_test.go index 156b926..e31166f 100644 --- a/pkg/nullio/nullio_test.go +++ b/pkg/nullio/nullio_test.go @@ -1,8 +1,9 @@ package nullio import ( + "context" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "io" "testing" "time" ) @@ -10,12 +11,15 @@ import ( const shortTimeout = 100 * time.Millisecond func TestReaderDoesNotReturnImmediately(t *testing.T) { - reader := &Reader{} + readingContext, cancel := context.WithCancel(context.Background()) + defer cancel() + + reader := &Reader{readingContext} readerReturned := make(chan bool) go func() { p := make([]byte, 0, 5) _, err := reader.Read(p) - require.NoError(t, err) + assert.ErrorIs(t, io.EOF, err) close(readerReturned) }()