Repository navigation
Conversation
TS 24.501 clause 6.4.1.7 c): "If the SMF receives a PDU SESSION ESTABLISHMENT REQUEST message with a PDU session ID identical to the PDU session ID of an existing PDU session and with request type set to "initial request", the SMF shall locally release the existing PDU session and proceed with the PDU session establishment procedure." The SMF does that for a session it holds in memory: the create finds it through ResolveRef and HandlePduSessionContextReplacement releases it. With enableDBStore, a restart leaves the session in the datastore only, ResolveRef misses it, and the new session is created beside it. Every restart followed by the subscriber re-attaching adds one more stored session, which is never released; one subscriber accumulated eighteen on a test cluster. A create whose PDU session ID resolves to no session in memory now lists the sessions stored for the subscriber and PDU session ID and releases every one: documents removed, PFCP sessions deleted at the user planes that acknowledged them, without waiting for the answers. The listing is backed by a non-unique index on the two fields: there are several until the older ones are released, and one whose delete failed stays stored beside its successor. A stored session a lookup has already read into the pool is released as that instance. One the release reads itself is put in the pool first, unless a lookup has put one there meanwhile, which is then the one released: the release removes the pool's entry, and must not remove an instance it did not release. A lookup that reads a stored session puts it in the pool the same way. Releasing a session read back from the datastore differs from releasing one this process created, and the release path now knows which it has: a context is marked as read back where its document is decoded, so none reaches a caller unmarked. - It was never counted in the session gauge, so releasing it no longer decrements the gauge, which then undercounted the live sessions by one, or wrapped when none was counted. - Its data path can name a user plane this SMF holds no association with; that node is skipped instead of dereferenced. Its UE address was allocated by an earlier start of the SMF, and the pool in use may have given it to another UE since. The allocator the context carries back has no pool, so releasing it returns nothing to the pool in use; omec-project#712 made that a no-op instead of a panic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Edvin Lindqvist <edvin.lindqvist@forsway.com>
Releasing a session deleted the seidSmContext document for each of its local SEIDs, and the context document that SEID document named, without checking whose they were. A SEID document is keyed by the SEID alone, so it may name another session than the one being released, and the release then deleted that session's documents, and its in-memory SEID entry with them. The SEID document is now deleted only while it maps to the session being released, and the delete is scoped to that session's reference; the context document deleted with it is the released session's own. The in-memory SEID index drops an entry only if it is held by the session being released. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Edvin Lindqvist <edvin.lindqvist@forsway.com>
…tore Deactivating a session's tunnels frees the PDR, FAR, BAR and QER identifiers its rules hold, returning them to the UPF's generators and removing what the UPF's pools hold under them. A session read back from the datastore carries identifiers an earlier SMF incarnation allocated. This one never allocated them, and may have given the same identifiers to a live session since, so releasing such a session freed the live session's identifiers and removed its rules from the pools, and the next session could be allocated them again. Deactivating the tunnels of any session read back from the datastore now removes its rules from its own PFCP session context only, and leaves the identifiers and the pool entries alone. That deactivation is where every release path frees them, so this holds however the session is released. Its PFCP session on a user plane this SMF holds is still deleted. A policy update that withdraws a rule from such a session had the same effect once the user plane accepted the removal, and now likewise takes the rule out of the session's tunnels and PFCP context and frees none of its identifiers. A rule this start added to the session since is left allocated too: a few identifiers lost until the SMF restarts, against taking a live session's. The revert of an abandoned modification, and a modification that is not sent, still free what they withdraw, which this start built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Edvin Lindqvist <edvin.lindqvist@forsway.com>
With enableDBStore a stored session is read back lazily, by whatever request next names it: an AMF update, a PCF notification, an OAM read, or a PFCP message for its SEID. After a restart and a re-attach the subscriber's PDU session is held by a new session, but the old one was still restored as live for any request that named it. The SMF then sent modifications for it that the user plane refused, and anything that ranges the session pool acted on a session that no longer existed. A restore now checks the subscriber's PDU session first. If it resolves to a different session, the stored one is released and the request is answered as for an unknown session. If nothing is current, the first restore lists every stored session for the key, serves the most recently created and releases the others, and registers the one served as current. Sessions record when they were created: the smfCount of the SMF start that created them, which every start with the datastore enabled increments, and a sequence within that start. The order requires smfCount never to go backwards; resetting it would rank an earlier start's sessions above a later one's. Several stored sessions written before that record existed have no order; none is served until the subscriber establishes the session again. A restore decides under the key's lock, so two requests naming two copies release each once. If the listing fails or does not contain the session being restored, the session is served as before, unregistered. A ReleaseSmContext is honoured whatever the order of the others: it names the session its sender holds, and refusing it would leave the sender holding a session nothing serves. A restored session anchored on a user plane that restarts is released through the path for a session that cannot be restored, AMF and PCF told, instead of being re-installed: its UE address was allocated by an earlier start of the SMF, and this one's allocator, which never reserved it, may have given it to another UE since. One that a later session superseded is not: the subscriber's PDU session is that later one, and the release command, which names the subscriber and the PDU session ID, would have the AMF release it. The sweep finds restored sessions with the other anchored sessions. While it has found nothing to re-install it looks again at the sessions whose locks it could not take, as it does for any busy session, and finding only restored sessions is no reason to stop; once it has found a session to re-install, a restored session that is busy is left, as a busy session this SMF created is. Restored sessions are released after the restoration waves, so their calls to the AMF do not hold up the sessions that can be re-installed. A session released or replaced in the meantime, its UE having established the PDU session again, is not released a second time; that holds for the sessions the user plane would not accept back as well, which are released after the waves too. A release that fails is counted among the user plane's sessions without service, and is reported in the restoration outcomes as releasedRestoredFromStore when it succeeds. The restored sessions found on the user plane are remembered as anchored there, so one still busy when the user plane restarts again is counted as without service rather than as absent. Releasing a superseded stored session does not publish the end of the subscriber's session: consumers key subscribers by IMSI, and the event would remove the record of the live one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Edvin Lindqvist <edvin.lindqvist@forsway.com>
A transaction that loads no SM context fails, and TxnFailure answers an update or a release of an unknown context with 404. It tested txn.Ctxt against nil, but the lookup stores a nil *SMContext in that interface, which is not nil, so the 404 was never built: the transaction ended without a response and the request was answered 500. Test the context the interface holds instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Edvin Lindqvist <edvin.lindqvist@forsway.com>
A policy update notification whose SM context is not found fails its transaction, and TxnFailure had no arm for it, so the transaction ended without a response and the notification was answered 500. The PCF reads a 500 as a fault of the SMF's, not as the session being gone, and counts it against the SMF; it is now answered 404 with a ProblemDetails body, as TS 29.500 clause 5.2.7.2 requires for a target resource that does not exist. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Edvin Lindqvist <edvin.lindqvist@forsway.com>
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.
Stacked on #713. This branch carries that PR's three commits. The changes for review here are the last three:
Do not serve a stored session that a later session supersededand the twoAnswer 404 …commits. Please merge #713 first. If this one is squash-merged first, it brings #713's commits with it and that PR is left empty.Problem
With
enableDBStore, a stored session is read back lazily, by whatever request next names it: an AMF update or release, a PCFSmPolicyUpdateNotification, an N1N2 failure notification, an OAM read, or a PFCP message for its SEID.GetSMContextandGetSMContextBySEIDput the stored document into the session pool as a live session. Neither checks whether the subscriber's PDU session has been established again since, and neither registers the restored session incanonicalRef.#713 releases the stored sessions when the subscriber establishes the PDU session again, as TS 24.501 clause 6.4.1.7 c) requires. That still leaves two ways a dead session is served:
A dead session served as live does harm. The SMF sends user-plane modifications for it that the UPF refuses (
PFCP session not found, repeatedly). Anything that looks it up by reference or SEID also acts on it: an AMF update, a PCF notification, an OAM read, a PFCP message.Fix
Do not serve a stored session that a later session superseded. A restore now checks the subscriber's PDU session first:
canonicalRefnames a different session, the stored one is released and the request is answered as for an unknown session.canonicalRef, so a later restore of an older copy is recognised as superseded.Sessions now record when they were created:
createdSmfCount, thesmfCountof the SMF start that created them, andcreatedSeq, a sequence within that start. Every start with the datastore enabled incrementssmfCount, and since #710 nothing depends on resetting it. The order requiressmfCountnever to go backwards: a reset would rank an earlier start's sessions above a later one's, and the SMF would then release the live session.Some cases are decided differently:
ReleaseSmContextis honoured whatever the order of the others. It names the session its sender holds, and refusing it would leave the AMF holding a session nothing serves.SubsOpDel). Consumers such as metricfunc key subscribers by IMSI, and the event would remove the record of the live session. A release on create (fix: release a stored session when its PDU session ID is established again #713) and an in-memory replacement still publish it, before the new session's start. Consumers see the delete, then the add, only if the topic keeps order, as a single partition does; the writer sets no message key.All callers of
GetSMContextandGetSMContextBySEIDalready treat nil as "no such session", so a refused restore needs no change at the call sites.A restored session on a UPF that restarts. Registering a restored session as the subscriber's current session also makes the UPF-restart sweep treat it as current. Until now such a session was skipped there, because nothing had registered it as current, and was left reporting active with no forwarding. Re-installing it would program the UPF for a UE address that an earlier start of the SMF allocated, which this start's running allocator never reserved and may have given to another UE since. So a restored session anchored on a UPF that restarts is now released through the existing path for a session that cannot be restored, with the AMF and PCF told. That holds whether or not it was registered.
releasedRestoredFromStore.The cost is that the restored flag is never cleared. A restored session that went on carrying traffic is released, not re-installed, on every later restart of its UPF, also when its address was provided by the UPF or is static. The UE re-establishes. Reconciling a restored session's address with the running allocator is the larger fix #712 leaves out of scope, and it is not done here.
Answer 404 when an update or release finds no SM context.
TxnFailuretestedtxn.Ctxt == nil, but the lookup stores a nil*SMContextin that interface, which is not nil. The 404 was never built, the transaction ended without a response, and the request was answered 500. This predates this PR, and any unknown reference hit it, but the AMF's handling of a refused session depends on getting 404.hasSMContextnow tests the context the interface holds.Answer 404 when a policy update notification finds no SM context.
TxnFailurehad no arm forSmPolicyUpdateNotification, so a refused notification was also answered 500. It is now answered 404 with a ProblemDetails body. TS 29.500 clause 5.2.7.2: "If the specified target resource does not exist, the NF shall reject the HTTP method with the HTTP status code "404 Not Found"." It also matters to the PCF. From v3.2.1, the PCF's notification of established sessions treats a 4xx from the SMF as a rejected session, and counts a 5xx towards giving up on the SMF.Testing
Unit tests in
context/stored_session_test.goandproducer/restored_session_test.go, against the in-memory datastore stand-in from #713:TestASupersededStoredSessionIsNotServed,TestTheMostRecentlyCreatedStoredSessionIsServed(including two sessions of one start, the older stored first),TestUnorderedStoredSessionsAreNeitherServedNorReleased,TestALoneStoredSessionIsRestored,TestALaterStartsSessionOutranksAnEarlierStarts;TestAReleaseOfAnUnorderedStoredSessionIsHonoured,TestARestoredSessionIsFoundMarkedForRelease,TestAReleaseRequestRacingALookupActsOnThePooledInstance;TestARestoreWhoseListingFailsIsServedUnregistered,TestARestoreTheListingMissesIsServedUnlessReleased;TestConcurrentRestoresOfOneSessionReleaseEachCopyOnce;TestARestoredSessionIsReleasedNotReinstalledWhenItsUserPlaneRestarts(including thereleasedRestoredFromStorecount)TestAnUnregisteredRestoredSessionIsReleasedWhenItsUserPlaneRestartsTestASupersededRestoredSessionIsNotSweptForReleaseTestARestoredSessionReleasedSinceTheSweepIsNotReleasedAgain(re-attach, re-attach while the listing fails, released on request: the AMF stub receives no release)TestABusyRestoredSessionIsReleasedOnceItCanBeExaminedTestABusyRestoredSessionIsCountedWithoutServiceTestARestoredSessionWhoseReleaseFailsIsCountedUnrestored,TestAFailedRestoredReleaseIsCountedBesideTheSessionsThatCouldNotBeExaminedTestARestoredSessionIsReleasedBesideSessionsToReinstallTestABusySessionBesideARestoredOneIsLookedAtAgainTestReleasingASupersededStoredSessionReportsNoEnd,TestReleasingTheCurrentRestoredSessionReportsItsEnd;TestARequestForASupersededSessionIsAnsweredNotFound(update, release of an unknown reference, OAM read) andTestAPolicyUpdateNotificationForASupersededSessionIsAnsweredNotFound.Each guard was reverted on its own, and a test failed every time. The reverts were:
releasedRestoredFromStoreoutcome;LoadOrStoreon the release-on-request path;TestALookupRacingTheReleaseOfAStoredSessionGetsNoSecondCopy: a lookup waits for the release and is then handed nothing);The one exception is the
LocalPurgedcheck before the release. Both purge paths also set the release state under the same lock, so the state check already catches them. It is kept to match the re-install path.On a lab cluster (SMF with
enableDBStore, BESS UPF, UERANSIM), on an earlier build of this change before its final rebase:pre-commit run --all-filesis clean on macOS (darwin/arm64, Go 1.26.10), with no hook skipped. That covers gitleaks, gci, staticcheck,go test -race ./... -count=20, golangci-lint v2.13.2, yamlfmt and reuse.go buildandgo vetare clean too. Each commit builds and vets on its own.Trade-offs
smfCount, and is ordered before an older session of the SMF that started later. The newer, live session is then the one released. The SMF can run several pods (it initialises DRSM), and a sibling pod's live session would look like a stored duplicate. fix: release a stored session when its PDU session ID is established again #713's release on create has the same exposure. The fix assumes one SMF instance per datastore, or instances whose sessions do not share keys.canonicalRefonly.🤖 Generated with Claude Code