@@ -2,6 +2,7 @@ package fleet
22
33import (
44 "fmt"
5+ "io"
56 "net"
67 "net/http"
78 "net/url"
@@ -118,8 +119,42 @@ func (t *sshTunnel) NewHostKeyChecker() *ssh.HostKeyChecker {
118119// simply a synchronized wrapper to the underlying http.Transport.
119120func (t * sshTunnel ) RoundTrip (req * http.Request ) (* http.Response , error ) {
120121 t .Mutex .Lock ()
121- defer t .Mutex .Unlock ()
122122
123- fmt .Printf ("req.URL.Path: %#v\n " , req .URL .Path )
124- return t .HTTPTransport .RoundTrip (req )
123+ fmt .Printf ("req.URL.Path: %#v?%s\n " , req .URL .Path , req .URL .Query ().Encode ())
124+ resp , err := t .HTTPTransport .RoundTrip (req )
125+
126+ /**
127+ Learning:
128+
129+ The fleet ssh tunnel does not support (for unknown reasons) more than one
130+ command-session in parallel. Just adding a mutex here does not 100% help,
131+ as the connection is only returned to the idle pool when the body gets closed.
132+ If multiple go routines try to call RoundTrip() the first one performs the request,
133+ then return the response. Then the second goroutine would start calling its
134+ request and since the initial connection is not yet closed would try to open a second
135+ connection which would fail with "forward request denied".
136+
137+ Thus we only unlock the mutex when the body of the response has been closed.
138+ This ensures the connection has been returned to the pool.
139+ **/
140+ resp .Body = & synchronousReadCloser {
141+ body : resp .Body ,
142+ mutex : & t .Mutex ,
143+ }
144+
145+ return resp , err
146+ }
147+
148+ type synchronousReadCloser struct {
149+ body io.ReadCloser
150+ mutex * sync.Mutex
151+ }
152+
153+ func (s * synchronousReadCloser ) Read (p []byte ) (n int , err error ) {
154+ return s .body .Read (p )
155+ }
156+
157+ func (s * synchronousReadCloser ) Close () error {
158+ defer s .mutex .Unlock ()
159+ return s .body .Close ()
125160}
0 commit comments