Bypass f-string tag round-trip for var operation operands#6798
Conversation
Interpolating a var into an f-string hashes it, permanently registers it in the module-global _global_vars dict (which never evicts - a memory leak, worst under dev hot-reload), and emits a tag that the return expression's __post_init__ regex-decodes back out. For operands of a @var_operation this round-trip is pure overhead (~30-40% of op construction): the operation merges operand VarData directly from _args. var_operation now suppresses tagging on its operands (ref-counted, so nested operations on the same var can't clear an outer suppression early) while the body runs. Vars created inside the body still tag normally, so their VarData keeps flowing through the return expression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Merging this PR will not alter performance
Comparing Footnotes
|
Greptile SummaryThis PR optimizes
Confidence Score: 3/5Safe in all normal usage paths; there is an error-path correctness issue where the increment loop running outside the try block can permanently leave _format_without_tagging set on operands when iteration is interrupted mid-way. The optimization is logically correct and well-tested for the happy path. The concern is that the for-loop incrementing _format_without_tagging runs before the try block — if it raises mid-iteration (e.g., LiteralVar.create returns None for a range argument, placing None in operands, causing AttributeError on None.dict), any already-incremented vars have the flag permanently stuck, silently disabling tagging for those vars in all future uses. packages/reflex-base/src/reflex_base/vars/base.py — specifically the operand increment loop in the var_operation wrapper and the try/finally structure around it. Important Files Changed
Reviews (1): Last reviewed commit: "Appease pyright on operand __dict__ tag ..." | Re-trigger Greptile |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f75b20c48
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if self.__dict__.get("_format_without_tagging"): | ||
| return str(self) |
There was a problem hiding this comment.
Keep format suppression local to the operation context
When the same Var is shared across threads, this instance-dict flag changes __format__ process-wide while an operation body is running. A concurrent f"{var}" therefore returns the raw expression without its tag, so a Var constructed from that string loses the original imports/hooks; overlapping operations can also race the non-atomic reference-count updates and leave the flag set or raise during cleanup. Use thread/task-local suppression rather than mutating the shared immutable operand.
Useful? React with 👍 / 👎.
| for operand in operands: | ||
| operand_dict = operand.__dict__ | ||
| operand_dict["_format_without_tagging"] = ( # pyright: ignore[reportIndexIssue] | ||
| operand_dict.get("_format_without_tagging", 0) + 1 | ||
| ) | ||
| try: | ||
| return_var = func(*args_vars.values(), **kwargs_vars) # pyright: ignore [reportCallIssue] | ||
| finally: | ||
| for operand in operands: | ||
| operand_dict = operand.__dict__ | ||
| remaining = operand_dict["_format_without_tagging"] - 1 | ||
| if remaining: | ||
| operand_dict["_format_without_tagging"] = remaining # pyright: ignore[reportIndexIssue] | ||
| else: | ||
| del operand_dict["_format_without_tagging"] # pyright: ignore[reportIndexIssue] |
There was a problem hiding this comment.
Increment loop outside try-finally can permanently poison operands
The increment loop runs before the try block, so if it raises mid-iteration (e.g., an operand whose __dict__ is inaccessible), any operands already incremented will have _format_without_tagging stuck at a non-zero value permanently — causing them to silently bypass tagging in all future uses. This can happen today when LiteralVar.create(range_value) returns None (see LiteralVar.create line ~1802), placing None in operands; the first valid-var operand gets incremented, then None.__dict__ raises before the try, so the finally never runs. Moving the increment loop inside try (and using .get(..., 0) in the decrement to tolerate partial increments) would guarantee cleanup regardless of which step fails.
All Submissions:
Type of change
Changes To Core Features:
What this does
Interpolating a
Varinto an f-string (Var.__format__) hashes the var, permanently registers it in the module-global_global_varsdict (which never evicts — a real memory leak, worst under dev hot-reload since entries and theirGLOBAL_CACHEvalues survive GC), and emits a<reflex-var>tag that the constructed expression's__post_init__then regex-decodes back out. Profiling showed this round-trip is ~30–40% of var-operation construction — and every@var_operation(all arithmetic, comparisons, string/array/object ops) pays it for each operand.Fix
Operand
VarDataalready reaches the operation throughCustomVarOperation._args, so the tag round-trip is pure overhead for operands.var_operationnow suppresses tagging on its operand vars while the body runs:finally;VarDatakeeps flowing through the return expression (covered by a dedicated test);Net effect: constructing
a + bno longer hashes/registers/regex-decodes its operands, and internal ops no longer grow_global_vars.Verification
_global_varsdoes not grow when constructing an operation while operandVarDatastill reaches the merged result; the suppression flag does not persist after the op; body-created vars keep contributingVarData; formatting outside an operation still registers/tags as before.test_console_log,test_evaluate_page*, and alltest_compile_*benchmarks.Part of a compiler-performance series; tracked in Linear as ENG-10104.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Jy8uHH11KircGa2MbTrR8g
Generated by Claude Code