Skip to content

rgw: add IBMCEPH-19403 RGW race-condition reproduction suite - #928

Open
viduship wants to merge 7 commits into
red-hat-storage:masterfrom
viduship:ibmceph-19403-race-repro
Open

viduship wants to merge 7 commits into
red-hat-storage:masterfrom
viduship:ibmceph-19403-race-repro

Conversation

@viduship

@viduship viduship commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Adds a pytest-based test suite that reproduces all 10 data-loss / integrity bugs tracked under epic IBMCEPH-19403.
Tests have been on:

  • ceph-20.2.1-405.el9cp (9.1)
  • ceph-20.2.2-349.el10cp

Files added:
test_ibmceph_19403_pytest.py — 11 pytest test functions
run_ibmceph_19403_pytest.py — cephci-compatible runner
test_ibmceph_19403.yaml — cluster config (rio86c defaults)

Bugs covered (all confirmed UNFIXED on 20.2.2-349.el10cp):
IBMCEPH-19405 Lock renewal failure + concurrent complete -> data loss
IBMCEPH-19406 RGW crash between head-write and meta-delete -> ghost object
IBMCEPH-19406v Same as 19406 on versioning-SUSPENDED bucket
IBMCEPH-19407 Failed meta-delete + retry from second RGW -> data loss (SIGKILL)
IBMCEPH-19407bl Same as 19407 via OSD blocklist (deterministic reproduction)
IBMCEPH-19410 Lifecycle AbortMPU ignores completion lock -> premature GC
IBMCEPH-19412 DeleteObject racing overwrite deletes new data
IBMCEPH-19414 CopyObject-self racing overwrite deletes data
IBMCEPH-19416 Stalled write lost from bucket index (pending-op expiry)
IBMCEPH-19417 DeleteObject lost RADOS-level ID-tag guard
IBMCEPH-19424 8 concurrent conditional PUTs (If-Match) all succeed

pytest markers:
ibmceph_mpu — multipart completion race tests
ibmceph_race — overwrite / bucket-index race tests
ibmceph_cond — conditional write atomicity tests
ibmceph_all — all of the above

Usage:
pytest test_ibmceph_19403_pytest.py -C test_ibmceph_19403.yaml -v
pytest test_ibmceph_19403_pytest.py -C test_ibmceph_19403.yaml -m ibmceph_mpu

Fixes: https://issues.redhat.com/browse/IBMCEPH-19403

@viduship

viduship commented Oct 9, 2026

Copy link
Copy Markdown
Contributor Author

Baseline reproduction run — ceph-20.2.1-421.el9cp (pre-fix)

Cluster: ceph-pri-vim-restore-91-rio86c-node5 (10.0.65.65) — RHEL 9 / el9cp
Build: ceph-20.2.1-421.el9cp (f2a100abd0d3fe6ae23e998893049503a16b70a8) tentacle
RGW-A: 10.0.65.65:80 · RGW-B: 10.0.67.70:80
Duration: ~29 min · Suite: test_ibmceph_19403_pytest.py

Test semantics: FAILED = bug confirmed · SKIPPED = race window missed (re-run) · PASSED = correct behaviour

