@@ -102,6 +102,7 @@ type sshSmartSubtransport struct {
102
102
stdout io.Reader
103
103
currentStream * sshSmartSubtransportStream
104
104
ckey string
105
+ addr string
105
106
}
106
107
107
108
// aMux is the read-write mutex to control access to sshClients.
@@ -182,6 +183,7 @@ func (t *sshSmartSubtransport) Action(urlString string, action git2go.SmartServi
182
183
port = u .Port ()
183
184
}
184
185
addr = fmt .Sprintf ("%s:%s" , u .Hostname (), port )
186
+ t .addr = addr
185
187
186
188
ckey , sshConfig , err := cacheKeyAndConfig (addr , cred )
187
189
if err != nil {
@@ -229,9 +231,9 @@ func (t *sshSmartSubtransport) Action(urlString string, action git2go.SmartServi
229
231
if t .session , err = t .client .NewSession (); err != nil {
230
232
discardCachedSshClient (ckey )
231
233
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 {
235
237
return nil , err
236
238
}
237
239
@@ -274,18 +276,16 @@ func (t *sshSmartSubtransport) Action(urlString string, action git2go.SmartServi
274
276
}
275
277
276
278
func (t * sshSmartSubtransport ) createConn (ckey , addr string , sshConfig * ssh.ClientConfig ) error {
277
- aMux .Lock ()
278
- defer aMux .Unlock ()
279
-
280
279
// In some scenarios the ssh handshake can hang indefinitely at
281
280
// golang.org/x/crypto/ssh.(*handshakeTransport).kexLoop.
282
281
//
283
282
// xref: https://github.com/golang/go/issues/51926
284
283
done := make (chan error , 1 )
285
284
var err error
286
285
286
+ var c * ssh.Client
287
287
go func () {
288
- t . client , err = ssh .Dial ("tcp" , addr , sshConfig )
288
+ c , err = ssh .Dial ("tcp" , addr , sshConfig )
289
289
done <- err
290
290
}()
291
291
@@ -304,8 +304,24 @@ func (t *sshSmartSubtransport) createConn(ckey, addr string, sshConfig *ssh.Clie
304
304
return err
305
305
}
306
306
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
+
307
323
sshClients [ckey ] = & cachedClient {
308
- Client : t . client ,
324
+ Client : c ,
309
325
activeSessions : 1 ,
310
326
}
311
327
@@ -322,21 +338,16 @@ func (t *sshSmartSubtransport) createConn(ckey, addr string, sshConfig *ssh.Clie
322
338
// may impair the transport to have successful actions on a new
323
339
// SmartSubTransport (i.e. unreleased resources, staled connections).
324
340
func (t * sshSmartSubtransport ) Close () error {
325
- traceLog .Info ("[ssh]: sshSmartSubtransport.Close()" )
341
+ traceLog .Info ("[ssh]: sshSmartSubtransport.Close()" , "server" , t . addr )
326
342
t .currentStream = nil
327
343
if t .client != nil && t .stdin != nil {
328
344
_ = t .stdin .Close ()
329
345
}
330
346
t .client = nil
331
347
332
348
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 ()
340
351
}
341
352
t .session = nil
342
353
@@ -439,16 +450,14 @@ func discardCachedSshClient(key string) {
439
450
defer aMux .Unlock ()
440
451
441
452
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 )
445
454
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 {
449
458
_ = v .Client .Close ()
450
- }()
451
- }
459
+ }
460
+ }()
452
461
}
453
462
454
463
// if no active sessions for this connection, close it right-away.
0 commit comments