Skip to content

Fix request lookup when frame locals mutate - #120

Open
duclucky wants to merge 1 commit into
bobbui:masterfrom
duclucky:fix/frame-locals-iteration
Open

Fix request lookup when frame locals mutate#120
duclucky wants to merge 1 commit into
bobbui:masterfrom
duclucky:fix/frame-locals-iteration

Conversation

@duclucky

@duclucky duclucky commented Aug 12, 2026

Copy link
Copy Markdown

Summary

  • snapshot frame-local items before scanning so trace/debug hooks cannot resize the live dictionary during iteration
  • reuse each snapshotted value during request type checks
  • add a FastAPI regression test that mutates frame.f_locals from a trace hook after iteration starts

Fixes #89

Testing

  • python -m pytest --ignore tests/smoketests -q (24 passed)
  • backend=fastapi python -m pytest tests/smoketests/test_run_smoketest.py -q (1 passed)
  • python -m flake8 json_logging tests --count --select=E9,F63,F7,F82 --show-source --statistics
  • python -m build
  • python -m pip check

Summary by CodeRabbit

  • Bug Fixes

    • Improved request inspection reliability when execution context variables change during processing.
    • Prevented errors caused by modifications to local variables while examining request data.
  • Tests

    • Added regression coverage for request inspection during runtime tracing and local-variable changes.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d4f04b8d-e51d-4b9e-9687-75343d112f5a

📥 Commits

Reviewing files that changed from the base of the PR and between ac68fce and 71fe7d6.

📒 Files selected for processing (2)
  • json_logging/util.py
  • tests/test_fastapi.py

📝 Walkthrough

Walkthrough

The request call-stack inspection now iterates over a snapshot of frame locals. A FastAPI regression test verifies safe behavior when tracing mutates those locals.

Changes

Request stack safety

Layer / File(s) Summary
Snapshot frame locals and validate mutation handling
json_logging/util.py, tests/test_fastapi.py
Request discovery iterates over a tuple snapshot of local-variable items. The regression test installs a tracer, mutates frame locals, verifies a None result, restores the previous tracer, and confirms the mutation occurred. The pattern-matching helper remains unchanged.

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

Mergeability Score: ⚪ Minimal · up to 71fe7

This localized change prevents request lookup failures when frame locals are modified during tracing, with regression coverage and reported checks passing; no actionable merge-blocking risk remains.

🚥 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 clearly and concisely describes the fix for request lookup failures caused by frame-local mutations.
Linked Issues check ✅ Passed The code snapshots frame locals and adds a regression test that directly addresses issue #89.
Out of Scope Changes check ✅ Passed All changes are limited to the request lookup fix and its FastAPI regression test.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Snapshot frame locals during request lookup to avoid mutation errors

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Snapshot frame.f_locals.items() before scanning to avoid dict-resize during iteration.
• Reuse snapshotted values during request-type checks for consistent inspection.
• Add FastAPI regression test that mutates frame.f_locals via sys.settrace().
Diagram

