11import os
22import time
33
4+ # How long a resume stays in effect. Every request that reaches the stall point
5+ # during this window is released, not just the first one to claim the key.
6+ RESUME_WINDOW = 5
7+
8+ # Waiting is bounded so that a request the UA has already cancelled doesn't park
9+ # its server thread for the rest of the run.
10+ RESUME_TIMEOUT = 30
11+
12+
413def main (request , response ):
514 key = request .GET .first (b"key" )
15+ stash = request .server .stash
616
717 if request .method == "POST" :
8- request .server .stash .put (key , True )
18+ stash .put (key , True )
19+
20+ # Hold the resume open long enough for every request already waiting --
21+ # and any that arrive just afterwards -- to be released, then take it
22+ # back out so the value doesn't outlive the test.
23+ time .sleep (RESUME_WINDOW )
24+ with stash .lock :
25+ stash .take (key )
26+
927 return f"put { key } into stash"
1028
1129 file_path = os .path .join (request .doc_root , "media" , "movie_300.webm" )
@@ -28,10 +46,21 @@ def main(request, response):
2846
2947 response .writer .write (f .read (first_size ))
3048
31- # Wait for the key to appear in the stash.
49+ # Wait for the key to appear in the stash. Several requests can be
50+ # waiting on the same key at once, because the UA may try more than one
51+ # media player before settling on one that can handle the response.
52+ deadline = time .monotonic () + RESUME_TIMEOUT
3253 while True :
33- if request .server .stash .take (key ) == True :
34- break
54+ with stash .lock :
55+ if stash .take (key ) == True :
56+ # `take` is destructive, so put the value straight back --
57+ # under the lock, making the pair atomic -- so that every
58+ # request waiting on this key is released, not just whichever
59+ # one happened to get here first.
60+ stash .put (key , True )
61+ break
62+ if time .monotonic () > deadline :
63+ return
3564 time .sleep (0.1 )
3665
3766 # Send the rest of the data.
0 commit comments