suite: async past_stamp_sleep for current-thread flavor readiness
Replace the harness's blocking std::thread::sleep with tokio::time::sleep across all nine call sites, note the no-blocking-calls-inside-rows posture in the helper's doc comment, and mark the task completed. Both engines' contract suites re-run green with assertions and tolerances unchanged (ADR-024 \u00a75 item 4).
This commit is contained in:
1 parent
a48c12745a
commit
6f9cfb5778
2 files changed
+44
-18
No files matched your search
@@ -569,7 +569,7 @@ pub async fn lock_ttl_expiry_and_reacquisition(factory: &dyn StoreFactory) {
|
|||||||
// second-precision stamp's resolution): exclusion lapses silently
|
// second-precision stamp's resolution): exclusion lapses silently
|
||||||
// — the second owner's acquire is a clean Ok(Some) with no error
|
// — the second owner's acquire is a clean Ok(Some) with no error
|
||||||
// preceding it and no revocation event to observe.
|
// preceding it and no revocation event to observe.
|
||||||
past_stamp_sleep(2_500);
|
past_stamp_sleep(2_500).await;
|
||||||
let second = store
|
let second = store
|
||||||
.try_lock("lapse", "b", 300)
|
.try_lock("lapse", "b", 300)
|
||||||
.await
|
.await
|
||||||
@@ -1680,8 +1680,14 @@ pub async fn payload_too_large_produced_on_pg(factory: &dyn StoreFactory) {
|
|||||||
/// clock read `> T + 1`; 2.5 s guarantees it, 3.5 s the `expires: 2`
|
/// clock read `> T + 1`; 2.5 s guarantees it, 3.5 s the `expires: 2`
|
||||||
/// stamps). Timing posture: state outcomes are asserted, never
|
/// stamps). Timing posture: state outcomes are asserted, never
|
||||||
/// durations.
|
/// durations.
|
||||||
fn past_stamp_sleep(millis: u64) {
|
///
|
||||||
std::thread::sleep(std::time::Duration::from_millis(millis));
|
/// Harness posture: **no blocking calls inside suite rows** — this
|
||||||
|
/// helper is async (`tokio::time::sleep`) and every row awaits it;
|
||||||
|
/// the mem engine's suite column runs the current-thread flavor
|
||||||
|
/// ([ADR-024](../../docs/architecture/decisions/024-mem-engine.md)
|
||||||
|
/// §5), which is what catches a blocking stall here.
|
||||||
|
async fn past_stamp_sleep(millis: u64) {
|
||||||
|
tokio::time::sleep(std::time::Duration::from_millis(millis)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// **Job-handle validity predicate** — the uniform rule (processing
|
/// **Job-handle validity predicate** — the uniform rule (processing
|
||||||
@@ -1785,7 +1791,7 @@ pub async fn job_handle_validity_predicate(factory: &dyn StoreFactory) {
|
|||||||
"claims hand out in enqueue order (equal priority)"
|
"claims hand out in enqueue order (equal priority)"
|
||||||
);
|
);
|
||||||
let retry_before = lapsed_retry.job().clone();
|
let retry_before = lapsed_retry.job().clone();
|
||||||
past_stamp_sleep(2_500);
|
past_stamp_sleep(2_500).await;
|
||||||
assert!(
|
assert!(
|
||||||
!lapsed_ack.heartbeat(600).await.unwrap(),
|
!lapsed_ack.heartbeat(600).await.unwrap(),
|
||||||
"late heartbeat must be refused: the deadline has lapsed"
|
"late heartbeat must be refused: the deadline has lapsed"
|
||||||
@@ -1845,7 +1851,7 @@ pub async fn job_handle_validity_predicate(factory: &dyn StoreFactory) {
|
|||||||
batch_live.heartbeat(600).await.unwrap(),
|
batch_live.heartbeat(600).await.unwrap(),
|
||||||
"the renewal keeps this id's claim live through the batch"
|
"the renewal keeps this id's claim live through the batch"
|
||||||
);
|
);
|
||||||
past_stamp_sleep(2_500);
|
past_stamp_sleep(2_500).await;
|
||||||
let counted = batch
|
let counted = batch
|
||||||
.ack_batch(&[batch_live_id, batch_lapse_id, 987_654_321])
|
.ack_batch(&[batch_live_id, batch_lapse_id, 987_654_321])
|
||||||
.await
|
.await
|
||||||
@@ -1967,7 +1973,7 @@ pub async fn queue_depth_reclaim_and_dead_letter(factory: &dyn StoreFactory) {
|
|||||||
.job()
|
.job()
|
||||||
.claim_expires_at
|
.claim_expires_at
|
||||||
.expect("claim stamps the deadline");
|
.expect("claim stamps the deadline");
|
||||||
past_stamp_sleep(2_500);
|
past_stamp_sleep(2_500).await;
|
||||||
|
|
||||||
let second = depth
|
let second = depth
|
||||||
.claim_one("w2")
|
.claim_one("w2")
|
||||||
@@ -2002,7 +2008,7 @@ pub async fn queue_depth_reclaim_and_dead_letter(factory: &dyn StoreFactory) {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
depth.claim_one("w1").await.unwrap().unwrap();
|
depth.claim_one("w1").await.unwrap().unwrap();
|
||||||
past_stamp_sleep(2_500);
|
past_stamp_sleep(2_500).await;
|
||||||
let at_budget = depth.claim_one("w2").await.unwrap().unwrap();
|
let at_budget = depth.claim_one("w2").await.unwrap().unwrap();
|
||||||
assert_eq!(at_budget.job().id, exhausted);
|
assert_eq!(at_budget.job().id, exhausted);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2010,7 +2016,7 @@ pub async fn queue_depth_reclaim_and_dead_letter(factory: &dyn StoreFactory) {
|
|||||||
at_budget.job().max_attempts,
|
at_budget.job().max_attempts,
|
||||||
"the reclaim spent the budget"
|
"the reclaim spent the budget"
|
||||||
);
|
);
|
||||||
past_stamp_sleep(2_500);
|
past_stamp_sleep(2_500).await;
|
||||||
assert!(
|
assert!(
|
||||||
depth.claim_one("w3").await.unwrap().is_none(),
|
depth.claim_one("w3").await.unwrap().is_none(),
|
||||||
"the budget-spent row dead-letters instead of reclaiming"
|
"the budget-spent row dead-letters instead of reclaiming"
|
||||||
@@ -2139,7 +2145,7 @@ pub async fn sweep_no_stranded_rows(factory: &dyn StoreFactory) {
|
|||||||
.enqueue(json!({"leg": "never-expiring"}), EnqueueOpts::default())
|
.enqueue(json!({"leg": "never-expiring"}), EnqueueOpts::default())
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
past_stamp_sleep(3_500);
|
past_stamp_sleep(3_500).await;
|
||||||
|
|
||||||
let moved = sweeping.sweep_expired().await.unwrap();
|
let moved = sweeping.sweep_expired().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2195,7 +2201,7 @@ pub async fn sweep_no_stranded_rows(factory: &dyn StoreFactory) {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let kept_dies = keeping.claim_one("w1").await.unwrap().unwrap();
|
let kept_dies = keeping.claim_one("w1").await.unwrap().unwrap();
|
||||||
assert!(kept_dies.fail(None).await.unwrap());
|
assert!(kept_dies.fail(None).await.unwrap());
|
||||||
past_stamp_sleep(2_500);
|
past_stamp_sleep(2_500).await;
|
||||||
|
|
||||||
let deleted = retained.sweep_expired().await.unwrap();
|
let deleted = retained.sweep_expired().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -2479,7 +2485,7 @@ pub async fn scheduler_bounded_catchup(factory: &dyn StoreFactory) {
|
|||||||
// Several boundaries elapse (the first sits one interval past
|
// Several boundaries elapse (the first sits one interval past
|
||||||
// registration; a 4 s runner-less pause leaves at least three
|
// registration; a 4 s runner-less pause leaves at least three
|
||||||
// elapsed) — still nothing, no runner in sight.
|
// elapsed) — still nothing, no runner in sight.
|
||||||
past_stamp_sleep(4_000);
|
past_stamp_sleep(4_000).await;
|
||||||
collect_fires(store.as_ref(), "catchup", &mut fired).await;
|
collect_fires(store.as_ref(), "catchup", &mut fired).await;
|
||||||
assert!(
|
assert!(
|
||||||
fired.is_empty(),
|
fired.is_empty(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
id: suite-harness-current-thread
|
id: suite-harness-current-thread
|
||||||
name: Contract-suite harness — async sleep in `past_stamp_sleep`, current-thread flavor readiness
|
name: Contract-suite harness — async sleep in `past_stamp_sleep`, current-thread flavor readiness
|
||||||
status: pending
|
status: completed
|
||||||
depends_on: []
|
depends_on: []
|
||||||
scope: narrow
|
scope: narrow
|
||||||
risk: low
|
risk: low
|
||||||
@@ -38,12 +38,12 @@ This task is engine-independent and can run any time before
|
|||||||
|
|
||||||
## Acceptance Criteria
|
## Acceptance Criteria
|
||||||
|
|
||||||
- [ ] `past_stamp_sleep` is async (`tokio::time::sleep`)
|
- [x] `past_stamp_sleep` is async (`tokio::time::sleep`)
|
||||||
- [ ] Both engines' existing suites re-run green with identical
|
- [x] Both engines' existing suites re-run green with identical
|
||||||
assertions and tolerances (determinism posture intact)
|
assertions and tolerances (determinism posture intact)
|
||||||
- [ ] The no-blocking-calls posture noted where the harness's
|
- [x] The no-blocking-calls posture noted where the harness's
|
||||||
determinism posture is documented
|
determinism posture is documented
|
||||||
- [ ] Clippy `-D warnings`, fmt clean; workspace gates green
|
- [x] Clippy `-D warnings`, fmt clean; workspace gates green
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
@@ -55,8 +55,28 @@ This task is engine-independent and can run any time before
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
> To be filled by implementation agent
|
- The posture note went into `past_stamp_sleep`'s doc comment rather
|
||||||
|
than the factory contract notes: the posture is a harness rule about
|
||||||
|
row internals (what a row may call), not part of the
|
||||||
|
`StoreFactory` shape, and the doc comment sits exactly where a row
|
||||||
|
author reaches for a sleep. The comment cites ADR-024 §5 for the
|
||||||
|
current-thread flavor that enforces it.
|
||||||
|
- Five rows exercise the helper across its two use shapes (the
|
||||||
|
lapse-settle legs and the scheduler runner-less-pause leg):
|
||||||
|
`lock_ttl_expiry_and_reacquisition`, `job_handle_validity_predicate`,
|
||||||
|
`queue_depth_reclaim_and_dead_letter`, `sweep_no_stranded_rows`,
|
||||||
|
`scheduler_bounded_catchup`. All nine call sites gained `.await`;
|
||||||
|
assertions and tolerances untouched.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
> To be filled on completion
|
`past_stamp_sleep` now awaits `tokio::time::sleep` instead of blocking
|
||||||
|
on `std::thread::sleep` (alkstore-contract-suite/src/properties.rs),
|
||||||
|
making every suite row current-thread-safe for the mem engine's
|
||||||
|
`#[tokio::test(flavor = "current_thread")]` column (ADR-024 §5 item 4);
|
||||||
|
the doc comment records the no-blocking-calls-inside-rows posture.
|
||||||
|
Both engines' `contract_suite` re-ran fully green (25/25 each, SQLite
|
||||||
|
15.0 s / Postgres 21.3 s — no timing drift, tolerances unchanged); the
|
||||||
|
full workspace build, tests, `clippy -D warnings`, and `fmt --check`
|
||||||
|
pass. PostgreSQL leg ran against the POC container (`pglo-poc`, port
|
||||||
|
15432, creds per docs/reviews/003 §env note).
|
||||||
Reference in new issue
Block a user