@@ -21,6 +21,7 @@ import (
21
21
"context"
22
22
"io"
23
23
"sync"
24
+ "time"
24
25
25
26
"github.com/saucelabs/forwarder/internal/martian/log"
26
27
)
@@ -43,13 +44,35 @@ var copyBufPool = sync.Pool{
43
44
},
44
45
}
45
46
47
+ var bicopyGracefulTimeout = 1 * time .Minute
48
+
46
49
func bicopy (ctx context.Context , cc ... copier ) {
50
+ ctx , cancel := context .WithCancel (ctx )
51
+ defer cancel ()
52
+
47
53
donec := make (chan struct {}, len (cc ))
48
54
for i := range cc {
49
55
go cc [i ].copy (ctx , donec )
50
56
}
51
- for range cc {
57
+
58
+ for i := range cc {
52
59
<- donec
60
+ if i == 0 {
61
+ // Forcibly close all tunnels 1 minute after the first tunnel finished.
62
+ go gracefulCloseAfter (ctx , bicopyGracefulTimeout , cc ... )
63
+ }
64
+ }
65
+ }
66
+
67
+ func gracefulCloseAfter (ctx context.Context , d time.Duration , cc ... copier ) {
68
+ select {
69
+ case <- ctx .Done ():
70
+ return
71
+ case <- time .After (d ):
72
+ log .Infof (ctx , "forcibly closing tunnel after %v of graceful period" , d )
73
+ }
74
+ for i := range cc {
75
+ cc [i ].close (ctx )
53
76
}
54
77
}
55
78
@@ -67,6 +90,13 @@ func (c copier) copy(ctx context.Context, donec chan<- struct{}) {
67
90
if _ , err := io .CopyBuffer (c .dst , c .src , buf ); err != nil && ! isClosedConnError (err ) {
68
91
log .Errorf (ctx , "failed to copy %s tunnel: %v" , c .name , err )
69
92
}
93
+ c .closeWriter (ctx )
94
+
95
+ log .Debugf (ctx , "%s tunnel finished copying" , c .name )
96
+ donec <- struct {}{}
97
+ }
98
+
99
+ func (c copier ) closeWriter (ctx context.Context ) {
70
100
var closeErr error
71
101
if cw , ok := asCloseWriter (c .dst ); ok {
72
102
closeErr = cw .CloseWrite ()
@@ -78,7 +108,15 @@ func (c copier) copy(ctx context.Context, donec chan<- struct{}) {
78
108
if closeErr != nil {
79
109
log .Infof (ctx , "failed to close write side of %s tunnel: %v" , c .name , closeErr )
80
110
}
111
+ }
81
112
82
- log .Debugf (ctx , "%s tunnel finished copying" , c .name )
83
- donec <- struct {}{}
113
+ func (c copier ) close (ctx context.Context ) {
114
+ cc , ok := asCloser (c .dst )
115
+ if ! ok {
116
+ log .Errorf (ctx , "cannot close %s tunnel (%T)" , c .name , c .dst )
117
+ return
118
+ }
119
+ if err := cc .Close (); err != nil && ! isClosedConnError (err ) {
120
+ log .Infof (ctx , "failed to close %s tunnel: %v" , c .name , err )
121
+ }
84
122
}
0 commit comments