@@ -102,6 +102,7 @@ type sshSmartSubtransport struct {
102102 stdout io.Reader
103103 currentStream * sshSmartSubtransportStream
104104 ckey string
105+ addr string
105106}
106107
107108// aMux is the read-write mutex to control access to sshClients.
@@ -182,6 +183,7 @@ func (t *sshSmartSubtransport) Action(urlString string, action git2go.SmartServi
182183 port = u .Port ()
183184 }
184185 addr = fmt .Sprintf ("%s:%s" , u .Hostname (), port )
186+ t .addr = addr
185187
186188 ckey , sshConfig , err := cacheKeyAndConfig (addr , cred )
187189 if err != nil {
@@ -229,9 +231,9 @@ func (t *sshSmartSubtransport) Action(urlString string, action git2go.SmartServi
229231 if t .session , err = t .client .NewSession (); err != nil {
230232 discardCachedSshClient (ckey )
231233
232- // if the current connection was cached, and the error is EOF,
233- // we can try again as this may be a stale connection.
234- if ! ( cacheHit && err . Error () == "EOF" ) {
234+ // if the current connection was cached, we can try again
235+ // as this may be a stale connection.
236+ if ! cacheHit {
235237 return nil , err
236238 }
237239
@@ -274,18 +276,16 @@ func (t *sshSmartSubtransport) Action(urlString string, action git2go.SmartServi
274276}
275277
276278func (t * sshSmartSubtransport ) createConn (ckey , addr string , sshConfig * ssh.ClientConfig ) error {
277- aMux .Lock ()
278- defer aMux .Unlock ()
279-
280279 // In some scenarios the ssh handshake can hang indefinitely at
281280 // golang.org/x/crypto/ssh.(*handshakeTransport).kexLoop.
282281 //
283282 // xref: https://github.com/golang/go/issues/51926
284283 done := make (chan error , 1 )
285284 var err error
286285
286+ var c * ssh.Client
287287 go func () {
288- t . client , err = ssh .Dial ("tcp" , addr , sshConfig )
288+ c , err = ssh .Dial ("tcp" , addr , sshConfig )
289289 done <- err
290290 }()
291291
@@ -304,8 +304,24 @@ func (t *sshSmartSubtransport) createConn(ckey, addr string, sshConfig *ssh.Clie
304304 return err
305305 }
306306
307+ t .client = c
308+
309+ // Mutex is set here to avoid the network latency being
310+ // absorbed by all competing goroutines.
311+ aMux .Lock ()
312+ defer aMux .Unlock ()
313+
314+ // A different goroutine won the race, dispose the connection
315+ // and carry on.
316+ if _ , ok := sshClients [ckey ]; ok {
317+ go func () {
318+ _ = c .Close ()
319+ }()
320+ return nil
321+ }
322+
307323 sshClients [ckey ] = & cachedClient {
308- Client : t . client ,
324+ Client : c ,
309325 activeSessions : 1 ,
310326 }
311327
@@ -322,21 +338,16 @@ func (t *sshSmartSubtransport) createConn(ckey, addr string, sshConfig *ssh.Clie
322338// may impair the transport to have successful actions on a new
323339// SmartSubTransport (i.e. unreleased resources, staled connections).
324340func (t * sshSmartSubtransport ) Close () error {
325- traceLog .Info ("[ssh]: sshSmartSubtransport.Close()" )
341+ traceLog .Info ("[ssh]: sshSmartSubtransport.Close()" , "server" , t . addr )
326342 t .currentStream = nil
327343 if t .client != nil && t .stdin != nil {
328344 _ = t .stdin .Close ()
329345 }
330346 t .client = nil
331347
332348 if t .session != nil {
333- traceLog .Info ("[ssh]: session.Close()" )
334- err := t .session .Close ()
335- // failure closing a session suggests a stale connection.
336- if err != nil && t .ckey != "" {
337- discardCachedSshClient (t .ckey )
338- t .ckey = ""
339- }
349+ traceLog .Info ("[ssh]: session.Close()" , "server" , t .addr )
350+ _ = t .session .Close ()
340351 }
341352 t .session = nil
342353
@@ -439,16 +450,14 @@ func discardCachedSshClient(key string) {
439450 defer aMux .Unlock ()
440451
441452 if v , found := sshClients [key ]; found {
442- traceLog .Info ("[ssh]: discard cached ssh client" )
443-
444- v .activeSessions --
453+ traceLog .Info ("[ssh]: discard cached ssh client" , "activeSessions" , v .activeSessions )
445454 closeConn := func () {
446- if v . Client != nil {
447- // run as async goroutine to minimise mutex time in immediate closures.
448- go func () {
455+ // run as async goroutine to minimise mutex time in immediate closures.
456+ go func () {
457+ if v . Client != nil {
449458 _ = v .Client .Close ()
450- }()
451- }
459+ }
460+ }()
452461 }
453462
454463 // if no active sessions for this connection, close it right-away.
0 commit comments