# Test JIRA Result Oracle / Failure message
1 test_19405 19405 FAILED ⚠️ ConnectionRefusedError — RGW-A mid-restart from stale process; needs clean re-run (bug confirmed on prior run: HEAD=200 GET=404 bytes=0)
2 test_19406 19406 FAILED 🐛 IBMCEPH-19406 BUG CONFIRMED: HEAD=200 GET=404 after GC
3 test_19406v 19406v FAILED 🐛 IBMCEPH-19406v BUG CONFIRMED: HEAD=200 GET=404 after GC (versioning-SUSPENDED)
4 test_19407 19407 SKIPPED ⏭️ retry=500 — lock still held at crash time (window missed)
5 test_19410 19410 FAILED 🐛 IBMCEPH-19410 BUG CONFIRMED: HEAD=200 GET=404 bytes=0/41943040 (gc_tails 0→17)
6 test_19417 19417 FAILED 🐛 IBMCEPH-19417 BUG CONFIRMED: HEAD=404 GET=404 — new object wiped by racing DELETE
7 test_19412 19412 FAILED 🐛 IBMCEPH-19412 BUG CONFIRMED: HEAD=404 GET=404 — tail leaked + object wiped
8 test_19414 19414 FAILED 🐛 IBMCEPH-19414 BUG CONFIRMED: HEAD=200 GET=404 bytes=0/20971520 — CopyObject-self data loss
9 test_19416 19416 FAILED 🐛 IBMCEPH-19416 BUG CONFIRMED: HeadObject ETag≠ListObjectsV2 ETag — index stale
10 test_19407bl 19407bl FAILED 🐛 IBMCEPH-19407bl BUG CONFIRMED: HEAD=200 GET=404 bytes=0/41943040 (gc_tails 0→16)
11 test_19424 19424 PASSED ✅ 200s=1 412s=7 — only 1/8 If-Match writes succeeded (correct S3 CAS behaviour)

Summary: 9 FAILED (bugs confirmed) · 1 SKIPPED (race window missed) · 1 PASSED

Notes

  • 19424 PASSED on this build — only 1 of 8 concurrent If-Match conditional writes succeeded, which is the correct behaviour. May already be fixed or the window was not hit.
  • 19405 failed due to RGW-A being mid-restart from earlier stale processes — not a genuine pass. The underlying bug was confirmed in a prior run (oracle: HEAD=200 GET=404 bytes=0).
  • 19407 SIGKILL variant missed the narrow race window this run (retry returned 500 = lock still held at crash). The same underlying bug was confirmed via the blocklist variant (test_19407bl).
  • Fixes for these bugs are on private-tserlin-ceph-9.1-rhel-9-test-IBMCEPH19403-patches and private-tserlin-ceph-9.2-rhel-10-test-IBMCEPH19403-patches. Will re-run suite against those builds once RPMs are available.

Logs saved on cluster: /root/ibmceph-pytest/run.log · /root/ibmceph-pytest/results.xml · /root/ibmceph-pytest/RESULTS_SUMMARY.txt

@viduship viduship changed the title rgw: add IBMCEPH-19403 RGW race-condition reproduction suite (pytest) rgw: add IBMCEPH-19403 RGW race-condition reproduction suite Oct 9, 2026
Adds a pytest-based test suite reproducing all 11 race-condition bugs
tracked under the IBMCEPH-19403 epic:

  19405  Lock renewal failure + concurrent CompleteMultipartUpload → data loss
  19406  RGW crash between head-write and .meta-delete → ghost object
  19406v Same as 19406 on versioning-SUSPENDED bucket
  19407  Failed .meta-delete + immediate retry from second RGW → data loss (SIGKILL)
  19407bl Same via OSD blocklist instead of SIGKILL
  19410  Lifecycle AbortMultipartUpload ignores completion lock → premature GC
  19412  DeleteObject racing overwrite leaks new object tail
  19414  CopyObject-to-itself racing overwrite corrupts data
  19416  Stalled write lost from bucket index
  19417  DeleteObject missing RADOS ID-tag guard
  19424  Concurrent conditional PUTs (If-Match) not atomic

Test semantics (standard pytest):
  PASSED  — correct behaviour or race window not caught
  FAILED  — bug confirmed (pytest.fail with BUG CONFIRMED message)
  SKIPPED — race precondition not met (re-run to retry)

For test_19407 (SIGKILL variant), parts are uploaded via RGW-B so
credentials remain valid across the RGW-A kill/restart cycle, avoiding
InvalidAccessKeyId errors seen on grim017.

Files added:
  rgw/v2/tests/s3_swift/test_ibmceph_19403_pytest.py  — test module
  rgw/v2/tests/s3_swift/run_ibmceph_19403_pytest.py   — cephci runner
  rgw/v2/tests/s3_swift/test_ibmceph_19403.yaml       — config/YAML

