Repository navigation
Prohibit rollback of a procedure which has passed its commit point #18909
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,8 +40,10 @@ | |
| import java.util.ArrayDeque; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.Collections; | ||
| import java.util.Deque; | ||
| import java.util.HashSet; | ||
| import java.util.IdentityHashMap; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
|
|
@@ -700,6 +702,16 @@ private LockStateResult<Env> executeRootStackRollback( | |
| () -> store.update(rootProcedure), "root procedure rollback"); | ||
| } | ||
| List<Procedure<Env>> subprocStack = procedureStack.getSubproceduresStack(); | ||
| // Decide which procedures must keep the states they have executed before anything is rolled | ||
| // back: the decision reads the state history, which the rollback itself shrinks. Identity is | ||
| // used because procedures override equals with the data of the operation they run. | ||
| final Set<Procedure<Env>> proceduresKeepingTheirStates = | ||
| Collections.newSetFromMap(new IdentityHashMap<>()); | ||
| for (Procedure<Env> procedure : subprocStack) { | ||
| if (!procedure.isSuccess() && !procedure.isRollbackSupported()) { | ||
| proceduresKeepingTheirStates.add(procedure); | ||
| } | ||
| } | ||
| int stackTail = subprocStack.size(); | ||
| while (stackTail-- > 0) { | ||
| Procedure<Env> procedure = subprocStack.get(stackTail); | ||
|
|
@@ -708,6 +720,10 @@ private LockStateResult<Env> executeRootStackRollback( | |
| cleanupAfterRollback(procedure); | ||
| continue; | ||
| } | ||
| if (proceduresKeepingTheirStates.contains(procedure)) { | ||
| // Finished below, once the rest of the stack has been rolled back. | ||
| continue; | ||
| } | ||
| ProcedureLockState lockState = acquireLock(procedure); | ||
| if (lockState != ProcedureLockState.LOCK_ACQUIRED) { | ||
| return new LockStateResult<>(lockState, procedure); | ||
|
|
@@ -730,12 +746,70 @@ private LockStateResult<Env> executeRootStackRollback( | |
| } | ||
| } | ||
|
|
||
| if (!proceduresKeepingTheirStates.isEmpty()) { | ||
| subprocStack.removeIf(proceduresKeepingTheirStates::contains); | ||
| proceduresKeepingTheirStates.forEach(this::finishWithoutRollback); | ||
| } | ||
|
|
||
| LOG.info( | ||
| ProcedureMessages.ROLLED_BACK_TIME_DURATION_IS, rootProcedure, rootProcedure.elapsedTime()); | ||
| rootProcedureCleanup(rootProcedure); | ||
| return new LockStateResult<>(ProcedureLockState.LOCK_ACQUIRED, rootProcedure); | ||
| } | ||
|
|
||
| /** | ||
| * Finishes a procedure without undoing any of the states it has executed. The failure is still | ||
| * reported, as the operation the procedure was part of did not complete; what is dropped is only | ||
| * the bookkeeping of the rollback, so that neither this rollback nor the one a new leader | ||
| * recovers touches this procedure again. | ||
| */ | ||
| private void finishWithoutRollback(final Procedure<Env> procedure) { | ||
| procedure.abandonRollback(); | ||
| if (!procedure.isSuccess()) { | ||
| procedure.setState(ProcedureState.ROLLEDBACK); | ||
| } | ||
| // The rollback loop skipped acquireLock() for this procedure, so a lock restored by | ||
| // restoreLock() on load is still recorded in lockedWhenLoading instead of locked. Finish that | ||
| // bookkeeping and release the lock here: rootProcedureCleanup() releases only the locks that | ||
| // hasLock() reports, and a finished procedure must not keep holding its lock. | ||
| if (procedure.hasLock() || procedure.isLockedWhenLoading()) { | ||
| if (acquireLock(procedure) == ProcedureLockState.LOCK_ACQUIRED) { | ||
| releaseLock(procedure, false); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P1] Delete child records before persisting the root's terminal state
I reproduced this using real Please make the child cleanup durable before the unlock persists the root's terminal state, or make recovery safely handle this intermediate state.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed it |
||
| } | ||
| } | ||
| if (procedure.hasParent()) { | ||
| try { | ||
| store.delete(procedure.getProcId()); | ||
| // do not remove this procedure when exception occurred | ||
| procedures.remove(procedure.getProcId()); | ||
| } catch (final Exception e) { | ||
| LOG.warn(ProcedureMessages.FAILED_TO_DELETE_PROCEDURE_ON_ROLLBACK, e); | ||
| } | ||
| } else { | ||
| // A child which never executed has no rollback step and is not visited above, so its record | ||
| // goes with the ones of the children which were. | ||
| final long[] childProcIds = rollbackStack.get(procedure.getProcId()).getSubprocedureIds(); | ||
| if (childProcIds.length > 0) { | ||
| try { | ||
| store.delete(childProcIds); | ||
| } catch (final Exception e) { | ||
| LOG.warn(ProcedureMessages.FAILED_TO_DELETE_PROCEDURE_ON_ROLLBACK, e); | ||
| } | ||
| } | ||
| // The stack indexes and the states just dropped are persisted here, so that the rollback | ||
| // recovered by a new leader leaves this procedure out. Endless retry since this step is not | ||
| // idempotent. | ||
| RetryUtils.executeWithEndlessBackoffRetry( | ||
| () -> store.update(procedure), | ||
| ProcedureMessages.LOG_FINISH_PROCEDURE_WITHOUT_ROLLBACK_41F7A354); | ||
| } | ||
| procedure.updateMetricsOnFinish(getEnvironment(), procedure.elapsedTime(), false); | ||
| LOG.info( | ||
| ProcedureMessages | ||
| .LOG_PROCEDURE_ARG_IS_PAST_ITS_COMMIT_POINT_KEEPING_EVERY_STATE_IT_EXECUTED_CA21D7E9, | ||
| procedure); | ||
| } | ||
|
|
||
| private ProcedureLockState acquireLock(Procedure<Env> proc) { | ||
| if (proc.hasLock()) { | ||
| return ProcedureLockState.LOCK_ACQUIRED; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.