Repository navigation
Fixed flakiness in integration tests - #378
Conversation
aaronmondal
left a comment
There was a problem hiding this comment.
Reviewed 1 of 1 files at r1, all commit messages.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @blakehatch)
run_integration_tests.sh line 111 at r1 (raw file):
echo "String 'Ready, listening on' not found in the logs within the given time." fi set +e
Hmm would there be a way to do this without the perl wrapper?
Also somewhat related question: WDYT about migrating these tests to rust in the long run? A tricky thing here seems to be getting cross-platform compatibility right. Rust might help with that.
blakehatch
left a comment
There was a problem hiding this comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on @MarcusSorealheis)
run_integration_tests.sh line 111 at r1 (raw file):
Previously, aaronmondal (Aaron Siddhartha Mondal) wrote…
Hmm would there be a way to do this without the perl wrapper?
Also somewhat related question: WDYT about migrating these tests to rust in the long run? A tricky thing here seems to be getting cross-platform compatibility right. Rust might help with that.
I originally tried timeout 30 bash -c 'until sudo docker-compose logs | grep -q "Ready, listening on"; do sleep 1; done' which is cleaner and works on Linux but the integration tests recently had some functionality added to make it support MacOS so I decided to use a command that works on all Unix-like OSes.
@MarcusSorealheis and I were discussing converting these integration tests to Python instead of Bash to fix the cross-platform compatibility while making the translation from Bash a bit simpler than rust. I do agree high level that they should definitely be converted in some form to have better cross-platform compatibility than the currently do.
aaronmondal
left a comment
There was a problem hiding this comment.
I guess let's just do it this way then. I'm not a huge fan, but it doesn't seem to really matter if we plan to port this to some other language anyways 💃
Reviewable status:
complete! all files reviewed, all discussions resolved (waiting on @blakehatch)
blakehatch
left a comment
There was a problem hiding this comment.
Yeah agree not a huge fan either but best not to break functionality in the meantime, esp in the context of the likely switch as you said.🕺
Reviewable status:
complete! all files reviewed, all discussions resolved (waiting on @blakehatch)
allada
left a comment
There was a problem hiding this comment.
Reviewable status: all files reviewed, 1 unresolved discussion
run_integration_tests.sh line 105 at r1 (raw file):
echo "Running test $FILENAME" sudo docker-compose up -d if perl -e 'alarm shift; exec @ARGV' 30 bash -c 'until sudo docker-compose logs | grep -q "Ready, listening on"; do sleep 1; done'
nit: spaces at end of line.
allada
left a comment
There was a problem hiding this comment.
Reviewable status: all files reviewed, 2 unresolved discussions
run_integration_tests.sh line 105 at r1 (raw file):
Previously, allada (Nathan (Blaise) Bruer) wrote…
nit: spaces at end of line.
Btw, this could have been done with a bash for loop in a function.
function wait_for_ready() {
for i in $(seq 1 10); do
if sudo docker-compose logs | grep -q "Ready, listening on"; then
return
fi
sleep 1
done
echo "Timed out"
exit 1
}
wait_for_readyrun_integration_tests.sh line 109 at r1 (raw file):
echo "String 'Ready, listening on' found in the logs." else echo "String 'Ready, listening on' not found in the logs within the given time."
nit: Also we don't exit fail here so this message will be buried deep in the logs.
MarcusSorealheis
left a comment
There was a problem hiding this comment.
It's merged and is totally fine. A small nit
In the future, I'd like to avoid Perl. There were some exceptions to this rule a few years ago, but I don't know if even those remain valid reasons to insist on using Perl. I would to need to look at some references.
Reviewable status: all files reviewed, 2 unresolved discussions
…ceMachina#100) Reframe of the keyspace-notification dispatcher from "closes the stale-positive class for ExistenceCacheStore wrapping RedisStore" to **defensive infrastructure for Redis (Valkey) keyspace eviction notifications. Currently dormant in production (Valkey on buildcache has had zero evictions in this server's lifetime; deployed for forward-compat against future workloads stressing the eviction path).** The original framing overclaimed; the design doc §0 already established the zero-eviction baseline. Addresses 6 BLOCKER findings from the 681ae250 reviewer pass (code-reviewer / perf-optimizer / security-reviewer / red-team / distributed-systems-reviewer): - BLOCKER 1 (silent init failure): convert `RedisStore::new_standard` to perform CONFIG GET / SET / 3× PSUBSCRIBE eagerly, before the Arc is handed back. Failure → `Err` from the constructor → server fails to start with a clear error. `register_item_callback` becomes a sync fast-path-only operation (no background spawn that swallows failures); ECS panics loudly via the `.expect()` if the dispatcher is not wired. - BLOCKER 2 (consume-before-validate `take()`): defer `subscriber_channel.lock().take()` to the LAST step of eager init, so any failure leaves the channel still in `Mutex<Option<...>>`. The misleading "already consumed by SchedulerSubscriptionManager" error on retries is now unreachable through legitimate paths. - BLOCKER 3 (`StoreKey::Str` over-action): `parse_keyspace_payload` drops non-digest payloads at the parser. Admin `DEL cas:debug-foo` and any non-digest scheduler/index keys never propagate to ECS or other ItemCallback consumers. The Str fallback was perf+log-leak waste, not a panic risk (`StoreKey::into_digest` blake3-hashes), but guaranteed-no-op cache misses + foreign-tenant info leaks are still worth filtering. - BLOCKER 4 (CONFIG SET non-persistent across Valkey restart): `StandardRedisManager::reconnect_with` re-issues `CONFIG SET notify-keyspace-events <flags>` on every subscriber-slot reconnect before replaying PSUBSCRIBE. Without this, a Valkey restart silently reverts the in-memory CONFIG to whatever `valkey.conf` says (typically empty), the dispatcher keeps running, and ECS retains stale positives indefinitely. Operator should ALSO persist `notify-keyspace-events Eegx` in `valkey.conf` for resiliency across NativeLink restarts. - BLOCKER 5 (shared subscriber_channel): `set_spec_defaults` rejects the `enable_keyspace_notifications=true` + `experimental_pub_sub_channel.is_some()` collision at construction time with `Code::FailedPrecondition`. Both consumers want the single Redis push-sender; the loser silently degrades to no-op invalidation today. Fan-out task (per design §1.1) remains future work. - BLOCKER 6 (CI gating): wire `--features redis-integration-tests` into `.github/workflows/native-cargo.yaml` with a Linux-only `valkey-server` install step. Default `cargo test` on dev boxes remains a no-op for these tests (the test bin is `#![cfg(...)]`-gated out, not silently skipped). Hardening (security-reviewer MEDIUMs): - `keyspace_notifications_db: i64` → `u8` (Redis db indices are non-negative single bytes in practice). - `set_spec_defaults` cross-checks `keyspace_notifications_db` against the URL-embedded db; mismatch is `Err` instead of silent listen-on-wrong-db. - Empty `key_prefix` with `enable_keyspace_notifications=true` warns loudly at construction (foreign-tenant DELs would otherwise dispatch into our ItemCallback consumers). Tests: - New regression `non_digest_key_under_prefix_does_not_fire_callback` (BLOCKER 3): asserts a digest-shaped positive control reaches the callback while a same-prefix non-digest DEL is dropped at the parser. - New regression `keyspace_and_scheduler_collision_refused_at_construction` (BLOCKER 5): `RedisStore::new_standard` returns `Err` when both flags are set; bespoke message verifies the rejection text. - Existing `foreign_prefix_does_not_fire_callback` updated to use a digest-shaped positive control (the previous `cas:positive-control` string-key would now be filtered by BLOCKER 3 — the test relied on that pre-fix Str fallback). - All 8 integration tests pass against real `valkey-server` v9.0.4 with the `redis-integration-tests` feature enabled. Mutation-step verification (CLAUDE.md TDD rule 5): - BLOCKER 1: comment out `store.init_keyspace_dispatcher_eager()` in `new_standard` → integration test TraceMachina#6 (`existence_cache_drops_positive_after_redis_eviction`) panics with `register_item_callback` returning Err and ECS `.expect()` firing. - BLOCKER 3: revert `parse_keyspace_payload` to fall through to `StoreKey::Str(...)` → BLOCKER 3 regression FAILED as expected (Str leaks into captured set). - BLOCKER 5: gate the collision check on `if false` → BLOCKER 5 regression FAILED as expected (no Err returned from new_standard). All three mutations reverted post-verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --- Squash-in (2026-05-10 — 9-reviewer cadre fix-up follow-up): The original 7830d435 WIP commit by `parent <parent@local>` flipped `default_enable_keyspace_notifications: true → false`. Two reviewers (code-reviewer + DSR) caught that the flip boot-panics the production CAS chain `cas_INNER (ExistenceCacheStore) → SizePartitioning → FastSlow → REDIS_CAS_SMALL_STORE`: ECS::new_with_time `.expect()`s `register_item_callback` to succeed, FastSlow/SizePartitioning propagate the Err, and `RedisStore::register_item_callback` returns `Code::FailedPrecondition` whenever `enable_keyspace_notifications=false` — putting nativelink.service into a systemd restart loop. Production Valkey actually grants `+@all` (verified via `valkey-cli ACL LIST`), so the original `true` default is correct. User authorized Option 1 (revert the default flip). The bool flip is reverted; the genuinely-useful redis_store.rs additions are folded in here (where the eager-init they support already lives): - `subscriber_channel` and `keyspace_dispatcher_tx` get explicit `UNBOUNDED-OK` justifications per the CLAUDE.md unbounded-buffer rule (DSR M2 + perf-optimizer M1 + code-reviewer S1). - New `callbacks_registered_total: AtomicU64` counter, bumped on successful `register_item_callback`, exported via metrics. - Inert-dispatcher 60s startup `warn!` (red-team TraceMachina#3 from the cadre): if `enable_keyspace_notifications=true` but no callback registers within 60s, log a single warn so the operator sees the dispatcher firing into a void (e.g. AC-only stores not yet wrapped in ECS). Single-shot; uses `Arc::downgrade` so it doesn't keep the store alive. Docstring rewritten to keep `Default: true` while tightening operator guidance: hardened-cluster operators (no `+config` ACL) MUST set `enable_keyspace_notifications=false` AND remove any `ExistenceCacheStore` wrapping the `RedisStore`, or `ECS::new_with_time` panics → restart loop. Hardened-cluster ACL operators (no `+config` grant) handled separately: tracked as TraceMachina#379-pending — a config-validator that fails-fast at startup when `enable_keyspace_notifications=true` is wrapped by ECS but the runtime CONFIG GET fails would let operators see the misconfiguration in their dev-loop instead of in the systemd restart loop. See TraceMachina#378 for the follow-up minor-cleanup bundle from the same cadre (security MINOR-1/2: debug-log info-leak + cfg-gating `set_spec_defaults_for_test`; code-simplifier MINOR-1/2/3: duplicated default-line / `.clone()` / `raw_conn` extraction; rust-crate NIT-1/2: import ordering + safe indexing).
…dre BLOCKERs (testing-czar MAJORs) Two regression tests covering the BLOCK-1/2 mechanism the 2026-05-10 9-reviewer cadre identified — the failure mode that the WIP commit 7830d435 (`default_enable_keyspace_notifications: true → false`) would have shipped to production: 1. `enable_keyspace_notifications_defaults_to_true` (+ const sibling `default_enable_keyspace_notifications_const_returns_true`) in `nativelink-config/src/stores.rs` — deserializes a minimal `RedisSpec` JSON5, asserts `spec.enable_keyspace_notifications == true`. Bespoke `.expect()` message names the production CAS chain (`cas_INNER → SizePartitioning → FSS → REDIS_CAS_SMALL_STORE`) and the `.expect()` site at `existence_cache_store.rs:215-216` so a future reader can trace why the default matters. Mutation: flip `default_enable_keyspace_notifications()` to return `false` — both tests red-fail with the bespoke message. 2. `register_item_callback_returns_failed_precondition_when_disabled` in `nativelink-store/tests/redis_store_notification_test.rs` — over-action coverage on the disabled path. Constructs a real `RedisStore` against a spawned `valkey-server` with `enable_keyspace_notifications=false`, asserts construction succeeds AND `register_item_callback` returns `Err(Code::FailedPrecondition)`. The asymmetric-contract-coverage rule in CLAUDE.md calls this out explicitly: under-action (eviction event NOT firing the callback) is covered by the existing `evicted_key_fires_callback` test; over-action (callback registration silently succeeding when notifications are disabled, leaving the wrapping ECS to silently retain stale-positive entries forever) had no coverage before this commit. Mutation: change the `Err(make_err!(Code::FailedPrecondition, ...))` arm at `redis_store.rs:2741-2750` to `Ok(())` — test red-fails with bespoke message naming the discriminator role. Both tests are in the production composition seam they protect: test (1) goes through serde (the same path the operator's `.json5` config takes), and test (2) constructs a real `RedisStore` via `new_standard` against a real Valkey subprocess (gated by `--features redis-integration-tests`). See TraceMachina#378 for the follow-up minor-cleanup bundle from the same cadre. Hardened-cluster ACL operators (no `+config` grant) handled separately as TraceMachina#379-pending. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Added server readiness string listening to integration tests to reduce flakiness
Description
Listens for string from docker-compose logs to indicate that the servers are ready and listening before running integration tests.
Fixes #375
Type of change
How Has This Been Tested?
Ran all tests individually and together. TLS test was flaky when running with all tests before change and had no failed runs afterwards.
Checklist
bazel test //...passes locallygit amendsee some docsThis change is