@@ -2,13 +2,16 @@ package client
22
33import (
44 "context"
5+ "crypto/tls"
56 "errors"
67 "net"
8+ "sync"
79 "testing"
810 "time"
911
1012 "github.com/stretchr/testify/assert"
1113 "github.com/stretchr/testify/require"
14+ "github.com/valyala/fasthttp"
1215 "github.com/valyala/fasthttp/fasthttputil"
1316
1417 "github.com/gofiber/fiber/v3"
@@ -154,6 +157,59 @@ func Test_Exec_Func(t *testing.T) {
154157
155158 require .Equal (t , ErrTimeoutOrCancel , err )
156159 })
160+
161+ t .Run ("cancel drains errChan" , func (t * testing.T ) {
162+ core , client , req := newCore (), New (), AcquireRequest ()
163+ ctx , cancel := context .WithCancel (context .Background ())
164+ defer cancel ()
165+
166+ core .ctx = ctx
167+ core .client = client
168+ core .req = req
169+
170+ req .RawRequest .SetRequestURI ("http://example.com/drain-err" )
171+
172+ blockingTransport := newBlockingErrTransport (errors .New ("upstream failure" ))
173+ client .transport = blockingTransport
174+ defer blockingTransport .release ()
175+
176+ type execResult struct {
177+ resp * Response
178+ err error
179+ }
180+
181+ resultCh := make (chan execResult , 1 )
182+ go func () {
183+ resp , err := core .execFunc ()
184+ resultCh <- execResult {resp : resp , err : err }
185+ }()
186+
187+ select {
188+ case <- blockingTransport .called :
189+ case <- time .After (time .Second ):
190+ t .Fatal ("transport Do was not invoked" )
191+ }
192+
193+ cancel ()
194+
195+ var result execResult
196+ select {
197+ case result = <- resultCh :
198+ case <- time .After (time .Second ):
199+ t .Fatal ("execFunc did not return" )
200+ }
201+
202+ require .Nil (t , result .resp )
203+ require .ErrorIs (t , result .err , ErrTimeoutOrCancel )
204+
205+ blockingTransport .release ()
206+
207+ select {
208+ case <- blockingTransport .finished :
209+ case <- time .After (time .Second ):
210+ t .Fatal ("transport Do did not finish" )
211+ }
212+ })
157213}
158214
159215func Test_Execute (t * testing.T ) {
@@ -269,3 +325,57 @@ func Test_Execute(t *testing.T) {
269325 require .Equal (t , "example.com hang up" , string (resp .RawResponse .Body ()))
270326 })
271327}
328+
329+ type blockingErrTransport struct {
330+ err error
331+
332+ called chan struct {}
333+ unblock chan struct {}
334+ finished chan struct {}
335+
336+ calledOnce sync.Once
337+ releaseOnce sync.Once
338+ finishedOnce sync.Once
339+ }
340+
341+ func newBlockingErrTransport (err error ) * blockingErrTransport {
342+ return & blockingErrTransport {
343+ err : err ,
344+ called : make (chan struct {}),
345+ unblock : make (chan struct {}),
346+ finished : make (chan struct {}),
347+ }
348+ }
349+
350+ func (b * blockingErrTransport ) Do (req * fasthttp.Request , resp * fasthttp.Response ) error {
351+ b .calledOnce .Do (func () { close (b .called ) })
352+ <- b .unblock
353+ b .finishedOnce .Do (func () { close (b .finished ) })
354+ return b .err
355+ }
356+
357+ func (b * blockingErrTransport ) DoTimeout (req * fasthttp.Request , resp * fasthttp.Response , timeout time.Duration ) error {
358+ return b .Do (req , resp )
359+ }
360+
361+ func (b * blockingErrTransport ) DoDeadline (req * fasthttp.Request , resp * fasthttp.Response , deadline time.Time ) error {
362+ return b .Do (req , resp )
363+ }
364+
365+ func (b * blockingErrTransport ) DoRedirects (req * fasthttp.Request , resp * fasthttp.Response , maxRedirects int ) error {
366+ return b .Do (req , resp )
367+ }
368+
369+ func (b * blockingErrTransport ) CloseIdleConnections () {}
370+
371+ func (b * blockingErrTransport ) TLSConfig () * tls.Config { return nil }
372+
373+ func (b * blockingErrTransport ) SetTLSConfig (_ * tls.Config ) {}
374+
375+ func (b * blockingErrTransport ) SetDial (_ fasthttp.DialFunc ) {}
376+
377+ func (b * blockingErrTransport ) Client () any { return nil }
378+
379+ func (b * blockingErrTransport ) release () {
380+ b .releaseOnce .Do (func () { close (b .unblock ) })
381+ }
0 commit comments