@@ -91,7 +91,8 @@ type Connection struct {
9191 // to protect following: closing, status
9292 mutex sync.Mutex
9393
94- // user has called Close
94+ // user has called Close or we are closing connection due to an error
95+ // once closing is set to true, connection is unusable
9596 closing bool
9697
9798 // connection status
@@ -101,7 +102,13 @@ type Connection struct {
101102var _ io.Writer = (* Connection )(nil )
102103
103104// New creates and configures Connection. To establish network connection, call `Connect()`.
104- func New (addr string , spec * iso8583.MessageSpec , mlReader MessageLengthReader , mlWriter MessageLengthWriter , options ... Option ) (* Connection , error ) {
105+ func New (
106+ addr string ,
107+ spec * iso8583.MessageSpec ,
108+ mlReader MessageLengthReader ,
109+ mlWriter MessageLengthWriter ,
110+ options ... Option ,
111+ ) (* Connection , error ) {
105112 opts := GetDefaultOptions ()
106113 for _ , opt := range options {
107114 if err := opt (& opts ); err != nil {
@@ -126,12 +133,20 @@ func New(addr string, spec *iso8583.MessageSpec, mlReader MessageLengthReader, m
126133// NewFrom accepts conn (net.Conn, or any io.ReadWriteCloser) which will be
127134// used as a transport for the returned Connection. Returned Connection is
128135// ready to be used for message sending and receiving
129- func NewFrom (conn io.ReadWriteCloser , spec * iso8583.MessageSpec , mlReader MessageLengthReader , mlWriter MessageLengthWriter , options ... Option ) (* Connection , error ) {
136+ func NewFrom (
137+ conn io.ReadWriteCloser ,
138+ spec * iso8583.MessageSpec ,
139+ mlReader MessageLengthReader ,
140+ mlWriter MessageLengthWriter ,
141+ options ... Option ,
142+ ) (* Connection , error ) {
130143 c , err := New ("" , spec , mlReader , mlWriter , options ... )
131144 if err != nil {
132145 return nil , fmt .Errorf ("creating client: %w" , err )
133146 }
147+
134148 c .conn = conn
149+
135150 c .run ()
136151 return c , nil
137152}
@@ -186,10 +201,11 @@ func (c *Connection) ConnectCtx(ctx context.Context) error {
186201
187202 if onConnect != nil {
188203 if err := onConnect (ctx , c ); err != nil {
189- // close connection if OnConnect failed
190- // but ignore the potential error from Close()
191- // as it's a rare case
192- _ = c .CloseCtx (ctx )
204+ // we can do the hard close here by calling TriggerFailure
205+ // If connection is not Online, pool will not return it and
206+ // no one will be able to use it. The rest of the messages
207+ // like echo, ping should be safe to terminate
208+ c .TriggerFailure (fmt .Errorf ("on connect callback %s: %w" , c .addr , err ))
193209
194210 return fmt .Errorf ("on connect callback %s: %w" , c .addr , err )
195211 }
@@ -230,6 +246,7 @@ func (c *Connection) run() {
230246 go c .readResponseLoop ()
231247}
232248
249+ // handleError is used to track errors that happen during message reading or writing
233250func (c * Connection ) handleError (err error ) {
234251 if c .Opts .ErrorHandler == nil {
235252 return
@@ -257,32 +274,33 @@ func (c *Connection) handleConnectionError(err error) {
257274 c .closing = true
258275 c .mutex .Unlock ()
259276
260- // channel to wait for all goroutines to exit
261- done := make (chan bool )
277+ // first, notify all handlers that connection is closed due to an error
278+ if len (c .Opts .ConnectionFailedHandlers ) > 0 {
279+ for _ , handler := range c .Opts .ConnectionFailedHandlers {
280+ go handler (c , err )
281+ }
282+ }
262283
263284 c .pendingRequestsMu .Lock ()
264285 for _ , resp := range c .respMap {
265286 resp .errCh <- ErrConnectionClosed
266287 }
267288 c .pendingRequestsMu .Unlock ()
268289
269- // return error to all Send methods
290+ // Drain requestsCh in background (will stop when c.done closes - which will happen
291+ // when no more requests are sent to the connection). All requests will receive
292+ // ErrConnectionClosed error.
270293 go func () {
271294 for {
272295 select {
273296 case req := <- c .requestsCh :
274297 req .errCh <- ErrConnectionClosed
275- case <- done :
298+ case <- c . done :
276299 return
277300 }
278301 }
279302 }()
280303
281- go func () {
282- c .wg .Wait ()
283- done <- true
284- }()
285-
286304 // close everything else we close normally
287305 c .close ()
288306}
@@ -294,10 +312,7 @@ func (c *Connection) close() error {
294312 close (c .done )
295313
296314 if c .conn != nil {
297- err := c .conn .Close ()
298- if err != nil {
299- return fmt .Errorf ("closing connection: %w" , err )
300- }
315+ c .closeConn ()
301316 }
302317
303318 if len (c .Opts .ConnectionClosedHandlers ) > 0 {
@@ -309,6 +324,29 @@ func (c *Connection) close() error {
309324 return nil
310325}
311326
327+ func (c * Connection ) closeConn () {
328+ t := time .AfterFunc (500 * time .Millisecond , c .forceCloseConn )
329+ defer t .Stop ()
330+ c .conn .Close ()
331+ }
332+
333+ // A tls.Conn.Close can hang for a long time if the peer is unresponsive.
334+ // Try to shut it down more aggressively. This bypasses the TLS protocol
335+ // entirely and forcibly closes the underlying TCP connection. The kernel will
336+ // send a TCP RST, immediately terminating the connection without waiting for
337+ // TLS handshake completion.
338+ // taken from here:
339+ // https://github.com/golang/go/blob/3bea95b2778312dd733c0f13fe9ec20bd2bf2d13/src/net/http/h2_bundle.go#L8424
340+ func (c * Connection ) forceCloseConn () {
341+ tc , ok := c .conn .(* tls.Conn )
342+ if ! ok {
343+ return
344+ }
345+ if nc := tc .NetConn (); nc != nil {
346+ nc .Close ()
347+ }
348+ }
349+
312350// Close waits for pending requests to complete and then closes network
313351// connection with ISO 8583 server
314352func (c * Connection ) Close () error {
@@ -318,6 +356,15 @@ func (c *Connection) Close() error {
318356// CloseCtx waits for pending requests to complete and then closes network
319357// connection with ISO 8583 server
320358func (c * Connection ) CloseCtx (ctx context.Context ) error {
359+ c .mutex .Lock ()
360+ // if we are closing already, just return
361+ if c .closing {
362+ c .mutex .Unlock ()
363+ return nil
364+ }
365+ c .closing = true
366+ c .mutex .Unlock ()
367+
321368 onClose := c .Opts .OnCloseCtx
322369 if onClose == nil && c .Opts .OnClose != nil {
323370 onClose = func (_ context.Context , c * Connection ) error {
@@ -331,15 +378,6 @@ func (c *Connection) CloseCtx(ctx context.Context) error {
331378 }
332379 }
333380
334- c .mutex .Lock ()
335- // if we are closing already, just return
336- if c .closing {
337- c .mutex .Unlock ()
338- return nil
339- }
340- c .closing = true
341- c .mutex .Unlock ()
342-
343381 return c .close ()
344382}
345383
@@ -378,6 +416,17 @@ type response struct {
378416//
379417// conn.Send(msg, connection.SendTimeout(5 * time.Second))
380418func (c * Connection ) Send (message * iso8583.Message , options ... Option ) (* iso8583.Message , error ) {
419+ c .mutex .Lock ()
420+ if c .closing {
421+ c .mutex .Unlock ()
422+ return nil , ErrConnectionClosed
423+ }
424+ // calling wg.Add(1) within mutex guarantees that it does not pass the wg.Wait() call in the Close method
425+ // otherwise we will have data race issue
426+ c .wg .Add (1 )
427+ c .mutex .Unlock ()
428+ defer c .wg .Done ()
429+
381430 // use the SendTimeout from the connection options
382431 sendTimeout := c .Opts .SendTimeout
383432
@@ -396,17 +445,6 @@ func (c *Connection) Send(message *iso8583.Message, options ...Option) (*iso8583
396445 sendTimeout = opts .SendTimeout
397446 }
398447
399- c .mutex .Lock ()
400- if c .closing {
401- c .mutex .Unlock ()
402- return nil , ErrConnectionClosed
403- }
404- // calling wg.Add(1) within mutex guarantees that it does not pass the wg.Wait() call in the Close method
405- // otherwise we will have data race issue
406- c .wg .Add (1 )
407- c .mutex .Unlock ()
408- defer c .wg .Done ()
409-
410448 // prepare request
411449 reqID , err := c .Opts .RequestIDGenerator .GenerateRequestID (message )
412450 if err != nil {
@@ -641,6 +679,7 @@ func (c *Connection) readLoop() {
641679
642680 r := bufio .NewReader (c .conn )
643681 for {
682+
644683 message , err := c .readMessage (r )
645684 if err != nil {
646685 c .handleError (utils .NewSafeError (err , "failed to read message from connection" ))
@@ -794,3 +833,18 @@ func (c *Connection) RejectMessage(rejectedMessage *iso8583.Message, rejectionEr
794833
795834 return nil
796835}
836+
837+ // TriggerFailure is used to trigger connection failure manually. It will notify
838+ // all ConnectionFailedHandlers and close the connection.
839+ func (c * Connection ) TriggerFailure (reason error ) {
840+ c .handleConnectionError (reason )
841+ }
842+
843+ // isAlive checks if the connection is alive. Currently, it is used to
844+ // filter out connections that are closing or closed in the pool.
845+ func (c * Connection ) isAlive () bool {
846+ c .mutex .Lock ()
847+ defer c .mutex .Unlock ()
848+
849+ return ! c .closing
850+ }
0 commit comments