@@ -57,7 +57,8 @@ func (s *switchable) RoundTrip(r *http.Request) (*http.Response, error) {
5757
5858// cancelClient is a guest not yet given a key, whose ZeroKMS requests go to
5959// rt and whose token comes from token. Its se_cipher_init is the call the
60- // tests drive: it makes a load-keyset request, so it reaches the network.
60+ // tests drive: it pauses on a load-keyset request, so it reaches the
61+ // network between two steps.
6162func cancelClient (t * testing.T , rt http.RoundTripper , token tokenSource ) * Client {
6263 t .Helper ()
6364 tr := & transport {rt : rt , token : token }
@@ -77,7 +78,7 @@ func cipherInit(ctx context.Context, c *Client) error {
7778 return err
7879 }
7980 defer wipe (encoded )
80- _ , err = c .call (ctx , func (ctx context.Context , inst * instance ) ([]byte , error ) {
81+ _ , err = c .callKMS (ctx , func (ctx context.Context , inst * instance ) ([]byte , error ) {
8182 return inst .call (ctx , inst .cipherInit , buf (encoded ))
8283 })
8384 return err
@@ -96,21 +97,28 @@ func wantOpen(t *testing.T, c *Client, rt *switchable) {
9697 }
9798}
9899
99- func TestACallCancelledDuringARequestFailsOnlyItself (t * testing.T ) {
100+ // The request runs between two steps, under the caller's context, with the
101+ // guest paused: cancelling it ends the call and aborts the paused
102+ // operation, and the client stays open.
103+ func TestACallCancelledBetweenStepsFailsOnlyItself (t * testing.T ) {
104+ ctx , cancel := context .WithCancel (context .Background ())
105+ defer cancel ()
100106 rt := & switchable {rt : func (r * http.Request ) (* http.Response , error ) {
107+ // The caller gives up once its request is in flight.
108+ cancel ()
101109 <- r .Context ().Done ()
102110 return nil , r .Context ().Err ()
103111 }}
104112 c := cancelClient (t , rt , staticToken ("t" ))
105- ctx , cancel := context .WithTimeout (context .Background (), 50 * time .Millisecond )
106- defer cancel ()
107113 err := cipherInit (ctx , c )
108- if ! errors .Is (err , context .DeadlineExceeded ) || ! errors .Is (err , ErrTransport ) {
109- t .Fatalf ("cancelled call: %v, want the deadline and ErrTransport" , err )
114+ if ! errors .Is (err , context .Canceled ) || ! errors .Is (err , ErrTransport ) {
115+ t .Fatalf ("cancelled call: %v, want context.Canceled and ErrTransport" , err )
110116 }
111117 wantOpen (t , c , rt )
112118}
113119
120+ // The token is fetched before the operation's first step: a caller that
121+ // gives up waiting on it never starts the operation.
114122func TestACallCancelledDuringATokenFetchFailsOnlyItself (t * testing.T ) {
115123 rt := & switchable {rt : unauthorized }
116124 var blocking atomic.Bool
@@ -126,8 +134,8 @@ func TestACallCancelledDuringATokenFetchFailsOnlyItself(t *testing.T) {
126134 ctx , cancel := context .WithCancel (context .Background ())
127135 time .AfterFunc (50 * time .Millisecond , cancel )
128136 err := cipherInit (ctx , c )
129- if ! errors .Is (err , context .Canceled ) || ! errors .Is (err , ErrTransport ) {
130- t .Fatalf ("cancelled call: %v, want context.Canceled and ErrTransport " , err )
137+ if ! errors .Is (err , context .Canceled ) || errors .Is (err , ErrState ) {
138+ t .Fatalf ("cancelled call: %v, want context.Canceled" , err )
131139 }
132140 if n := rt .sent .Load (); n != 0 {
133141 t .Fatalf ("%d request(s) sent without a token" , n )
@@ -175,31 +183,53 @@ func TestACallWhoseContextEndsInTheQueueNeverReachesTheGuest(t *testing.T) {
175183 wantOpen (t , c , rt )
176184}
177185
178- // The backstop: a call still running long after its caller gave up is a
186+ // The backstop: a step still running long after its caller gave up is a
179187// guest or host function that ignores cancellation. Its state is unknown,
180- // so the client closes, and says so.
181- func TestACallThatRunsOnAfterItsContextEndsClosesTheClient (t * testing.T ) {
182- rt := & switchable {rt : func (* http.Request ) (* http.Response , error ) {
183- // A RoundTripper that ignores the request's context.
184- time .Sleep (300 * time .Millisecond )
185- return unauthorized (nil )
186- }}
188+ // so the client closes, and says so. The step here waits out its caller's
189+ // context and the backstop and only then enters the guest, as a guest
190+ // stuck in a loop would still be running when the backstop fired.
191+ func TestAStepThatRunsOnAfterItsContextEndsClosesTheClient (t * testing.T ) {
192+ rt := & switchable {rt : unauthorized }
187193 c := cancelClient (t , rt , staticToken ("t" ))
188194 c .stuck = 20 * time .Millisecond
189- ctx , cancel := context .WithTimeout (context .Background (), 20 * time . Millisecond )
195+ ctx , cancel := context .WithCancel (context .Background ())
190196 defer cancel ()
191- err := cipherInit (ctx , c )
197+ _ , err := c .call (ctx , func (run context.Context , inst * instance ) ([]byte , error ) {
198+ cancel ()
199+ <- run .Done ()
200+ return inst .call (run , inst .targets )
201+ })
192202 if err == nil || ! strings .Contains (err .Error (), "the client is closed" ) {
193203 t .Fatalf ("stuck call: %v, want the client closed" , err )
194204 }
195- if ! errors .Is (err , context .DeadlineExceeded ) {
196- t .Errorf ("stuck call: %v, want it to match the deadline " , err )
205+ if ! errors .Is (err , context .Canceled ) {
206+ t .Errorf ("stuck call: %v, want it to match context.Canceled " , err )
197207 }
198208 if err := cipherInit (context .Background (), c ); ! errors .Is (err , ErrState ) {
199209 t .Fatalf ("a call after the close: %v, want ErrState" , err )
200210 }
201211}
202212
213+ // A request that ignores its context holds up only its own call: the guest
214+ // is paused while it runs, so nothing is stuck, and the client stays open.
215+ func TestARequestThatIgnoresItsContextLeavesTheClientOpen (t * testing.T ) {
216+ ctx , cancel := context .WithCancel (context .Background ())
217+ defer cancel ()
218+ rt := & switchable {rt : func (* http.Request ) (* http.Response , error ) {
219+ // The caller gives up once its request is in flight, and the
220+ // request runs on past the backstop regardless.
221+ cancel ()
222+ time .Sleep (100 * time .Millisecond )
223+ return unauthorized (nil )
224+ }}
225+ c := cancelClient (t , rt , staticToken ("t" ))
226+ c .stuck = 20 * time .Millisecond
227+ if err := cipherInit (ctx , c ); ! errors .Is (err , context .Canceled ) || ! errors .Is (err , ErrTransport ) {
228+ t .Fatalf ("call: %v, want context.Canceled and ErrTransport" , err )
229+ }
230+ wantOpen (t , c , rt )
231+ }
232+
203233// A call that finishes in time stops the backstop: the client stays open
204234// however long after the call its context ends.
205235func TestAFinishedCallStopsTheBackstop (t * testing.T ) {
@@ -236,6 +266,67 @@ func TestACallThatSucceedsAfterItsContextEndsFailsWithIt(t *testing.T) {
236266 wantOpen (t , c , rt )
237267}
238268
269+ // A step that pauses after its caller gave up sends no request: the
270+ // operation is aborted in the guest, so its handle is spent, and the call
271+ // fails with the context's error.
272+ func TestAStepThatPausesAfterItsContextEndsSendsNothing (t * testing.T ) {
273+ rt := & switchable {rt : unauthorized }
274+ c := cancelClient (t , rt , staticToken ("t" ))
275+ encoded , err := encodeConfig (testInit ("http://zerokms.invalid" ))
276+ if err != nil {
277+ t .Fatal (err )
278+ }
279+ ctx , cancel := context .WithCancel (context .Background ())
280+ _ , err = c .callKMS (ctx , func (run context.Context , inst * instance ) ([]byte , error ) {
281+ cancel ()
282+ return inst .call (run , inst .cipherInit , buf (encoded ))
283+ })
284+ if ! errors .Is (err , context .Canceled ) || ! errors .Is (err , ErrTransport ) {
285+ t .Fatalf ("call: %v, want context.Canceled and ErrTransport" , err )
286+ }
287+ if n := rt .sent .Load (); n != 0 {
288+ t .Fatalf ("%d request(s) sent for a caller that had given up" , n )
289+ }
290+ if _ , err := c .inst .call (context .Background (), c .inst .abort , scalar (1 )); ! errors .Is (err , ErrState ) {
291+ t .Fatalf ("the paused operation's handle: %v, want ErrState (already dropped)" , err )
292+ }
293+ wantOpen (t , c , rt )
294+ }
295+
296+ // A caller whose context ends while its resume waits for the client never
297+ // resumes: the paused operation is still aborted in the guest once the
298+ // client is free.
299+ func TestAResumeWhoseContextEndsInTheQueueAbortsTheOperation (t * testing.T ) {
300+ stub := newHeldStub (t )
301+ c := stepClient (t , staticToken ("stub-token" ))
302+ ctx , cancel := context .WithCancel (context .Background ())
303+ defer cancel ()
304+ done := make (chan error , 1 )
305+ go func () {
306+ _ , err := c .callKMS (ctx , initStep (t , stub .URL ))
307+ done <- err
308+ }()
309+ waitFor (t , stub .arrived , "the init's request" )
310+ // Another call holds the client when the response comes back.
311+ if err := c .mu .Acquire (context .Background ()); err != nil {
312+ t .Fatal (err )
313+ }
314+ close (stub .release )
315+ time .Sleep (50 * time .Millisecond )
316+ cancel ()
317+ time .Sleep (20 * time .Millisecond )
318+ c .mu .Unlock ()
319+ if err := <- done ; ! errors .Is (err , context .Canceled ) {
320+ t .Fatalf ("call: %v, want context.Canceled" , err )
321+ }
322+ if _ , err := c .inst .call (context .Background (), c .inst .abort , scalar (1 )); ! errors .Is (err , ErrState ) {
323+ t .Fatalf ("the paused operation's handle: %v, want ErrState (already dropped)" , err )
324+ }
325+ if c .closed || c .inst .module .IsClosed () {
326+ t .Fatal ("giving up closed the client" )
327+ }
328+ }
329+
239330// A closed module is ErrState, and Close still releases the runtime.
240331func TestAClosedModuleIsState (t * testing.T ) {
241332 ctx := context .Background ()
@@ -382,8 +473,8 @@ func TestACallCancelledDuringATokenExchangeLeavesTheProfileOpen(t *testing.T) {
382473 hold .Store (true )
383474 ctx , cancel := context .WithTimeout (context .Background (), 50 * time .Millisecond )
384475 defer cancel ()
385- if err := cipherInit (ctx , c ); ! errors .Is (err , context .DeadlineExceeded ) || ! errors .Is (err , ErrTransport ) {
386- t .Fatalf ("call cancelled during the exchange: %v, want the deadline and ErrTransport " , err )
476+ if err := cipherInit (ctx , c ); ! errors .Is (err , context .DeadlineExceeded ) || errors .Is (err , ErrState ) {
477+ t .Fatalf ("call cancelled during the exchange: %v, want the deadline" , err )
387478 }
388479 hold .Store (false )
389480 wantOpen (t , c , rt )
0 commit comments