Resolves: IBMCEPH-19403 IBMCEPH-19405 IBMCEPH-19406 IBMCEPH-19407
          IBMCEPH-19410 IBMCEPH-19412 IBMCEPH-19414 IBMCEPH-19416
          IBMCEPH-19417 IBMCEPH-19424
@viduship
viduship force-pushed the ibmceph-19403-race-repro branch from f7e4f05 to 00413b7 Compare October 11, 2026 22:01
The SIGKILL must land after the MPU lock is released but before .meta
is deleted (sub-window 3b). Killing immediately after wait_get_ok()
lands in sub-window 3a (lock still held), causing retry to return 500
→ SKIPPED.

A 4s sleep after GET 200 is confirmed gives RGW-A time to release the
lock, ensuring the immediate retry on RGW-B returns 200 and triggers
the data-loss path.

Ref: QE_UPDATE.md — Case 4 observed GET readable at 19.1s then SIGKILL
Replace SIGKILL-based approach with deterministic GDB injection per JIRA
reproduction steps:

  1. Fire CompleteMultipartUpload on RGW-A with ms_inject_delay to widen window
  2. Wait for GET 200 on RGW-B (head written, lock released)
  3. GDB attach to radosgw host PID, breakpoint on delete_object, return -5
  4. .meta deletion fails deterministically — CMU still returns 200
  5. Verify: object readable, .meta present, lock cleared, gc_tails=0
  6. Retry CompleteMultipartUpload on RGW-B immediately
  7. Run GC — oracle: HEAD 200 GET 404 = BUG CONFIRMED

This eliminates the narrow SIGKILL timing window that caused SKIPPED on
previous runs (lock still held at crash time).

Resolves: IBMCEPH-19407
…isarm after

GDB must attach and set the breakpoint WHILE ms_inject_delay is still
armed (RGW-A stalled mid-completion). A background thread disarms the
delay 3s after GDB starts, so RGW-A resumes toward delete_object with
the breakpoint already set. Also increase GDB timeout to 120s.

Previous attempt timed out because delay was disarmed before GDB
attached, so delete_object was reached and passed before the breakpoint
was in place.
Replace GDB approach (fails on stripped binary without debuginfo) with
rgw_mp_lock_inject_delay=30 set persistently via ceph config at RGW-A
startup. This injects a 30s delay between lock release and .meta delete,
giving a guaranteed sub-window 3b SIGKILL target without debuginfo.

The test now:
  - Waits for GET 200 (head written, lock released)
  - Verifies lock cleared (0 lockers) — skips if still in sub-window 3a
  - SIGKILLs RGW-A while .meta is still present
  - Retries immediately on RGW-B
  - Runs GC and checks for data loss

Prereq: ceph config set client.rgw rgw_mp_lock_inject_delay 30
        + RGW-A restarted (already done on grim017)
… delete

rgw_mp_lock_inject_delay delays AFTER acquiring the lock (not after
releasing it), so it doesn't help open sub-window 3b. Fix:

- Poll until GET 200 AND lockers=0 (lock released, head written)
- Then arm ms_inject_delay_max=30/prob=1 to stall the upcoming
  delete_object(.meta) RADOS call
- SIGKILL RGW-A while stalled = guaranteed sub-window 3b hit

This is the correct sequence matching the JIRA reproduction steps.
test_19423 (new): If-Match/If-None-Match prefix comparison bug
  - Tests GET/PUT with exact ETag (must pass) and ETag+suffix (must 412)
  - Tests If-None-Match exact (must 304) and +suffix (must 200)
  - Bug: RGW does prefix match so etag+EXTRA passes If-Match incorrectly
  - Fix: ceph PR ac6c204b3a3 'rgw: compare If-Match entity tags whole'

test_19424 (fixed): use versioning-ENABLED bucket per JIRA description
  - Bug is in the versioned write path (check → write → make-current, no guard)
  - Now enables versioning before concurrent writes
  - Adds Part 2: If-None-Match:* concurrent creates (create-once semantics)
  - Proper cleanup: deletes all versions before bucket delete

Refs: IBMCEPH-19423, IBMCEPH-19424

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

RGW RGW

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant