Skip to content

[#857] Iterate a snapshot of the connection set in AuthenticatedUsers.doPostResponse(modify) - #858

Merged
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:fix/857-authenticated-users-infinite-loop
Aug 10, 2026
Merged

[#857] Iterate a snapshot of the connection set in AuthenticatedUsers.doPostResponse(modify)#858
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:fix/857-authenticated-users-infinite-loop

Conversation

@vharseko

Copy link
Copy Markdown
Member

Fixes #857.

Problem

AuthenticatedUsers.doPostResponse(PostResponseModifyOperation) iterates the live per-user connection set (ConcurrentHashMap.newKeySet()), while updateAuthenticationInfo()setAuthenticationInfo() re-registers each visited connection (remove + put) into that same set. The re-inserted node is appended to the tail of its hash-bin chain and the removed node keeps its next pointer, so the weakly-consistent iterator meets the connection again; with two or more connections in one bin the loop ping-pongs between them forever. The worker thread spins RUNNABLE and the bind that triggered the internal modify (password-policy last-login-time update) never gets a response — seen in CI as the PasswordPolicyTestCase.testResetWithLastLoginTime 600 s timeout.

Regression of #660: before it, the loop iterated a CopyOnWriteArraySet snapshot under the reentrant global write lock, so the nested remove/put could not affect the iteration. Both protections were removed together.

Fix

Iterate a snapshot (connectionSet.toArray(new ClientConnection[0])) instead of the live set — restores the copy-on-write iteration semantics while keeping registration lock-free. The delete/modifyDN paths are not affected: their sets are detached from the map first via removeSubtree.

Regression test

AuthenticatedUsersTestCase registers two connections with a constant hashCode() (forcing both into one bin of the connection set) under the same user DN and drives doPostResponse(modify) directly:

  • without the fix the call never returns — deterministic reproduction, fails with the same ThreadTimeoutException ... 600000 signature as the CI hang;
  • with the fix it passes in ~11 s: each connection is updated exactly once, stays registered, and caches the updated entry.

…n AuthenticatedUsers.doPostResponse(modify)

updateAuthenticationInfo() re-registers the connection (remove + put),
appending it back to the tail of the live set's hash-bin chain; a
weakly-consistent iterator over the set itself then meets the connection
again and, with two or more connections in one bin, ping-pongs between
them forever, hanging the bind that triggered the internal modify (seen
in CI as the PasswordPolicyTestCase.testResetWithLastLoginTime timeout).

Regression of OpenIdentityPlatform#660, which replaced the CopyOnWriteArraySet snapshot
iteration under the global write lock with direct iteration of the live
concurrent set. Add a regression test that pins two colliding
connections into one bin and reproduces the livelock deterministically.
@vharseko vharseko added bug concurrency Thread-safety / race-condition bugs tests Test suites: fixing, enabling, un-disabling labels Aug 10, 2026
@vharseko
vharseko requested a review from maximthomas August 10, 2026 09:53
@vharseko
vharseko merged commit a315ca0 into OpenIdentityPlatform:master Aug 10, 2026
18 checks passed
@vharseko
vharseko deleted the fix/857-authenticated-users-infinite-loop branch August 10, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug concurrency Thread-safety / race-condition bugs tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Infinite loop in AuthenticatedUsers.doPostResponse hangs bind (regression of #660): PasswordPolicyTestCase.testResetWithLastLoginTime timeout in CI

2 participants