Conversation
…onflict instead of giving it up at once JEStorage.write made one attempt: the DeadlockException JE throws at the victim of a deadlock - the one conflict left with je.lock.timeout at 0 - reached the caller bare after that attempt, and the LDAP client was answered with a server error carrying JE's account of its lock table. PDBStorage and JDBCStorage replay under an attempt cap and a retry window; JEStorage now does the same for a LockConflictException, and gives it up the way they do. Three points are JE's own. The conflict is raised inside the operation and, should the operation swallow it, again by commit(), so both forms are matched and a swallowed conflict commits nothing. The replay backs off first: a record is locked by the LSN of its current version, the victim's abort undoes the version the survivor was granted the lock on, and a replay which comes back at once wins the race for the version put back and forms the deadlock again. And the interrupt flag is not restored when the backoff is interrupted, since JE invalidates the whole environment when a thread carrying the flag makes any call - which the caller's failure road does.
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.
Fixes #1064.
What JE does, and what
JEStorage.writedid with itWith the shipped configuration
je.lock.timeoutis 0 (ConfigurableEnvironment.defaultConfig), so a writer waiting for a record lock waits rather than times out, and the one conflict JE raises is the deadlock: it detects the cycle as soon as a wait would close it -LockManager.waitForLockrunscheckAndHandleDeadlockbefore every wait, the detection delay being 0 - and ends one transaction of the cycle, chosen at random, with aDeadlockException; the other is granted its lock once the victim has aborted.JEStorage.writemade one attempt: the transaction wraps the conflict in aStorageRuntimeException,write()unwrapped it and threw the bareDeadlockException, andBackendImpl.createDirectoryExceptionanswered the LDAP client with the server error result code and JE's account of its lock table as the diagnostic message. Reproduced at the storage level before the change: two writers locking two records in opposite order, the victim'swrite()throwing after one attempt within ~8 ms while the other commits.PDBStorage.writereplays a rolled back transaction under an attempt cap and a retry window (#937);JDBCStorage.writeunderMAX_RETRIESandMAX_RETRY_WINDOW_NANOS. NowJEStorage.writedoes the same for aLockConflictException- the base class JE documents as "abort and retry" - under the same two bounds, and gives it up the same way: aStorageRuntimeExceptionnaming the backend, the attempts, the time and which bound was spent, the conflict suppressed rather than made the cause, since every caller strips a cause off (write()itself andEntryContainer.throwAllowedExceptionTypes).Where the JE loop differs from the PDB one
commit(). JE raises it from a record read or write, never from the commit, which takes no lock; the operation may catch it there -DN2URI.targetEntryReferralsswallows aStorageRuntimeException, andVLVIndex.applyConfigurationChangedid until [#991] Decide and report an index configuration change outside the write which is replayed #997 - and the transaction is then abort-only, socommit()raises the conflict again, bare. The loop matches both forms, and an attempt which swallowed its conflict commits nothing and is replayed, where on PersistIt it "committed an attempt which had done nothing" ([#991] Decide and report an index configuration change outside the write which is replayed #997).TxnManager.registerTxn/unRegisterTxnacquire their latch interruptibly, andEntryContainer.writeTrustState, on the caller's own failure road, is such a call. The interrupt is reported instead, next to the conflict, as the suppressed exceptions of theStorageRuntimeExceptionthrown; the flag stays as the sleep cleared it.ConfigurableEnvironment's comment abovesetLockTimeout(0)said a deadlocked operation blocks indefinitely; it does not, and the comment now says what JE does with it.Reach
The ordinary LDAP write path does not reach the deadlock:
IndexBufferflushes keys inTreeMaporder,EntryContainerkeeps one tree order across add, delete, modify and modDN ("Ensure same access ordering as deleteEntry", "Ensure that all index updates are done in the correct order to avoid deadlocks" - the ordering the 2.6 JE backend already had, OPENDJ-1375), the counters are sharded by thread, and the core holds DN locks. 3992 concurrent adds, modifies, deletes and renames over 8 threads on shared index keys produced none. So this is parity with the other two engines and a guard for an ordering nothing checks: a new write path breaks it silently, and an operator who setsje.lock.timeoutthroughds-cfg-je-propertyturns plain contention into aLockTimeoutException, which the loop replays as well.Tests
JEStorageTest, new, afterPDBStorageTest. The conflicts are JE's own - aDeadlockExceptioncannot be built by a test, its constructor needs the internal locker it invalidates - a deadlock made by two writers locking two records in opposite order, and a conflict on every attempt made by a transaction which keeps a record locked while the storage runs with aje.lock.timeout, set the way an operator sets it:testDeadlockVictimIsReplayedandtestConflictSwallowedInsideTheOperationIsRaisedAgainByCommitAndReplayed: both writers commit, at least one was replayed, the records agree; red at the base with the bareDeadlockException- the swallowed variant with the onecommit()raises;testWriteGivesUpAfterTheAttemptCap,testWriteGivesUpOnTheWindowWhenAttemptsAreSlow: the message names the bound, the cause is null, the conflict is suppressed, the record is untouched;testWriteIsReplayedUntilTheConflictClears,testWriteIsReplayedOnceWhenTheFirstAttemptOutlastsTheWindow: the attempt after the holder's commit is the one which applies;testInterruptedWriteReportsTheConflictItWasReplaying: the flag is clear afterwards and the exception carries both. To deliver the interrupt to the sleep and to nothing of JE, it runs on the storage's import environment, whose writes open no transaction, with an operation raising a conflict JE raised earlier: a transaction aborted with the flag set takes the environment down before the sleep is reached, which is what the first version of this case did to every test after it;testRetryDelayGrowsAndStaysBounded, the PDB pin for the JE copy.The four holder cases and the delay pin need the bounds and the constructor, so they are red at the base by construction rather than by assertion. Green here:
JEStorageTest8/8 five times in a row,JETestCase,EncryptedJETestCase,PDBStorageTest,ReplayedConfigChangeTest.Not in this change
JEStorage.readmakes one attempt as well. A non-transactional reader can only be a deadlock victim inside a cycle, and with forward cursor walks against writers which lock in one order there is none to close; left alone.EntryContainer.renameEntryupdates the old superior's count before the new one's, so two opposite renames between the same parents on threads whose ids agree modulo 256 (the counter shard) can deadlock. Reachable, rare, and now replayed rather than reported; not otherwise addressed.