@@ -166,6 +166,65 @@ func TestTransitionThreadsWhileDoingRequests(t *testing.T) {
166166 transitionsWG .Wait ()
167167}
168168
169+ // Test https://github.com/php/frankenphp/issues/2469
170+ // A request queued for a thread when the server reloads must be served
171+ // by the new threads, not rejected with ErrMaxWaitTimeExceeded.
172+ func TestQueuedRequestSurvivesReload (t * testing.T ) {
173+ t .Cleanup (Shutdown )
174+
175+ initOpts := []Option {
176+ WithNumThreads (1 ),
177+ WithMaxThreads (1 ),
178+ WithMaxWaitTime (5 * time .Second ),
179+ }
180+ assert .NoError (t , Init (initOpts ... ))
181+
182+ doRequest := func (query string ) error {
183+ r := httptest .NewRequest ("GET" , "http://localhost/sleep.php?" + query , nil )
184+ w := httptest .NewRecorder ()
185+ req , err := NewRequestWithContext (r , WithRequestDocumentRoot (testDataPath , false ))
186+ if err != nil {
187+ return err
188+ }
189+
190+ return ServeHTTP (w , req )
191+ }
192+
193+ // Occupy the single PHP thread so the next request has to wait in the queue.
194+ started := make (chan struct {})
195+ go func () {
196+ close (started )
197+ _ = doRequest ("sleep=800" )
198+ }()
199+ <- started
200+ time .Sleep (100 * time .Millisecond )
201+
202+ queued := make (chan error , 1 )
203+ go func () {
204+ queued <- doRequest ("sleep=0" )
205+ }()
206+
207+ // Wait until the second request is genuinely queued waiting for a thread.
208+ deadline := time .Now ().Add (2 * time .Second )
209+ for queuedRegularThreads .Load () == 0 {
210+ if ! assert .True (t , time .Now ().Before (deadline ), "request was never queued" ) {
211+ return
212+ }
213+ time .Sleep (time .Millisecond )
214+ }
215+
216+ // Reload while the request is queued, exactly like the Caddy module does.
217+ Shutdown ()
218+ assert .NoError (t , Init (initOpts ... ))
219+
220+ select {
221+ case err := <- queued :
222+ assert .NoError (t , err , "a queued request must not be rejected by a reload" )
223+ case <- time .After (15 * time .Second ):
224+ t .Fatal ("the queued request never completed after the reload" )
225+ }
226+ }
227+
169228func TestFinishBootingAWorkerScript (t * testing.T ) {
170229 setupGlobals (t )
171230
0 commit comments