Skip to content

test: fix false-positive corruption in reload-under-load harness#81

Merged
eilandert merged 1 commit into
masterfrom
fix/reload-test-dict-decode
Jul 15, 2026
Merged

test: fix false-positive corruption in reload-under-load harness#81
eilandert merged 1 commit into
masterfrom
fix/reload-test-dict-decode

Conversation

@eilandert

@eilandert eilandert commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Issue #80 reported "data corruption" under SIGHUP reload with large (CSTREAM_IN+1-sized) bodies. Root cause turned out to be the test harness, not the filter module: nginx.conf sets zstd_dict_file, so the filter compresses every response against that dictionary via ZSTD_CCtx_refCDict. The test's decode step never passed -D <dict> to zstd -d, so libzstd correctly rejected the dictionary-compressed frame as corrupted.

  • Fixed one() in tools/test_reload_under_load.py to decode with -D dict_path.
  • Verified 3x clean: 593-625 requests per run, 6 SIGHUP reloads, 4-way load, zero decode failures.
  • No changes to filter/ngx_http_zstd_filter_module.c — the byte-accounting bug I originally suspected does not exist.

Closes #80.

Test plan

  • python3 tools/test_reload_under_load.py --nginx-binary <debug build> --reloads 6 --workers 4 --duration 18 — 3 consecutive clean runs

Summary by CodeRabbit

  • Tests
    • Improved compression validation during server reloads.
    • Responses compressed with a configured Zstandard dictionary are now decoded and compared accurately, including responses spanning reloads.
    • This provides more reliable detection of data integrity issues under sustained load.

Filter compresses with a referenced dictionary (zstd_dict_file /
ZSTD_CCtx_refCDict) but the test's decode step never passed -D, so
libzstd correctly rejected every response as corrupted. Not a real
bug in the filter module — verified 3x clean after fix (593-625 reqs
per run, 6 SIGHUP reloads, 4-way load).
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The reload-under-load test now passes nginx’s Zstandard dictionary to its response decoder, ensuring compressed responses are decoded with the same dictionary across reload timing scenarios.

Changes

Reload verification

Layer / File(s) Summary
Dictionary-aware response decoding
tools/test_reload_under_load.py
one() accepts dict_path, passes it to zstd -dq with -D, and the worker thread supplies the generated dictionary path.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: fixing the reload-under-load harness to avoid false corruption reports.
Linked Issues check ✅ Passed The harness now decodes with the configured Zstd dictionary, matching the issue’s root cause analysis and resolving the reported false-positive corruption.
Out of Scope Changes check ✅ Passed The changes stay within the issue scope and only adjust the reload-under-load test harness decode path.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reload-test-dict-decode

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tools/test_reload_under_load.py`:
- Around line 144-151: Update the subprocess.run invocation that decodes the
response blob to explicitly pass check=False, preserving the existing manual
handling of non-zero decoder exits and satisfying Ruff PLW1510.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 073dd222-ee73-4331-9291-174eb05ad355

📥 Commits

Reviewing files that changed from the base of the PR and between 9ef5d65 and 706d3c3.

📒 Files selected for processing (1)
  • tools/test_reload_under_load.py

Comment on lines +144 to +151
# nginx.conf below sets zstd_dict_file, so every response is compressed
# against that dictionary (ZSTD_CCtx_refCDict in the filter module) --
# the decoder needs the same dictionary via -D, or libzstd correctly
# rejects the frame as "Data corruption detected" even though nothing is
# actually corrupt. Omitting -D here previously made this test fail on
# every run regardless of reload timing (any response, any size).
r = subprocess.run([zstd_bin, "-dq", "-c", "-D", str(dict_path)],
input=blob, capture_output=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
ruff check --select PLW1510 tools/test_reload_under_load.py

Repository: myguard-labs/nginx-zstd-module

Length of output: 861


Add check=False to this subprocess.run call. The non-zero decoder exit is handled manually, and the explicit flag silences Ruff PLW1510.

🧰 Tools
🪛 ast-grep (0.44.1)

[error] 149-150: Command coming from incoming request
Context: subprocess.run([zstd_bin, "-dq", "-c", "-D", str(dict_path)],
input=blob, capture_output=True)
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(subprocess-from-request)

🪛 Ruff (0.15.21)

[error] 150-150: subprocess call: check for execution of untrusted input

(S603)


[warning] 150-150: subprocess.run without explicit check argument

Add explicit check=False

(PLW1510)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/test_reload_under_load.py` around lines 144 - 151, Update the
subprocess.run invocation that decodes the response blob to explicitly pass
check=False, preserving the existing manual handling of non-zero decoder exits
and satisfying Ruff PLW1510.

Source: Linters/SAST tools

@eilandert
eilandert merged commit e289021 into master Jul 15, 2026
13 checks passed
@eilandert
eilandert deleted the fix/reload-test-dict-decode branch July 15, 2026 01:50
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.

Data corruption in streaming compress under SIGHUP reload (CSTREAM_IN+1-sized bodies)

1 participant