@@ -46,12 +46,14 @@ package managed
4646import (
4747 "context"
4848 "crypto/sha256"
49+ "errors"
4950 "fmt"
5051 "io"
5152 "net"
5253 "net/url"
5354 "runtime"
5455 "strings"
56+ "sync"
5557 "time"
5658
5759 "golang.org/x/crypto/ssh"
@@ -83,16 +85,22 @@ func sshSmartSubtransportFactory(remote *git2go.Remote, transport *git2go.Transp
8385type sshSmartSubtransport struct {
8486 transport * git2go.Transport
8587
86- lastAction git2go.SmartServiceAction
88+ lastAction git2go.SmartServiceAction
89+ stdin io.WriteCloser
90+ stdout io.Reader
91+ addr string
92+ ctx context.Context
93+
94+ con connection
95+ }
96+
97+ type connection struct {
8798 conn net.Conn
8899 client * ssh.Client
89100 session * ssh.Session
90- stdin io.WriteCloser
91- stdout io.Reader
92101 currentStream * sshSmartSubtransportStream
93- addr string
94102 connected bool
95- ctx context. Context
103+ m sync. Mutex
96104}
97105
98106func (t * sshSmartSubtransport ) Action (transportOptionsURL string , action git2go.SmartServiceAction ) (git2go.SmartSubtransportStream , error ) {
@@ -128,17 +136,17 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
128136 var cmd string
129137 switch action {
130138 case git2go .SmartServiceActionUploadpackLs , git2go .SmartServiceActionUploadpack :
131- if t .currentStream != nil {
139+ if t .con . currentStream != nil {
132140 if t .lastAction == git2go .SmartServiceActionUploadpackLs {
133- return t .currentStream , nil
141+ return t .con . currentStream , nil
134142 }
135143 }
136144 cmd = fmt .Sprintf ("git-upload-pack '%s'" , uPath )
137145
138146 case git2go .SmartServiceActionReceivepackLs , git2go .SmartServiceActionReceivepack :
139- if t .currentStream != nil {
147+ if t .con . currentStream != nil {
140148 if t .lastAction == git2go .SmartServiceActionReceivepackLs {
141- return t .currentStream , nil
149+ return t .con . currentStream , nil
142150 }
143151 }
144152 cmd = fmt .Sprintf ("git-receive-pack '%s'" , uPath )
@@ -147,7 +155,7 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
147155 return nil , fmt .Errorf ("unexpected action: %v" , action )
148156 }
149157
150- if t .connected {
158+ if t .con . connected {
151159 // Disregard errors from previous stream, futher details inside Close().
152160 _ = t .Close ()
153161 }
@@ -185,21 +193,23 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
185193 if err != nil {
186194 return nil , err
187195 }
188- t .connected = true
196+ t .con .m .Lock ()
197+ t .con .connected = true
198+ t .con .m .Unlock ()
189199
190200 traceLog .Info ("[ssh]: creating new ssh session" )
191- if t .session , err = t .client .NewSession (); err != nil {
201+ if t .con . session , err = t . con .client .NewSession (); err != nil {
192202 return nil , err
193203 }
194204
195- if t .stdin , err = t .session .StdinPipe (); err != nil {
205+ if t .stdin , err = t .con . session .StdinPipe (); err != nil {
196206 return nil , err
197207 }
198208
199209 var w * io.PipeWriter
200210 var reader io.Reader
201211 t .stdout , w = io .Pipe ()
202- if reader , err = t .session .StdoutPipe (); err != nil {
212+ if reader , err = t .con . session .StdoutPipe (); err != nil {
203213 return nil , err
204214 }
205215
@@ -208,7 +218,15 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
208218 //
209219 // xref: https://github.com/golang/crypto/blob/eb4f295cb31f7fb5d52810411604a2638c9b19a2/ssh/session.go#L553-L558
210220 go func () error {
211- defer w .Close ()
221+ defer func () {
222+ w .Close ()
223+
224+ // In case this goroutine panics, handle recovery.
225+ if r := recover (); r != nil {
226+ traceLog .Error (errors .New (r .(string )),
227+ "[ssh]: recovered from libgit2 ssh smart subtransport panic" , "address" , t .addr )
228+ }
229+ }()
212230
213231 var cancel context.CancelFunc
214232 ctx := t .ctx
@@ -226,9 +244,12 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
226244 return nil
227245
228246 default :
229- if ! t .connected {
247+ t .con .m .Lock ()
248+ if ! t .con .connected {
249+ t .con .m .Unlock ()
230250 return nil
231251 }
252+ t .con .m .Unlock ()
232253
233254 _ , err := io .Copy (w , reader )
234255 if err != nil {
@@ -240,16 +261,16 @@ func (t *sshSmartSubtransport) Action(transportOptionsURL string, action git2go.
240261 }()
241262
242263 traceLog .Info ("[ssh]: run on remote" , "cmd" , cmd )
243- if err := t .session .Start (cmd ); err != nil {
264+ if err := t .con . session .Start (cmd ); err != nil {
244265 return nil , err
245266 }
246267
247268 t .lastAction = action
248- t .currentStream = & sshSmartSubtransportStream {
269+ t .con . currentStream = & sshSmartSubtransportStream {
249270 owner : t ,
250271 }
251272
252- return t .currentStream , nil
273+ return t .con . currentStream , nil
253274}
254275
255276func (t * sshSmartSubtransport ) createConn (addr string , sshConfig * ssh.ClientConfig ) error {
@@ -265,8 +286,8 @@ func (t *sshSmartSubtransport) createConn(addr string, sshConfig *ssh.ClientConf
265286 return err
266287 }
267288
268- t .conn = conn
269- t .client = ssh .NewClient (c , chans , reqs )
289+ t .con . conn = conn
290+ t .con . client = ssh .NewClient (c , chans , reqs )
270291
271292 return nil
272293}
@@ -282,31 +303,35 @@ func (t *sshSmartSubtransport) createConn(addr string, sshConfig *ssh.ClientConf
282303// SmartSubTransport (i.e. unreleased resources, staled connections).
283304func (t * sshSmartSubtransport ) Close () error {
284305 traceLog .Info ("[ssh]: sshSmartSubtransport.Close()" , "server" , t .addr )
285- t .currentStream = nil
286- if t .client != nil && t .stdin != nil {
306+ t .con .m .Lock ()
307+ defer t .con .m .Unlock ()
308+ t .con .currentStream = nil
309+ if t .con .client != nil && t .stdin != nil {
287310 _ = t .stdin .Close ()
288311 }
289- t .client = nil
312+ t .con . client = nil
290313
291- if t .session != nil {
314+ if t .con . session != nil {
292315 traceLog .Info ("[ssh]: session.Close()" , "server" , t .addr )
293- _ = t .session .Close ()
316+ _ = t .con . session .Close ()
294317 }
295- t .session = nil
318+ t .con . session = nil
296319
297320 return nil
298321}
299322
300323func (t * sshSmartSubtransport ) Free () {
301324 traceLog .Info ("[ssh]: sshSmartSubtransport.Free()" )
302- if t .client != nil {
303- _ = t .client .Close ()
325+ if t .con . client != nil {
326+ _ = t .con . client .Close ()
304327 }
305328
306- if t .conn != nil {
307- _ = t .conn .Close ()
329+ if t .con . conn != nil {
330+ _ = t .con . conn .Close ()
308331 }
309- t .connected = false
332+ t .con .m .Lock ()
333+ t .con .connected = false
334+ t .con .m .Unlock ()
310335}
311336
312337type sshSmartSubtransportStream struct {
0 commit comments