drain the coarse gate on async errors + ignore stale async replies - #59
Merged
Conversation
This was referenced Jul 26, 2026
next-nf
force-pushed
the
async-ue-bearer-22-reject
branch
from
July 27, 2026 11:44
53e3c87 to
59fecdd
Compare
next-nf
force-pushed
the
async-gate-51-m3
branch
from
July 27, 2026 11:44
0e92e90 to
033dae2
Compare
next-nf
force-pushed
the
async-ue-bearer-22-reject
branch
from
July 27, 2026 12:16
59fecdd to
0dac27c
Compare
next-nf
force-pushed
the
async-gate-51-m3
branch
from
July 27, 2026 12:16
033dae2 to
4fb8c8e
Compare
next-nf
force-pushed
the
async-ue-bearer-22-reject
branch
from
July 27, 2026 12:23
0dac27c to
54053d9
Compare
next-nf
force-pushed
the
async-gate-51-m3
branch
from
July 27, 2026 12:23
4fb8c8e to
8657782
Compare
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.
Closes #51. Also resolves the step-2 design-review item M3.
The coarse gate postpones procedure-initiating events while
async_pendingisnon-empty and relies on the drain being a state-term change — that is what makes
gen_statemre-deliver postponed events. Success callbacks honour this(
update_credits_ok/3,br_ok/5return{next_state, DrainedState, …}). The errorpath did not, in two places.
1.
handle_ctx_error/4discarded its State. The head bound_Stateand therecoverable branches returned
{keep_state, Data}— keeping the state term currentbefore the event, which still holds the stale registry entry.
update_credits_err/3passed the correctly drained State and it was thrown away.
2.
async_dispatch/4's catch ran on a pre-drain State — the worse leak, and notmentioned in the issue.
async_m:handle_reply/4doesmaps:take(ReqId, Pending)andthreads the drained
S1into the resumed computation; if a resumed step throws#ctx_err{}, thatS1dies with the throw and the catch clause gets the State capturedat dispatch — holding a ReqId whose reply has already been consumed and can never
arrive again.
pgw_s5s8:br_err/9'sthrow(E)re-throw lands exactly there. Fixingonly (1) would have left this in place.
Fix
handle_ctx_error/4binds its State and returns{next_state, State, Data}from therecoverable branches. A no-op for synchronous callers:
gen_statemretries postponedevents only when the state term actually changes, and both sync call sites pass their
clause's
State— the state at event entry, which is exactly whatkeep_statepreserved. FATAL branches unchanged.
async_dispatch/4pre-computes the drained State for its catch clause. Correct forevery throw: a procedure that re-parks returns from
async_m:dispatch/3and neverreaches the catch, so no ReqId registered during that invocation can be lost.
$async_replynaming a non-pending ReqId, mirroring the{'$async_down', _}clause below it.Reachability. Not reachable today, verified rather than assumed:
smf_corecontainsexactly one
?CTX_ERR(?WARNING, …)(gtp_context.erl:942, mandatory-IE validation, whichruns before any procedure starts). Every post-await failure is FATAL, and FATAL stops the
process, taking its postponed mailbox with it. This is a landmine fix — any future async
procedure whose error callback carries a recoverable
#ctx_err{}would have silentlywedged that session's gate.
Testing.
async_error_drains_gateinjects a recoverable post-await error with noproduction test surface:
await_modify/1liftssmf_pfcp_context:modify_session_result/2into
async_m's error channel, andsmf_pfcp_contextis already mecked withpassthroughby
smf_test_lib:meck_init/1. The UPF is held so the park is observed (async_pending= 1),then released; the case asserts the registry drains to 0 and that
delete_session— a gatedprocedure-initiating event — still completes.
Verified failing first for the right reason: before the fix it fails on the
=:= 0waitwith
{badmatch, {error, timeout}}, having passed the=:= 1park assertion. After thefix it passes.
M3 is deliberately untested:
pgw_s5s8,ggsn_gnandsaegw_s11each have anhandle_event(info, _Info, …)catch-all, so such a reply is already dropped today and atest would pass before and after. The value is making the ignore a property of the driver
rather than an accident of each interface.
pgw_SUITE135/135,ggsn_SUITE+saegw_s11_SUITEalongside it 235 total, 0 failures —those three cover
handle_ctx_error/4's recoverable branch heavily via ordinary protocolerror replies.
Known adjacent item, deliberately out of scope:
update_credits_err/3has only a#ctx_err{}clause, so a non-#ctx_errerror reaching it is afunction_clausecrash. Adifferent failure mode, unreachable today (the PFCP transport posts no monitored-worker DOWN).
Stacked on #58. Merge bottom-up: #45 → #46 → #48 → #56 → #57 → #58 → this.