Skip to content

Fixed flakiness in integration tests - #378

Merged
blakehatch merged 1 commit into
TraceMachina:mainfrom
blakehatch:integration-test-flakiness-fix
Nov 5, 2023
Merged

blakehatch merged 1 commit into
TraceMachina:mainfrom
blakehatch:integration-test-flakiness-fix

Conversation

@blakehatch

@blakehatch blakehatch commented Nov 4, 2023 •

Copy link
Copy Markdown
Contributor

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

  • Bug fix (non-breaking change which fixes an issue)

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

  • Updated documentation if needed
  • Tests added/amended
  • bazel test //... passes locally
  • PR is contained in a single commit, using git amend see some docs

This change is Reviewable

@aaronmondal aaronmondal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 blakehatch left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 aaronmondal left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 💃

:lgtm:

Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @blakehatch)

@blakehatch blakehatch left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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: :shipit: complete! all files reviewed, all discussions resolved (waiting on @blakehatch)

@blakehatch
blakehatch merged commit 22abf90 into TraceMachina:main Nov 5, 2023

@allada allada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 allada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_ready

run_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 MarcusSorealheis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

MarcusSorealheis pushed a commit to MarcusSorealheis/nativelink that referenced this pull request Nov 3, 2025
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…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).
rejuvenile added a commit to rejuvenile/nativelink that referenced this pull request Aug 19, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix Integration test flakiness by listening for connection string echo

4 participants