rclone: Give rclone time to finish before closing stdin pipe

Calling `Close()` on the rclone backend sometimes failed during test
execution with 'signal: Broken pipe'. The stdio connection closed both
the stdin and stdout file descriptors at the same moment, therefore
giving rclone no chance to properly send any final http2 data frames.

Now the stdin connection to rclone is closed first and will only be
forcefully closed after a timeout. In case rclone exits before the
timeout then the stdio connection will be closed normally.
This commit is contained in:
Michael Eischer 2020-07-24 23:27:47 +02:00
parent bf7b1f12ea
commit 3cd927d180
2 changed files with 23 additions and 18 deletions

View File

@ -197,7 +197,7 @@ func newBackend(cfg Config, lim limiter.Limiter) (*Backend, error) {
debug.Log("Wait returned %v", err) debug.Log("Wait returned %v", err)
be.waitResult = err be.waitResult = err
// close our side of the pipes to rclone // close our side of the pipes to rclone
stdioConn.Close() stdioConn.CloseAll()
close(waitCh) close(waitCh)
}() }()
@ -314,7 +314,7 @@ func (be *Backend) Close() error {
debug.Log("rclone exited") debug.Log("rclone exited")
case <-time.After(waitForExit): case <-time.After(waitForExit):
debug.Log("timeout, closing file descriptors") debug.Log("timeout, closing file descriptors")
err := be.conn.Close() err := be.conn.CloseAll()
if err != nil { if err != nil {
return err return err
} }

View File

@ -12,10 +12,11 @@ import (
// StdioConn implements a net.Conn via stdin/stdout. // StdioConn implements a net.Conn via stdin/stdout.
type StdioConn struct { type StdioConn struct {
stdin *os.File stdin *os.File
stdout *os.File stdout *os.File
cmd *exec.Cmd cmd *exec.Cmd
close sync.Once closeIn sync.Once
closeOut sync.Once
} }
func (s *StdioConn) Read(p []byte) (int, error) { func (s *StdioConn) Read(p []byte) (int, error) {
@ -28,21 +29,25 @@ func (s *StdioConn) Write(p []byte) (int, error) {
return n, err return n, err
} }
// Close closes both streams. // Close closes the stream to the child process.
func (s *StdioConn) Close() (err error) { func (s *StdioConn) Close() (err error) {
s.close.Do(func() { s.closeOut.Do(func() {
debug.Log("close stdio connection") debug.Log("close stdio send connection")
var errs []error err = s.stdout.Close()
})
for _, f := range []func() error{s.stdin.Close, s.stdout.Close} { return err
err := f() }
if err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 { // CloseAll closes both streams.
err = errs[0] func (s *StdioConn) CloseAll() (err error) {
err = s.Close()
s.closeIn.Do(func() {
debug.Log("close stdio receive connection")
err2 := s.stdin.Close()
if err == nil {
err = err2
} }
}) })