docs: protected void backout(boolean reUse,
                             Transaction transaction)
                      throws ObjectManagerException {
          final String methodName = "backout";
          if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
              trace.entry(this, cclass, methodName, new Object[] { new Boolean(reUse),
                                                                  transaction });
  
          boolean persistentWorkDone = false;
          ManagedObject[] lockedManagedObjects;
          int numberOfLockedManagedObjects = 0;
  
          synchronized (this) {
              // To defend against two application threads completing the same transaction and trying to
              // continue with it at the same time we check that the Transaction still refers to this one,
              // now that we are synchronized on the InternalTransaction.
              if (transaction.internalTransaction != this) {
                  if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
                      trace.exit(this, cclass,
                                 methodName,
                                 new Object[] { "via InvalidTransactionException", transaction.internalTransaction }
                                      );
                  // Same behaviour as if the transaction was completed and replaced by
                  // objectManagerState.dummyInternalTransaction.
                  throw new InvalidStateException(this,
                                                  InternalTransaction.stateTerminated,
                                                  InternalTransaction.stateNames[InternalTransaction.stateTerminated]);
  
              } // if (transaction.internalTransaction != this).
  
              // Only call prePrepare if we have not already prepared the transaction.
              if (state == stateInactive
                  || state == stateActiveNonPersistent
                  || state == stateActivePersistent) {
                  prePrepare(transaction);
              } // If already prepared.
  
              testState(nextStateForStartBackout);
              setState(nextStateForStartBackout);
  
              preBackout(transaction); // Tell ManagedObjects the outcome.
  
              // Is there any logging to do?
              if (state == stateBackingOutPersistent) {
                  persistentWorkDone = true;
  
                  TransactionBackoutLogRecord transactionBackoutLogRecord = new TransactionBackoutLogRecord(this);
                  objectManagerState.logOutput.writeNext(transactionBackoutLogRecord,
                                                         -logSpaceReserved,
                                                         true,
                                                         true);
                  logSpaceReserved = 0;
              } // If logging work to do.
  
              // Drive the backout method for the included objects.
              // The synchronized block prevents us from taking a checkpoint until all of the
              // ManagedObjects have had their opportunity to update the ObjectStore. If a
              // checkpoint is currently active we will update the current checkpoint set in
              // the ObjectStore, if not we will update the next set of updates.
              lockedManagedObjects = new ManagedObject[includedManagedObjects.size()];
  
              boolean requiresCurrentPersistentCheckpoint = requiresPersistentCheckpoint
                                                            || (objectManagerState.checkpointStarting == ObjectManagerState.CHECKPOINT_STARTING_PERSISTENT);
  
              for (java.util.Iterator managedObjectIterator = includedManagedObjects.values().iterator(); managedObjectIterator.hasNext();) {
                  ManagedObject managedObject = (ManagedObject) managedObjectIterator.next();
                  long managedObjectSequenceNumber = ((Long) managedObjectSequenceNumbers.get(managedObject.owningToken)).longValue();
  
                  if (managedObject.lockedBy(transaction)) {
                      managedObject.backout(transaction,
                                            managedObjectSequenceNumber,
                                            requiresCurrentPersistentCheckpoint);
                      lockedManagedObjects[numberOfLockedManagedObjects++] = managedObject;
                  } else {
                      ObjectManagerByteArrayOutputStream serializedBytes = (ObjectManagerByteArrayOutputStream) loggedSerializedBytes.get(managedObject.owningToken);
                      managedObject.optimisticReplaceBackout(transaction,
                                                             serializedBytes,
                                                             managedObjectSequenceNumber,
                                                             requiresCurrentPersistentCheckpoint);
                  } // if(managedObject.lockedBy(transaction)).
  
              } // for... includedManagedObjects.
  
              setState(nextStateForBackout);
              transactionLock.unLock(objectManagerState);
              postBackout(transaction); // Tell ManagedObjects the outcome is complete.
              // Tidy up the transaction.
              complete(reUse,
                       transaction);
          } // synchronized (this).
  
          // We don't want to clear the transaction lock held by the managedObject otherwise
          // ManagedObject.wasLocked() will not be able to give the past locked state. The
          // Unlock point wa noted above so now notify the ManagedObject and give a new
          // waiter a chance to acquire the lock. If a new transaction acquires the lock then
          // ManagedObject.wasLocked will return its result for the new transaction and
          // wasLocked() will then be true for an even later time.  Do this after we have release the
          // synchronize lock on InternalTransaction so that we avoid deadlock with ManagedObjects
          // that invoke synchronized InternalTransaction methods.
          for (int i = 0; i < numberOfLockedManagedObjects; i++) {
              synchronized (lockedManagedObjects[i]) {
                  lockedManagedObjects[i].notify();
              } // synchronized (lockedManagedObjects[i]).
          } // for... lockedManagedObjects.
  
          // Tell the ObjectManager that we are done, once the transaction is unlocked
          // in case it is needed for checkpoint.
          objectManagerState.transactionCompleted(this,
                                                  persistentWorkDone);
          // See if we need to delay while a checkpoint completes. Applications amy ask to reUSe
          // the same transaction, if so we introduce the delay here. Internal transactions are never
          // reUsed so we don't need to wory about blocking them. This call must be made when we
          // are not synchronized on the transaction because it might block waiting for a checkpoint
          // to complete if the log is full.
          if (reUse)
              objectManagerState.transactionPacing();
  
          if (Tracing.isAnyTracingEnabled() && trace.isEntryEnabled())
              trace.exit(this, cclass, methodName);
      }