Skip to content

[#896] Open the entry containers of a backend inside the write and register them after it - #1002

Open
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:fix/896-root-container-open-replay
Open

[#896] Open the entry containers of a backend inside the write and register them after it#1002
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:fix/896-root-container-open-replay

Conversation

@vharseko

@vharseko vharseko commented Sep 9, 2026

Copy link
Copy Markdown
Member

Fixes #896

RootContainer.open opens and registers the entry container of every base DN inside a single
storage.write, and Storage.write replays its operation after a transaction conflict - "In case of
a write operation rollback, implementations must ensure the write operation is retried until it
succeeds"
, whose WriteOperation is required to be idempotent. No rollback reaches the registry, so
a replay found every base DN the attempt it replaced had reached already registered and failed with
ERR_ENTRY_CONTAINER_ALREADY_REGISTERED: the backend did not open at all, and what the operator saw
said nothing about the conflict that caused the replay. Every attempt also left the configuration
listeners of its entry containers - five per container, plus one per attribute index and per VLV
index - registered on the backend configuration.

The change

The shape is the one #907 / #914 already established for the sibling path: work a rollback undoes
goes inside the write, work no rollback reaches goes after the commit.

  • RootContainer.open collects the containers the write opens and registers them once the write has
    committed. Registering after the commit cannot fail here - the base DNs come from a SortedSet and
    the map of a root container being opened is empty until then - so
    ERR_ENTRY_CONTAINER_ALREADY_REGISTERED becomes unreachable from this path rather than merely
    unlikely.
  • Every attempt begins by closing what the previous one opened, as BackendImpl.changeBaseDNTrees
    does: its trees are gone with the rollback, and its containers still hold their listeners.
  • An open which fails closes what it opened, for the same reason - otherwise the change would trade a
    wrong error for a silent leak.
  • openAndRegisterEntryContainers becomes openEntryContainers, which no longer registers anything.

Nothing inside the write reads the registry, which is what makes this available: an EntryContainer
asks its root container for getCompressedSchema() and nothing else, and every caller of
getEntryContainer/getBaseDNs runs after open() has returned.

Which engines reached it

Tests

ReplayedOpenTest, built on the harness ReplayedConfigChangeTest introduced in #914 - a storage
which raises PersistIt's own RollbackException from inside the operation, so the replay is driven by
PDBStorage.write's retry loop rather than by a second call to it:

  • a conflict raised while the second base DN is opened, with the first one already registered by the
    attempt being replaced - the case the report is written from;
  • a conflict reported by commit(), which replays an operation that ran to completion, and which
    also asserts that the replay gave up both containers of the previous attempt;
  • a failure which is not replayed at all, which must leave no entry container behind.

All three fail on master with ERR_ENTRY_CONTAINER_ALREADY_REGISTERED wrapped in ERR_OPEN_ENV_FAIL,
or with no listener ever deregistered.

Run green with the change: ReplayedOpenTest (3), ReplayedConfigChangeTest (12),
PersistentCompressedSchemaTest (9), OnDiskMergeImporterTest (29), PDBTestCase (35),
EncryptedPDBTestCase (35), PDBStorageTest (10), JETestCase (35), EncryptedJETestCase (35). The
jdbc engine suites need Docker and were not run locally; the change carries no jdbc specific code.

Not in scope

The listeners an EntryContainer abandons when its own open() throws, and the storage a failed
RootContainer.open leaves open, are #993.

…ide the write and register them after it

RootContainer.open opens and registers the entry container of every base DN in a
single storage.write, and Storage.write replays its operation after a transaction
conflict. No rollback reaches the registry, so a replay found every base DN the
attempt it replaced had reached already registered and failed with
ERR_ENTRY_CONTAINER_ALREADY_REGISTERED: the backend did not open at all, on a
message which said nothing about the conflict that caused the replay. Every
attempt also left the configuration listeners of its entry containers behind.

The containers are now opened inside the write and registered once it has
committed, and every attempt begins by giving up what the previous one opened, as
BackendImpl.changeBaseDNTrees does for the base DN change of OpenIdentityPlatform#907. An open which
fails closes what it opened, for the same reason.

Reached on persistit through a plain rollback, and on the jdbc backend through the
conflict and dropped connection replays of an existing backend, whose open issues
no committing statement; je and cassandra replay no write at all.
@vharseko
vharseko requested a review from maximthomas September 9, 2026 15:34
@vharseko vharseko added bug jdbc concurrency Thread-safety / race-condition bugs tests Test suites: fixing, enabling, un-disabling labels Sep 9, 2026
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 jdbc tests Test suites: fixing, enabling, un-disabling

Projects

None yet

1 participant