@@ -118,9 +118,12 @@ def __init__(
118
118
self ._request_cache = deque [Request ]()
119
119
"""Cache for requests: forefront requests at the beginning, regular requests at the end."""
120
120
121
- self ._cache_needs_refresh = True
121
+ self ._request_cache_needs_refresh = True
122
122
"""Flag indicating whether the cache needs to be refreshed from filesystem."""
123
123
124
+ self ._is_empty_cache : bool | None = None
125
+ """Cache for is_empty result: None means unknown, True/False is cached state."""
126
+
124
127
@property
125
128
@override
126
129
def metadata (self ) -> RequestQueueMetadata :
@@ -203,10 +206,23 @@ async def open(
203
206
204
207
# If the RQ directory exists, reconstruct the client from the metadata file.
205
208
if rq_path .exists ():
206
- # If metadata file is missing, raise an error.
207
209
if not metadata_path .exists ():
208
- raise ValueError (f'Metadata file not found for request queue "{ name } "' )
209
-
210
+ now = datetime .now (timezone .utc )
211
+ metadata = RequestQueueMetadata (
212
+ id = crypto_random_object_id (),
213
+ name = name ,
214
+ created_at = now ,
215
+ accessed_at = now ,
216
+ modified_at = now ,
217
+ had_multiple_clients = False ,
218
+ handled_request_count = 0 ,
219
+ pending_request_count = 0 ,
220
+ stats = {},
221
+ total_request_count = 0 ,
222
+ )
223
+ await asyncio .to_thread (rq_path .mkdir , parents = True , exist_ok = True )
224
+ data = await json_dumps (metadata .model_dump ())
225
+ await atomic_write (metadata_path , data )
210
226
file = await asyncio .to_thread (open , metadata_path )
211
227
try :
212
228
file_content = json .load (file )
@@ -260,7 +276,10 @@ async def drop(self) -> None:
260
276
261
277
self ._in_progress .clear ()
262
278
self ._request_cache .clear ()
263
- self ._cache_needs_refresh = True
279
+ self ._request_cache_needs_refresh = True
280
+
281
+ # Invalidate is_empty cache.
282
+ self ._is_empty_cache = None
264
283
265
284
@override
266
285
async def purge (self ) -> None :
@@ -272,15 +291,17 @@ async def purge(self) -> None:
272
291
273
292
self ._in_progress .clear ()
274
293
self ._request_cache .clear ()
275
- self ._cache_needs_refresh = True
294
+ self ._request_cache_needs_refresh = True
276
295
277
- # Update metadata counts
278
296
await self ._update_metadata (
279
297
update_modified_at = True ,
280
298
update_accessed_at = True ,
281
299
new_pending_request_count = 0 ,
282
300
)
283
301
302
+ # Invalidate is_empty cache.
303
+ self ._is_empty_cache = None
304
+
284
305
@override
285
306
async def add_batch_of_requests (
286
307
self ,
@@ -298,6 +319,7 @@ async def add_batch_of_requests(
298
319
Response containing information about the added requests.
299
320
"""
300
321
async with self ._lock :
322
+ self ._is_empty_cache = None
301
323
new_total_request_count = self ._metadata .total_request_count
302
324
new_pending_request_count = self ._metadata .pending_request_count
303
325
processed_requests = list [ProcessedRequest ]()
@@ -409,7 +431,10 @@ async def add_batch_of_requests(
409
431
410
432
# Invalidate the cache if we added forefront requests.
411
433
if forefront :
412
- self ._cache_needs_refresh = True
434
+ self ._request_cache_needs_refresh = True
435
+
436
+ # Invalidate is_empty cache.
437
+ self ._is_empty_cache = None
413
438
414
439
return AddRequestsResponse (
415
440
processed_requests = processed_requests ,
@@ -450,7 +475,7 @@ async def fetch_next_request(self) -> Request | None:
450
475
"""
451
476
async with self ._lock :
452
477
# Refresh cache if needed or if it's empty.
453
- if self ._cache_needs_refresh or not self ._request_cache :
478
+ if self ._request_cache_needs_refresh or not self ._request_cache :
454
479
await self ._refresh_cache ()
455
480
456
481
next_request : Request | None = None
@@ -481,6 +506,8 @@ async def mark_request_as_handled(self, request: Request) -> ProcessedRequest |
481
506
Information about the queue operation. `None` if the given request was not in progress.
482
507
"""
483
508
async with self ._lock :
509
+ self ._is_empty_cache = None
510
+
484
511
# Check if the request is in progress.
485
512
if request .id not in self ._in_progress :
486
513
logger .warning (f'Marking request { request .id } as handled that is not in progress.' )
@@ -537,6 +564,8 @@ async def reclaim_request(
537
564
Information about the queue operation. `None` if the given request was not in progress.
538
565
"""
539
566
async with self ._lock :
567
+ self ._is_empty_cache = None
568
+
540
569
# Check if the request is in progress.
541
570
if request .id not in self ._in_progress :
542
571
logger .info (f'Reclaiming request { request .id } that is not in progress.' )
@@ -587,28 +616,35 @@ async def reclaim_request(
587
616
588
617
@override
589
618
async def is_empty (self ) -> bool :
590
- """Check if the queue is empty.
591
-
592
- Returns:
593
- True if the queue is empty, False otherwise.
594
- """
619
+ """Check if the queue is empty, using a cached value if available and valid."""
595
620
async with self ._lock :
621
+ # If we have a cached value, return it immediately.
622
+ if self ._is_empty_cache is not None :
623
+ return self ._is_empty_cache
624
+
625
+ # If we have a cached requests, check them first (fast path).
626
+ if self ._request_cache :
627
+ for req in self ._request_cache :
628
+ if req .handled_at is None :
629
+ self ._is_empty_cache = False
630
+ return False
631
+ self ._is_empty_cache = True
632
+ return True
633
+
634
+ # Fallback: check files on disk (slow path).
596
635
await self ._update_metadata (update_accessed_at = True )
597
636
request_files = await self ._get_request_files (self .path_to_rq )
598
637
599
- # Check each file to see if there are any unhandled requests.
600
638
for request_file in request_files :
601
639
request = await self ._parse_request_file (request_file )
602
-
603
640
if request is None :
604
641
continue
605
-
606
- # If any request is not handled, the queue is not empty.
607
642
if request .handled_at is None :
643
+ self ._is_empty_cache = False
608
644
return False
609
645
610
- # If we got here, all requests are handled or there are no requests.
611
- return True
646
+ self . _is_empty_cache = True
647
+ return True
612
648
613
649
def _get_request_path (self , request_id : str ) -> Path :
614
650
"""Get the path to a specific request file.
@@ -732,7 +768,7 @@ async def _refresh_cache(self) -> None:
732
768
break
733
769
self ._request_cache .append (request )
734
770
735
- self ._cache_needs_refresh = False
771
+ self ._request_cache_needs_refresh = False
736
772
737
773
@classmethod
738
774
async def _get_request_files (cls , path_to_rq : Path ) -> list [Path ]:
0 commit comments