Skip to content

JCR-5095: retry token creation on concurrent modification of the token parent - #368

Open
slachiewicz wants to merge 1 commit into
apache:trunkfrom
slachiewicz:JCR-5095-token-creation-retry
Open

JCR-5095: retry token creation on concurrent modification of the token parent#368
slachiewicz wants to merge 1 commit into
apache:trunkfrom
slachiewicz:JCR-5095-token-creation-retry

Conversation

@slachiewicz

Copy link
Copy Markdown
Member

Retry token node creation when a concurrent login invalidates the pending changes.

Problem

TokenBasedLoginTest fails intermittently on CI (JCR-5095, open since 2024-08). Two symptoms, both from the same race:

javax.jcr.LoginException: Failed to commit: Could not find child <uuid> of node <uuid>
  at TokenProvider.createToken(TokenProvider.java:173)   // session.save()
javax.jcr.LoginException: Failed to commit: failed to build path of <uuid>:
    <uuid> has no child entry for <uuid>                 // tokenNode.getPath()

Cause

Concurrent logins of the same user each get their own session (DefaultLoginModule.commit) but add their token node below the same .tokens parent. When another session commits below that parent, this session's view of the parent can lose the child entry for its own pending token node. The failure then surfaces either while saving (InvalidItemStateException from ItemSaveOperation.validateTransientItems) or right after, while resolving the new node's path (ItemNotFoundException from HierarchyManagerImpl).

getTokenParent already handles exactly this kind of conflict for the concurrent creation of the token store itself — it refreshes the session and re-reads the parent. The creation of the token node below that parent had no such handling, so a single conflict failed the whole login.

Change

Wrap the token node creation in a bounded retry (3 attempts) that discards the doomed transient state with session.refresh(false) and re-reads the token parent, mirroring the existing idiom in getTokenParent. The body of the old method is extracted unchanged into createTokenNode. After the last attempt the original exception is rethrown, so behaviour on persistent failures is unchanged.

This makes token creation tolerate the conflict; it does not remove the underlying core race in transient state handling, which is why JCR-5095 should stay open.

Verification

TokenBasedLoginTest run repeatedly on a clean repository, macOS / JDK 24:

before after
runs with the race failure 1 of 12 0 of 54

One run in the "after" set failed with an error I did not manage to capture, and it did not recur in 30 further runs, so I cannot say what it was. The baseline showed unrelated environment errors ("Failed to get Repository instance", stale repository lock) at a comparable rate.

mvn test -Dtest='org.apache.jackrabbit.core.security.authentication.**' — 114 tests, all passing.

Note for reviewers: session.refresh(false) discards all transient changes on the session. That is safe for DefaultLoginModule, which creates a dedicated session for token creation, but TokenBasedAuthentication.createToken is public and accepts a caller-supplied session. The pre-existing getTokenParent conflict path already calls session.refresh(false) on the same session, so this is not a new hazard, but it now applies to a second code path — happy to scope it differently if you prefer.

…n parent

Concurrent logins of the same user each use their own session but add their
token node below the same .tokens parent. A concurrent commit below that
parent can invalidate this session's pending changes, so that the token node
can neither be saved (InvalidItemStateException from validateTransientItems)
nor resolved afterwards (ItemNotFoundException while building its path).
Either one failed the whole login.

Wrap the token node creation in a bounded retry that discards the doomed
transient state via session.refresh(false) and re-reads the token parent,
mirroring the conflict handling that getTokenParent already performs for the
concurrent creation of the token store itself. The original exception is
rethrown once the attempts are exhausted, so behaviour on persistent
failures is unchanged.

This makes token creation tolerate the conflict but does not remove the
underlying race in the transient state handling.
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.

1 participant