@@ -165,6 +165,38 @@ impl<T, CondVal: Default + Eq> ColdQueue<T, CondVal> {
165165 self . cold_queue . push_back ( ( id, value) ) ;
166166 ColdQueueAddIfNotSatisfiedResult :: Enqueued
167167 }
168+
169+ /// Adds an element to the cold queue based on condition evaluation:
170+ /// - if satisfied: it to the front of the queue
171+ /// - if not: at the end
172+ /// and modifies barriers accordingly.
173+ pub ( self ) fn ingest ( & mut self , id : UniqueId , value : T ) {
174+ self . update_condition_value ( ) ;
175+ if ( self . condition ) ( & self . last_condition_value , & value) {
176+ self . cold_queue . push_front ( ( id, value) ) ;
177+ self . cold_ready += 1 ;
178+ self . cold_available += 1 ;
179+ } else {
180+ self . cold_queue . push_back ( ( id, value) ) ;
181+ }
182+ }
183+
184+ /// Removes and returns the first element satisfying the condition with
185+ /// supplied condition value.
186+ /// Modifies barriers accordingly.
187+ pub ( self ) fn take_first_matching ( & mut self , cond_val : & CondVal ) -> Option < ( UniqueId , T ) > {
188+ let pos = self
189+ . cold_queue
190+ . iter ( )
191+ . position ( |( _, val) | ( self . condition ) ( cond_val, val) ) ?;
192+ if pos < self . cold_ready {
193+ self . cold_ready -= 1 ;
194+ }
195+ if pos < self . cold_available {
196+ self . cold_available -= 1 ;
197+ }
198+ self . cold_queue . remove ( pos)
199+ }
168200}
169201
170202enum ColdQueueTakeResult < T > {
@@ -270,6 +302,31 @@ where
270302 }
271303 }
272304
305+ pub async fn take_owned_matching ( & self , cond_val : CondVal ) -> ( UniqueId , T ) {
306+ loop {
307+ {
308+ let mut cold = self . cold_queue . lock ( ) . unwrap ( ) ;
309+ while let Some ( Ok ( ( id, value) ) ) = self . hot_receiver . recv_async ( ) . now_or_never ( ) {
310+ cold. ingest ( id, value) ;
311+ }
312+ if let Some ( taken) = cold. take_first_matching ( & cond_val) {
313+ return taken;
314+ }
315+ }
316+ // If the cold queue is exhausted, wait for a new element.
317+ tokio:: select! {
318+ _ = self . clock. sleep( near_time:: Duration :: seconds( 1 ) ) => {
319+ continue ;
320+ }
321+ received = self . hot_receiver. recv_async( ) => {
322+ // can't fail, because self keeps a sender.
323+ let ( id, val) = received. unwrap( ) ;
324+ self . cold_queue. lock( ) . unwrap( ) . ingest( id, val) ;
325+ }
326+ }
327+ }
328+ }
329+
273330 /// Process `num_elements_to_process`, removing any that doesn't satisfy condition.
274331 /// Return ids, that were removed from cold storage.
275332 pub async fn maybe_discard_owned ( & self , mut num_elements_to_process : usize ) -> Vec < UniqueId > {
@@ -569,6 +626,18 @@ where
569626 result
570627 }
571628
629+ // Takes an owned asset whose participants are all in eligible.
630+ // Blocks until there is an asset available satisfying the condition.
631+ pub async fn take_owned_matching ( & self , eligible : Vec < ParticipantId > ) -> ( UniqueId , T ) {
632+ let ( id, val) = self . owned_queue . take_owned_matching ( eligible) . await ;
633+ let mut update = self . db . update ( ) ;
634+ update. delete ( self . col , & self . make_key ( id) ) ;
635+ update
636+ . commit ( )
637+ . expect ( "Unrecoverable error writing to database" ) ;
638+ ( id, val)
639+ }
640+
572641 fn take_unowned_inner ( & self , id : UniqueId ) -> anyhow:: Result < T > {
573642 let key = self . make_key ( id) ;
574643 let value_ser = self . db . get ( self . col , & key) ?. ok_or_else ( || {
@@ -1398,4 +1467,76 @@ mod tests {
13981467 }
13991468 }
14001469 }
1470+
1471+ #[ test]
1472+ #[ expect( non_snake_case) ]
1473+ fn take_owned_matching__should_return_matching_asset_immediately ( ) {
1474+ // Given: a queue holding one non-matching and one matching asset.
1475+ let clock = FakeClock :: default ( ) ;
1476+ let queue = DoubleQueue :: new ( clock. clock ( ) , |cond, val| val % 2 == * cond, Arc :: new ( || 0 ) ) ;
1477+ let id1 = UniqueId :: new ( ParticipantId :: from_raw ( 42 ) , 123 , 456 ) ;
1478+ let id2 = id1. add_to_counter ( 1 ) . unwrap ( ) ;
1479+ queue. add_owned ( id1, 2 ) ;
1480+ queue. add_owned ( id2, 3 ) ;
1481+
1482+ // When: taking with a condition value matching only the second asset.
1483+ let taken = queue. take_owned_matching ( 1 ) . now_or_never ( ) ;
1484+
1485+ // Then: the matching asset is returned and the non-matching one stays.
1486+ assert_eq ! ( taken, Some ( ( id2, 3 ) ) ) ;
1487+ assert_eq ! ( queue. available( ) , 1 ) ;
1488+ }
1489+
1490+ #[ test]
1491+ #[ expect( non_snake_case) ]
1492+ fn take_owned_matching__should_wait_until_matching_asset_is_added ( ) {
1493+ // Given: a queue holding only a non-matching asset.
1494+ let clock = FakeClock :: default ( ) ;
1495+ let queue = DoubleQueue :: new ( clock. clock ( ) , |cond, val| val % 2 == * cond, Arc :: new ( || 0 ) ) ;
1496+ let id1 = UniqueId :: new ( ParticipantId :: from_raw ( 42 ) , 123 , 456 ) ;
1497+ let id2 = id1. add_to_counter ( 1 ) . unwrap ( ) ;
1498+ queue. add_owned ( id1, 2 ) ;
1499+
1500+ // When: taking with a condition value nothing matches yet.
1501+ let fut = queue. take_owned_matching ( 1 ) ;
1502+ let MaybeReady :: Future ( fut) = run_future_once ( fut) else {
1503+ panic ! ( "should not take a value when no element matches" ) ;
1504+ } ;
1505+
1506+ // Then: the take completes once a matching asset is added.
1507+ queue. add_owned ( id2, 3 ) ;
1508+ assert_eq ! ( fut. now_or_never( ) . unwrap( ) , ( id2, 3 ) ) ;
1509+ // And: the non-matching asset was not consumed.
1510+ assert_eq ! ( queue. available( ) , 1 ) ;
1511+ }
1512+
1513+ #[ test]
1514+ #[ expect( non_snake_case) ]
1515+ fn take_first_matching__should_maintain_barrier_invariants ( ) {
1516+ // Given: a cold queue partitioned into ready and non-satisfying sections.
1517+ let clock = FakeClock :: default ( ) ;
1518+ let mut queue = ColdQueue :: new ( clock. clock ( ) , |cond, val| val % 2 == * cond, Arc :: new ( || 0 ) ) ;
1519+ let id1 = UniqueId :: new ( ParticipantId :: from_raw ( 42 ) , 1 , 0 ) ;
1520+ let id2 = id1. add_to_counter ( 1 ) . unwrap ( ) ;
1521+ let id3 = id1. add_to_counter ( 2 ) . unwrap ( ) ;
1522+ queue. ingest ( id1, 2 ) ;
1523+ queue. ingest ( id2, 4 ) ;
1524+ queue. ingest ( id3, 3 ) ;
1525+ verify_cold_queue_internal_consistency ( & queue, 3 ) ;
1526+
1527+ // When: taking with a condition value differing from the standing one.
1528+ let taken = queue. take_first_matching ( & 1 ) ;
1529+
1530+ // Then: the non-satisfying element is removed, barriers stay consistent.
1531+ assert_eq ! ( taken, Some ( ( id3, 3 ) ) ) ;
1532+ verify_cold_queue_internal_consistency ( & queue, 2 ) ;
1533+
1534+ // And: taking under the standing value removes from the ready section.
1535+ assert_eq ! ( queue. take_first_matching( & 0 ) , Some ( ( id2, 4 ) ) ) ;
1536+ verify_cold_queue_internal_consistency ( & queue, 1 ) ;
1537+
1538+ // And: no match leaves the queue untouched.
1539+ assert_eq ! ( queue. take_first_matching( & 1 ) , None ) ;
1540+ verify_cold_queue_internal_consistency ( & queue, 1 ) ;
1541+ }
14011542}
0 commit comments