graph TD
  A["RequestUtil.get_request_from_call_stack()"] --> B{"Iterate frame locals"} --> C["Snapshot: tuple(f_locals.items())"] --> D["Type-check values"] --> E["Return request or None"]
  T["Trace hook (sys.settrace)"] --> F["Mutates frame.f_locals"]
  F --> C
  subgraph Legend
    direction LR
    _fn["Function"] ~~~ _dec{"Decision"} ~~~ _test["Test/Hook"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Iterate over list(f_locals) and re-index values
  • ➕ Avoids 'dict changed size' by snapshotting keys only
  • ➕ Slightly less memory than snapshotting (key, value) pairs
  • ➖ Still reads values from the live dict, so values could change between key snapshot and lookup
  • ➖ Extra dict lookup per key
2. Copy locals dict via f_locals.copy()
  • ➕ Clear intent: stable snapshot of locals
  • ➕ Provides both keys and values consistently
  • ➖ Potentially higher overhead than tuple(items()) (creates a full dict)
  • ➖ May differ in subtle ways if code relies on dict identity/ordering (unlikely here)

Recommendation: Keep the current approach (tuple(f_locals.items())) because it provides a stable, minimal snapshot (including values) and avoids additional live-dict reads during type checks. The added regression test using sys.settrace() is a strong guard against reintroducing the mutation-during-iteration failure mode.

Files changed (2) +39 / -4

Bug fix (1) +7 / -4
util.pySnapshot frame locals items to prevent mutation during stack inspection +7/-4

Snapshot frame locals items to prevent mutation during stack inspection

• Changes frame-local scanning to iterate over 'tuple(f_locals.items())' and reuse the snapshotted value when checking request types, preventing failures when tracing/debug hooks mutate 'f_locals' mid-iteration. Also normalizes the file to include a trailing newline.

json_logging/util.py

Tests (1) +32 / -0
test_fastapi.pyAdd regression test for frame locals mutation during request lookup +32/-0

Add regression test for frame locals mutation during request lookup

• Adds a FastAPI-focused regression test that installs a trace function via 'sys.settrace()' and mutates 'frame.f_locals' after iteration begins inside 'get_request_from_call_stack()'. Verifies the lookup completes without error and that the mutation path was exercised.

tests/test_fastapi.py

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Extra locals snapshot allocations 🐞 Bug ➹ Performance
Description
get_request_from_call_stack() now allocates tuple(f_locals.items()) for every inspected frame,
creating an O(n) tuple of 2-tuples per frame walked. This increases CPU/memory overhead for
correlation-id lookup during log formatting in FastAPI, where the library falls back to stack
scanning (no global request object).
Code

json_logging/util.py[190]

+            for key, value in tuple(f_locals.items()):
Evidence
The PR introduces a per-frame tuple(f_locals.items()) allocation during stack scanning. This scan
is triggered during log formatting when correlation-id is missing, and in FastAPI the library
explicitly cannot rely on a global request object, making stack scanning more common.

json_logging/util.py[180-200]
json_logging/formatters.py[141-154]
json_logging/util.py[124-152]
json_logging/framework/fastapi/implementation.py[61-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`RequestUtil.get_request_from_call_stack()` snapshots `tuple(f_locals.items())`, which allocates a tuple plus a 2-tuple per local entry for every frame visited.

## Issue Context
This codepath is exercised from `JSONLogWebFormatter` via `request_util.get_correlation_id(within_formatter=True)`. For FastAPI, `support_global_request_object()` is `False`, so correlation-id lookup may frequently fall back to scanning the call stack.

## Fix Focus Areas
- json_logging/util.py[180-200]

### Suggested approach
Preserve mutation-safety but reduce allocations by snapshotting only values:
- Keep the existing fast-path checks for `request` and `req`.
- Replace `for key, value in tuple(f_locals.items()): ...` with `for value in tuple(f_locals.values()): ...` (and drop the key filter), since you only need to detect any local whose value is an instance of `class_type`.
- Ensure the regression test still passes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can enable the Remediation agent and Qodo fixes findings in a dedicated fix PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread json_logging/util.py
for key in f_locals:
if key not in {'request', 'req'} and isinstance(f_locals[key], class_type):
return f_locals[key]
for key, value in tuple(f_locals.items()):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Extra locals snapshot allocations 🐞 Bug ➹ Performance

get_request_from_call_stack() now allocates tuple(f_locals.items()) for every inspected frame,
creating an O(n) tuple of 2-tuples per frame walked. This increases CPU/memory overhead for
correlation-id lookup during log formatting in FastAPI, where the library falls back to stack
scanning (no global request object).
Agent Prompt
## Issue description
`RequestUtil.get_request_from_call_stack()` snapshots `tuple(f_locals.items())`, which allocates a tuple plus a 2-tuple per local entry for every frame visited.

## Issue Context
This codepath is exercised from `JSONLogWebFormatter` via `request_util.get_correlation_id(within_formatter=True)`. For FastAPI, `support_global_request_object()` is `False`, so correlation-id lookup may frequently fall back to scanning the call stack.

## Fix Focus Areas
- json_logging/util.py[180-200]

### Suggested approach
Preserve mutation-safety but reduce allocations by snapshotting only values:
- Keep the existing fast-path checks for `request` and `req`.
- Replace `for key, value in tuple(f_locals.items()): ...` with `for value in tuple(f_locals.values()): ...` (and drop the key filter), since you only need to detect any local whose value is an instance of `class_type`.
- Ensure the regression test still passes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@llamapreview llamapreview 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.

LlamaPReview — No blocking issues found

This PR safely fixes the RuntimeError by snapshotting frame locals before scanning, with a regression test that fails on the old code and no PR-caused correctness or security regressions found.

1 non-blocking finding retained — highest: Optional: per-frame tuple allocation in logging hot path.

Review details and evidence
Priority File Finding Evidence
P2 json_logging/util.py Optional: per-frame tuple allocation in logging hot path needs verification

Finding details

P2 · Optional: per-frame tuple allocation in logging hot path

json_logging/util.py

The fix snapshots frame locals with tuple(f_locals.items()), adding a new O(n) allocation per scanned frame. The old loop already did O(n) work per frame, and typical frame-local counts are small; there is no evidence this is a material regression. Profiling of high-volume logging would determine if a follow-up optimization is warranted; this is not a merge blocker.

Owner action: Optionally profile high-volume logging to assess the tuple allocation cost; only optimize if a regression is measured.

Verification boundary: needs verification; scope: changed region.

Material unknowns

  • Exact-head CI evidence for pytest/flake8/build is not independently present; only CodeRabbit status is available. Would confirm the change passes full CI rather than only statically analyzed; does not change the clear verdict.
    • Check: Confirm CI run results on the final commit before merge for completeness.

LlamaPReview checks

  • Read the complete PR-head file json_logging/util.py.
  • Read the complete PR-head file tests/test_fastapi.py.

LlamaPReview reviewed this pull request at its exact head commit. Inspect the source or share feedback.

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.

RuntimeError: dictionary changed size during iteration

1 participant