use crate::adapter::net::behavior::fold::{Fold, IslandId, NodeId, ReservationFold};
use crate::adapter::net::identity::EntityKeypair;
use super::claim::{single_island_claim, ClaimError, ClaimOutcome};
pub fn claim_first_available(
reservations: &Fold<ReservationFold>,
keypair: &EntityKeypair,
node_id: NodeId,
generation: &mut u64,
islands: &[IslandId],
until_unix_us: u64,
) -> Result<Option<IslandId>, ClaimError> {
for &island in islands {
let gen = *generation;
*generation += 1;
match single_island_claim(reservations, keypair, node_id, gen, island, until_unix_us)? {
ClaimOutcome::Won => return Ok(Some(island)),
ClaimOutcome::Lost => continue,
}
}
Ok(None)
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use std::sync::{Arc, Barrier};
use std::time::Duration;
use super::*;
use crate::adapter::net::behavior::fold::{ReservationQuery, ReservationState};
use crate::adapter::net::current_timestamp_micros;
fn new_reservations() -> Fold<ReservationFold> {
Fold::with_sweep_interval(Duration::ZERO)
}
fn fresh_deadline() -> u64 {
current_timestamp_micros() + 60_000_000
}
#[test]
fn claim_first_available_skips_held_islands() {
let fold = new_reservations();
let holder = EntityKeypair::generate();
let hn = holder.entity_id().node_id();
let mut g = 1;
claim_first_available(&fold, &holder, hn, &mut g, &[0x10, 0x11], fresh_deadline()).unwrap();
super::single_island_claim(&fold, &holder, hn, g, 0x11, fresh_deadline()).unwrap();
let claimant = EntityKeypair::generate();
let cn = claimant.entity_id().node_id();
let mut cg = 1;
let got = claim_first_available(
&fold,
&claimant,
cn,
&mut cg,
&[0x10, 0x11, 0x12],
fresh_deadline(),
)
.unwrap();
assert_eq!(got, Some(0x12), "must skip the two held islands");
assert_eq!(
fold.query(ReservationQuery::State(0x12))[0].1.holder(),
Some(cn)
);
}
#[test]
fn claim_first_available_returns_none_when_all_held() {
let fold = new_reservations();
let holder = EntityKeypair::generate();
let hn = holder.entity_id().node_id();
for (i, island) in [0x10, 0x11].iter().enumerate() {
super::single_island_claim(&fold, &holder, hn, i as u64 + 1, *island, fresh_deadline())
.unwrap();
}
let claimant = EntityKeypair::generate();
let cn = claimant.entity_id().node_id();
let mut cg = 1;
let got = claim_first_available(
&fold,
&claimant,
cn,
&mut cg,
&[0x10, 0x11],
fresh_deadline(),
)
.unwrap();
assert_eq!(got, None);
}
#[test]
fn concurrent_claimants_yield_one_winner_per_island() {
const N: usize = 24; const M: u64 = 8;
let fold = Arc::new(new_reservations());
let islands: Vec<IslandId> = (0x100..0x100 + M).collect();
let barrier = Arc::new(Barrier::new(N));
let deadline = fresh_deadline();
let handles: Vec<_> = (0..N)
.map(|_| {
let fold = fold.clone();
let barrier = barrier.clone();
let islands = islands.clone();
std::thread::spawn(move || {
let kp = EntityKeypair::generate();
let node = kp.entity_id().node_id();
let mut gen = 1u64;
barrier.wait();
let won = claim_first_available(&fold, &kp, node, &mut gen, &islands, deadline)
.expect("claim attempt");
won.map(|island| (node, island))
})
})
.collect();
let winners: Vec<(NodeId, IslandId)> = handles
.into_iter()
.filter_map(|h| h.join().unwrap())
.collect();
assert_eq!(
winners.len() as u64,
M,
"exactly one winner per island, no partial/extra holds",
);
let won_islands: HashSet<IslandId> = winners.iter().map(|(_, i)| *i).collect();
assert_eq!(won_islands.len() as u64, M, "no island claimed twice");
assert_eq!(
won_islands,
islands.iter().copied().collect::<HashSet<_>>(),
"every island ended up claimed",
);
for (node, island) in &winners {
let state = fold.query(ReservationQuery::State(*island));
assert!(
matches!(state[0].1, ReservationState::Reserved { holder, .. } if holder == *node),
"island {island:#x} must be Reserved by its claimed winner {node:#x}",
);
}
}
}