@@ -175,3 +175,103 @@ func TestKeepRegisteredRecoversVolumesRegistrationLoss(t *testing.T) {
175175 }
176176 })
177177}
178+
179+ type keepaliveTransport struct {
180+ http.RoundTripper
181+ closed chan struct {}
182+ }
183+
184+ func (transport * keepaliveTransport ) CloseIdleConnections () {
185+ close (transport .closed )
186+ }
187+
188+ func TestKeepRegisteredBlockedPIDProbe (t * testing.T ) {
189+ for _ , scenario := range []string {"cancel" , "recover" } {
190+ t .Run (scenario , func (t * testing.T ) {
191+ synctest .Test (t , func (t * testing.T ) {
192+ ctx , cancel := context .WithCancel (t .Context ())
193+ defer cancel ()
194+ client , err := fuse .NewClient (registration )
195+ require .NoError (t , err )
196+ var mu sync.Mutex
197+ requests := map [string ]int {}
198+ transport := & keepaliveTransport {
199+ closed : make (chan struct {}),
200+ RoundTripper : roundTripFunc (func (request * http.Request ) (* http.Response , error ) {
201+ mu .Lock ()
202+ defer mu .Unlock ()
203+ requests [request .URL .Port ()]++
204+ return & http.Response {StatusCode : http .StatusOK , Body : http .NoBody }, nil
205+ }),
206+ }
207+ httpClient := fuse .HTTPClient (client )
208+ httpClient .Transport = transport
209+ releaseProbe := make (chan struct {}, 1 )
210+ defer close (releaseProbe )
211+ probeCalls := 0
212+ fuse .ConfigureTestClient (client , httpClient , testHosts , func () (int , error ) {
213+ mu .Lock ()
214+ probeCalls ++
215+ firstProbe := probeCalls == 1
216+ mu .Unlock ()
217+ if firstProbe {
218+ <- releaseProbe
219+ }
220+ return registration .PID , nil
221+ })
222+ require .NoError (t , fuse .KeepRegistered (ctx , client , func (context.Context ) (string , error ) {
223+ return "fixed-bootstrap-token" , nil
224+ }, "12345" , testNotebookDir ))
225+ synctest .Wait ()
226+ start := time .Now ()
227+ for tick := range 3 {
228+ time .Sleep (time .Until (start .Add (time .Duration (tick + 1 ) * fuse .RefreshInterval )))
229+ synctest .Wait ()
230+ mu .Lock ()
231+ assert .Equal (t , tick + 1 , requests ["1015" ], "wait for the probe before refreshing" )
232+ mu .Unlock ()
233+ time .Sleep (fuse .PIDProbeTimeout )
234+ synctest .Wait ()
235+ mu .Lock ()
236+ assert .Equal (t , tick + 2 , requests ["1015" ], "keep refreshing volumes while WSFS is blocked" )
237+ assert .Equal (t , tick + 2 , requests ["1021" ], "force WSFS registration after a probe timeout" )
238+ assert .Equal (t , 1 , probeCalls , "reuse the outstanding probe across refreshes" )
239+ mu .Unlock ()
240+ }
241+
242+ if scenario == "recover" {
243+ releaseProbe <- struct {}{}
244+ synctest .Wait ()
245+ time .Sleep (2 * fuse .RefreshInterval )
246+ synctest .Wait ()
247+ mu .Lock ()
248+ assert .Equal (t , 6 , requests ["1015" ])
249+ assert .Equal (t , 4 , requests ["1021" ], "stop forcing WSFS registration after probe recovery" )
250+ assert .Equal (t , 2 , probeCalls , "resume probing after the outstanding read completes" )
251+ mu .Unlock ()
252+ } else {
253+ time .Sleep (fuse .RefreshInterval - fuse .PIDProbeTimeout )
254+ synctest .Wait ()
255+ }
256+
257+ cancel ()
258+ synctest .Wait ()
259+ select {
260+ case <- transport .closed :
261+ default :
262+ assert .Fail (t , "refresh loop must exit on cancellation even with a blocked probe" )
263+ }
264+ mu .Lock ()
265+ callsAtCancel := probeCalls
266+ requestsAtCancel := requests ["1015" ]
267+ mu .Unlock ()
268+ time .Sleep (2 * fuse .RefreshInterval )
269+ synctest .Wait ()
270+ mu .Lock ()
271+ assert .Equal (t , callsAtCancel , probeCalls )
272+ assert .Equal (t , requestsAtCancel , requests ["1015" ])
273+ mu .Unlock ()
274+ })
275+ })
276+ }
277+ }
0 commit comments