Modules: fix SharedDict.pop() for unexpired keys in timeout zones - #1102
Open
horus wants to merge 1 commit into
Open
Modules: fix SharedDict.pop() for unexpired keys in timeout zones#1102horus wants to merge 1 commit into
horus wants to merge 1 commit into
Conversation
Previously, SharedDict.pop() removed an entry from the expiration rbtree before checking whether it had expired. ngx_rbtree_delete() clears the removed node's key, so the subsequent expiration check treated every entry in a timeout zone as expired. pop() therefore deleted unexpired entries but returned undefined instead of their values. The fix preserves the expiration key before removing the node and uses the saved value for the check in both the njs and QuickJS implementations. Tests are added for popping an unexpired entry from a timeout zone.
|
✅ All required contributors have signed the F5 CLA for this PR. Thank you! |
Author
|
I have hereby read the F5 CLA and agree to its terms |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Proposed changes
Problem
SharedDict.pop()returnsundefinedfor unexpired entries in zones declared withtimeout=— the entry is deleted, but its value is lost. Both engines are affected (njs VM and QuickJS). Present in current master.Runtime probe against a
timeout=5mzone (from a production OIDC deployment where every login callback failed because session states live in a timeout zone):Root cause
ngx_js_dict_delete()(andngx_qjs_dict_delete()) removes the node from the expire rbtree before evaluating expiration:nginx core
ngx_rbtree_delete()poisons the removed node unconditionally —node->key = 0,left/right/parent = NULL(the/* DEBUG stuff */block is not behind anNGX_DEBUGguard). Sonode->expire.keyis already0when the comparison runs,now < node->expire.keyis always false, the helper returnsNGX_DECLINED, andpop()maps that toundefined.Scope: only
pop()— the other callers of the same helper passretval = NULL, so the bogus expire check never runs; and only zones withtimeout=— otherwise!dict->timeoutshort-circuits.get()/delete()/set()/incr()are unaffected.Fix
Snapshot
node->expire.keyinto a local variable before the rbtree removal, and compare against the snapshot — in bothngx_js_dict_delete()andngx_qjs_dict_delete(). nginx core's key zeroing is legitimate node poisoning; njs must not read the field after removal.Tests
nginx/t/js_shared_dict.tpreviously covered "pop expired key from a timeout zone" (zone=foo,timeout=2s) and "pop unexpired key from a no-timeout zone" (zone=bar) — but never "pop unexpired key from a timeout zone". Added the missing pair:Verification
Minimal reproduction (runs in one minute):
nginx.conf:probe.js:(Repeated curls print the same line: each request re-adds the key that the previous pop deleted.)
Verified on macOS/arm64 and Linux/amd64 (Debian 12, nginx 1.30.4). The new
pop unexpired foo.POPtest fails without the fix, while the fulljs_shared_dict.tsuite passes with it. The patched Linux module also restores the OIDC callback flow that originally exposed the issue.The QuickJS path is patched identically and verified at runtime with
js_engine qjson nginx 1.30.4 —pop()correctly returns unexpired values from atimeout=zone with both QuickJS flavors (quickjs-ng v0.9.0 and bellard quickjs-2026-06-04). Thejs_shared_dict.tsuite itself runs the default njs VM engine.Checklist
CONTRIBUTINGdocument.