Skip to content

Commit 53a5d14

Browse files
arskaclaude
andcommitted
Fix truncated stdin backups by closing pipe before signaling done
Application-aware backups (stdin backups from pod exec) were being truncated because the pipe writer was closed after signaling done to the backup trigger. This created a race condition: 1. StreamWithContext finishes writing data to the pipe 2. done <- true signals the backup trigger to call cmd.Wait() 3. defer stdoutWriter.Close() hasn't executed yet 4. restic finishes before all data flows through the pipe Fix: close the pipe writer before signaling done, ensuring restic's stdin receives all data and EOF before the backup command exits. Also improved error handling: use CloseWithError on stream failure instead of os.Exit(1), propagating the error through the pipe to the reader. Fixes #1109 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Aarno Aukia <aarno.aukia@vshn.ch>
1 parent 2a90b53 commit 53a5d14

1 file changed

Lines changed: 14 additions & 10 deletions

File tree

restic/kubernetes/pod_exec.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"fmt"
66
"io"
7-
"os"
87
"strings"
98

109
"github.com/firepear/qsplit/v2"
@@ -62,22 +61,27 @@ func PodExec(pod BackupPod, log logr.Logger) (*ExecData, error) {
6261
var stdoutReader, stdoutWriter = io.Pipe()
6362
done := make(chan bool, 1)
6463
go func() {
65-
err = exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
64+
streamErr := exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
6665
Stdin: nil,
6766
Stdout: stdoutWriter,
6867
Stderr: logging.NewErrorWriter(log.WithName(pod.PodName)),
6968
Tty: false,
7069
})
7170

72-
defer stdoutWriter.Close()
73-
done <- true
74-
75-
if err != nil {
76-
execLogger.Error(err, "streaming data failed", "namespace", pod.Namespace, "pod", pod.PodName)
77-
// we just completely hard fail the whole backup pod
78-
os.Exit(1)
79-
return
71+
if streamErr != nil {
72+
execLogger.Error(streamErr, "streaming data failed", "namespace", pod.Namespace, "pod", pod.PodName)
73+
// Close with error so the pipe reader gets the error
74+
stdoutWriter.CloseWithError(streamErr)
75+
} else {
76+
stdoutWriter.Close()
8077
}
78+
79+
// Signal done after closing the writer so that the pipe reader
80+
// (restic's stdin) receives all data and an EOF before the backup
81+
// command is waited on. Previously, done was signaled before close,
82+
// causing a race where restic could finish before all data was
83+
// flushed through the pipe, resulting in truncated backups.
84+
done <- true
8185
}()
8286

8387
data := &ExecData{

0 commit comments

Comments
 (0)