Search in sources :

Example 1 with TxnReadState

use of org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState in project alfresco-repository by Alfresco.

the class RetryingTransactionHelper method doInTransaction.

/**
 * Execute a callback in a transaction until it succeeds, fails
 * because of an error not the result of an optimistic locking failure,
 * or a deadlock loser failure, or until a maximum number of retries have
 * been attempted.
 * <p>
 * It is possible to force a new transaction to be created or to partake in
 * any existing transaction.
 *
 * @param cb                The callback containing the unit of work.
 * @param readOnly          Whether this is a read only transaction.
 * @param requiresNew       <tt>true</tt> to force a new transaction or
 *                          <tt>false</tt> to partake in any existing transaction.
 * @return                  Returns the result of the unit of work.
 * @throws                  RuntimeException  all checked exceptions are converted
 */
public <R> R doInTransaction(RetryingTransactionCallback<R> cb, boolean readOnly, boolean requiresNew) {
    // First validate the requiresNew setting
    if (!requiresNew) {
        TxnReadState readState = AlfrescoTransactionSupport.getTransactionReadState();
        switch(readState) {
            case TXN_READ_ONLY:
                if (!readOnly) {
                    // The current transaction is read-only, but a writable transaction is requested
                    throw new AlfrescoRuntimeException("Read-Write transaction started within read-only transaction");
                }
                // We are in a read-only transaction and this is what we require so continue with it.
                break;
            case TXN_READ_WRITE:
                // We are in a read-write transaction.  It cannot be downgraded so just continue with it.
                break;
            case TXN_NONE:
                // There is no current transaction so we need a new one.
                requiresNew = true;
                break;
            default:
                throw new RuntimeException("Unknown transaction state: " + readState);
        }
    }
    // If we need a new transaction, then we have to check that the read-write request can be served
    if (requiresNew) {
        if (this.readOnly && !readOnly) {
            throw new AccessDeniedException(MSG_READ_ONLY);
        }
    }
    // If we are time limiting, set ourselves a time limit and maintain the count of concurrent transactions
    long startTime = 0;
    Throwable stackTrace = null;
    if (requiresNew && maxExecutionMs > 0) {
        startTime = System.currentTimeMillis();
        synchronized (this) {
            if (txnCount > 0) {
                // If this transaction would take us above our ceiling, reject it
                long oldestStart = txnsInProgress.firstKey();
                long oldestDuration = startTime - oldestStart;
                if (oldestDuration > maxExecutionMs) {
                    throw new TooBusyException("Too busy: " + txnCount + " transactions. Oldest " + oldestDuration + " milliseconds", txnsInProgress.get(oldestStart).get(0));
                }
            }
            // Record the start time and stack trace of the starting thread
            List<Throwable> traces = txnsInProgress.get(startTime);
            if (traces == null) {
                traces = new LinkedList<Throwable>();
                txnsInProgress.put(startTime, traces);
            }
            stackTrace = new Exception("Stack trace");
            traces.add(stackTrace);
            ++txnCount;
        }
    }
    try {
        // Track the last exception caught, so that we
        // can throw it if we run out of retries.
        RuntimeException lastException = null;
        for (int count = 0; count == 0 || count < maxRetries; count++) {
            UserTransaction txn = null;
            try {
                if (requiresNew) {
                    txn = txnService.getNonPropagatingUserTransaction(readOnly, forceWritable);
                    txn.begin();
                    // Wrap it to protect it
                    UserTransactionProtectionAdvise advise = new UserTransactionProtectionAdvise();
                    ProxyFactory proxyFactory = new ProxyFactory(txn);
                    proxyFactory.addAdvice(advise);
                    UserTransaction wrappedTxn = (UserTransaction) proxyFactory.getProxy();
                    // Store the UserTransaction for static retrieval.  There is no need to unbind it
                    // because the transaction management will do that for us.
                    AlfrescoTransactionSupport.bindResource(KEY_ACTIVE_TRANSACTION, wrappedTxn);
                }
                // Do the work.
                R result = cb.execute();
                // Only commit if we 'own' the transaction.
                if (txn != null) {
                    if (txn.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("\n" + "Transaction marked for rollback: \n" + "   Thread: " + Thread.currentThread().getName() + "\n" + "   Txn:    " + txn + "\n" + "   Iteration: " + count);
                        }
                        // Something caused the transaction to be marked for rollback
                        // There is no recovery or retrying with this
                        txn.rollback();
                    } else {
                        // The transaction hasn't been flagged for failure so the commit
                        // sould still be good.
                        txn.commit();
                    }
                }
                if (logger.isDebugEnabled()) {
                    if (count != 0) {
                        logger.debug("\n" + "Transaction succeeded: \n" + "   Thread: " + Thread.currentThread().getName() + "\n" + "   Txn:    " + txn + "\n" + "   Iteration: " + count);
                    }
                }
                return result;
            } catch (Throwable e) {
                // Somebody else 'owns' the transaction, so just rethrow.
                if (txn == null) {
                    RuntimeException ee = AlfrescoRuntimeException.makeRuntimeException(e, "Exception from transactional callback: " + cb);
                    throw ee;
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("\n" + "Transaction commit failed: \n" + "   Thread: " + Thread.currentThread().getName() + "\n" + "   Txn:    " + txn + "\n" + "   Iteration: " + count + "\n" + "   Exception follows:", e);
                }
                // Rollback if we can.
                if (txn != null) {
                    try {
                        int txnStatus = txn.getStatus();
                        // then the status will be NO_TRANSACTION.
                        if (txnStatus != Status.STATUS_NO_TRANSACTION && txnStatus != Status.STATUS_ROLLEDBACK) {
                            txn.rollback();
                        }
                    } catch (Throwable e1) {
                        // A rollback failure should not preclude a retry, but logging of the rollback failure is required
                        logger.error("Rollback failure.  Normal retry behaviour will resume.", e1);
                    }
                }
                if (e instanceof RollbackException) {
                    lastException = (e.getCause() instanceof RuntimeException) ? (RuntimeException) e.getCause() : new AlfrescoRuntimeException("Exception in Transaction.", e.getCause());
                } else {
                    lastException = (e instanceof RuntimeException) ? (RuntimeException) e : new AlfrescoRuntimeException("Exception in Transaction.", e);
                }
                // Check if there is a cause for retrying
                Throwable retryCause = extractRetryCause(e);
                // ALF-17361 fix, also check for configured extra exceptions
                if (retryCause == null && extraExceptions != null && !extraExceptions.isEmpty()) {
                    retryCause = ExceptionStackUtil.getCause(e, extraExceptions.toArray(new Class[] {}));
                }
                if (retryCause != null) {
                    // Sleep a random amount of time before retrying.
                    // The sleep interval increases with the number of retries.
                    int sleepIntervalRandom = (count > 0 && retryWaitIncrementMs > 0) ? random.nextInt(count * retryWaitIncrementMs) : minRetryWaitMs;
                    int sleepInterval = Math.min(maxRetryWaitMs, sleepIntervalRandom);
                    sleepInterval = Math.max(sleepInterval, minRetryWaitMs);
                    if (logger.isInfoEnabled() && !logger.isDebugEnabled()) {
                        String msg = String.format("Retrying %s: count %2d; wait: %1.1fs; msg: \"%s\"; exception: (%s)", Thread.currentThread().getName(), count, (double) sleepInterval / 1000D, retryCause.getMessage(), retryCause.getClass().getName());
                        logger.info(msg);
                    }
                    try {
                        Thread.sleep(sleepInterval);
                    } catch (InterruptedException ie) {
                    // Do nothing.
                    }
                    // Try again
                    continue;
                } else {
                    // It was a 'bad' exception.
                    throw lastException;
                }
            }
        }
        // So, fail.
        throw lastException;
    } finally {
        if (requiresNew && maxExecutionMs > 0) {
            synchronized (this) {
                txnCount--;
                List<Throwable> traces = txnsInProgress.get(startTime);
                if (traces != null) {
                    if (traces.size() == 1) {
                        txnsInProgress.remove(startTime);
                    } else {
                        traces.remove(stackTrace);
                    }
                }
            }
        }
    }
}
Also used : UserTransaction(javax.transaction.UserTransaction) AccessDeniedException(org.alfresco.repo.security.permissions.AccessDeniedException) ProxyFactory(org.springframework.aop.framework.ProxyFactory) TxnReadState(org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState) RollbackException(javax.transaction.RollbackException) ConcurrencyFailureException(org.springframework.dao.ConcurrencyFailureException) TooManyResultsException(org.apache.ibatis.exceptions.TooManyResultsException) JdbcUpdateAffectedIncorrectNumberOfRowsException(org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException) BatchUpdateException(java.sql.BatchUpdateException) UncategorizedSQLException(org.springframework.jdbc.UncategorizedSQLException) SQLException(java.sql.SQLException) DeadlockLoserDataAccessException(org.springframework.dao.DeadlockLoserDataAccessException) DataIntegrityViolationException(org.springframework.dao.DataIntegrityViolationException) RollbackException(javax.transaction.RollbackException) AlfrescoRuntimeException(org.alfresco.error.AlfrescoRuntimeException) LicenseIntegrityException(org.alfresco.service.license.LicenseIntegrityException) AccessDeniedException(org.alfresco.repo.security.permissions.AccessDeniedException) LockTryException(org.alfresco.util.LockHelper.LockTryException) AlfrescoRuntimeException(org.alfresco.error.AlfrescoRuntimeException) AlfrescoRuntimeException(org.alfresco.error.AlfrescoRuntimeException)

Example 2 with TxnReadState

use of org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState in project alfresco-repository by Alfresco.

the class AlfrescoTransactionSupportTest method testReadWriteStateRetrieval.

@Test
public void testReadWriteStateRetrieval() throws Exception {
    final TxnReadState[] postCommitReadState = new TxnReadState[1];
    final TransactionListenerAdapter getReadStatePostCommit = new TransactionListenerAdapter() {

        @Override
        public void afterCommit() {
            postCommitReadState[0] = AlfrescoTransactionSupport.getTransactionReadState();
        }

        @Override
        public void afterRollback() {
            postCommitReadState[0] = AlfrescoTransactionSupport.getTransactionReadState();
        }
    };
    RetryingTransactionCallback<TxnReadState> getReadStateWork = new RetryingTransactionCallback<TxnReadState>() {

        public TxnReadState execute() throws Exception {
            // Register to list to post-commit
            AlfrescoTransactionSupport.bindListener(getReadStatePostCommit);
            return AlfrescoTransactionSupport.getTransactionReadState();
        }
    };
    // Check TXN_NONE
    TxnReadState checkTxnReadState = AlfrescoTransactionSupport.getTransactionReadState();
    assertEquals("Expected 'no transaction'", TxnReadState.TXN_NONE, checkTxnReadState);
    assertNull("Expected no post-commit read state", postCommitReadState[0]);
    // Check TXN_READ_ONLY
    checkTxnReadState = transactionService.getRetryingTransactionHelper().doInTransaction(getReadStateWork, true);
    assertEquals("Expected 'read-only transaction'", TxnReadState.TXN_READ_ONLY, checkTxnReadState);
    assertEquals("Expected 'no transaction'", TxnReadState.TXN_NONE, postCommitReadState[0]);
    // check TXN_READ_WRITE
    checkTxnReadState = transactionService.getRetryingTransactionHelper().doInTransaction(getReadStateWork, false);
    assertEquals("Expected 'read-write transaction'", TxnReadState.TXN_READ_WRITE, checkTxnReadState);
    assertEquals("Expected 'no transaction'", TxnReadState.TXN_NONE, postCommitReadState[0]);
    // Check TXN_NONE on rollback
    UserTransaction txn = transactionService.getUserTransaction();
    txn.begin();
    AlfrescoTransactionSupport.bindListener(getReadStatePostCommit);
    txn.rollback();
    assertEquals("Expected 'no transaction'", TxnReadState.TXN_NONE, postCommitReadState[0]);
    // Check TXN_NONE on commit
    txn = transactionService.getUserTransaction();
    txn.begin();
    AlfrescoTransactionSupport.bindListener(getReadStatePostCommit);
    txn.commit();
    assertEquals("Expected 'no transaction'", TxnReadState.TXN_NONE, postCommitReadState[0]);
}
Also used : UserTransaction(javax.transaction.UserTransaction) RetryingTransactionCallback(org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback) TxnReadState(org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState) BaseSpringTest(org.alfresco.util.BaseSpringTest) Test(org.junit.Test)

Example 3 with TxnReadState

use of org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState in project alfresco-repository by Alfresco.

the class AuditComponentImpl method recordAuditValuesWithUserFilter.

@Override
public Map<String, Serializable> recordAuditValuesWithUserFilter(String rootPath, Map<String, Serializable> values, boolean useUserFilter) {
    ParameterCheck.mandatory("rootPath", rootPath);
    AuditApplication.checkPathFormat(rootPath);
    String username = AuthenticationUtil.getFullyAuthenticatedUser();
    if (values == null || values.isEmpty() || !areAuditValuesRequired() || !(userAuditFilter.acceptUser(username) || !useUserFilter) || !auditFilter.accept(rootPath, values)) {
        return Collections.emptyMap();
    }
    // Log inbound values
    if (loggerInbound.isDebugEnabled()) {
        StringBuilder sb = new StringBuilder(values.size() * 64);
        sb.append("\n").append("Inbound audit values:");
        for (Map.Entry<String, Serializable> entry : values.entrySet()) {
            String pathElement = entry.getKey();
            String path = AuditApplication.buildPath(rootPath, pathElement);
            Serializable value = entry.getValue();
            sb.append("\n\t").append(path).append("=").append(value);
        }
        loggerInbound.debug(sb.toString());
    }
    // Build the key paths using the session root path
    Map<String, Serializable> pathedValues = new HashMap<String, Serializable>(values.size() * 2);
    for (Map.Entry<String, Serializable> entry : values.entrySet()) {
        String pathElement = entry.getKey();
        String path = AuditApplication.buildPath(rootPath, pathElement);
        pathedValues.put(path, entry.getValue());
    }
    // Translate the values map
    PathMapper pathMapper = auditModelRegistry.getAuditPathMapper();
    final Map<String, Serializable> mappedValues = pathMapper.convertMap(pathedValues);
    if (mappedValues.isEmpty()) {
        return mappedValues;
    }
    // We have something to record.  Start a transaction, if necessary
    TxnReadState txnState = AlfrescoTransactionSupport.getTransactionReadState();
    switch(txnState) {
        case TXN_NONE:
        case TXN_READ_ONLY:
            // New transaction
            RetryingTransactionCallback<Map<String, Serializable>> callback = new RetryingTransactionCallback<Map<String, Serializable>>() {

                public Map<String, Serializable> execute() throws Throwable {
                    return recordAuditValuesImpl(mappedValues);
                }
            };
            RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper();
            txnHelper.setForceWritable(true);
            return txnHelper.doInTransaction(callback, false, true);
        case TXN_READ_WRITE:
            return recordAuditValuesImpl(mappedValues);
        default:
            throw new IllegalStateException("Unknown txn state: " + txnState);
    }
}
Also used : Serializable(java.io.Serializable) PathMapper(org.alfresco.util.PathMapper) HashMap(java.util.HashMap) RetryingTransactionHelper(org.alfresco.repo.transaction.RetryingTransactionHelper) TxnReadState(org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState) RetryingTransactionCallback(org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback) HashMap(java.util.HashMap) Map(java.util.Map)

Example 4 with TxnReadState

use of org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState in project alfresco-repository by Alfresco.

the class CommandExecutorImpl method execute.

@Override
public Object execute(final SrvSession sess, final TreeConnection tree, final Command command) throws IOException {
    TxnReadState readState = command.getTransactionRequired();
    Object ret = null;
    // No transaction required.
    if (readState == TxnReadState.TXN_NONE) {
        ret = executeInternal(sess, tree, command, null);
    } else {
        // Yes a transaction is required.
        RetryingTransactionHelper helper = transactionService.getRetryingTransactionHelper();
        boolean readOnly = readState == TxnReadState.TXN_READ_ONLY;
        RetryingTransactionCallback<Object> cb = new RetryingTransactionCallback<Object>() {

            /**
             * Perform a set of commands as a unit of transactional work.
             *
             * @return              Return the result of the unit of work
             * @throws IOException
             */
            public Object execute() throws IOException {
                try {
                    return executeInternal(sess, tree, command, null);
                } catch (IOException e) {
                    // Ensure original checked IOExceptions get propagated
                    throw new PropagatingException(e);
                }
            }
        };
        try {
            ret = helper.doInTransaction(cb, readOnly);
        } catch (PropagatingException pe) {
            if (command instanceof CompoundCommand) {
                if (logger.isDebugEnabled()) {
                    logger.debug("error executing command :command" + command, pe);
                }
                CompoundCommand c = (CompoundCommand) command;
                // Error Callback Here ?
                List<Command> commands = c.getPostErrorCommands();
                if (commands != null) {
                    for (Command c2 : commands) {
                        try {
                            executeInternal(sess, tree, c2, ret);
                        } catch (Throwable t) {
                            logger.warn("caught and ignored exception from error handler", t);
                        // Swallow exception from error handler.
                        }
                    }
                }
            }
            // Unwrap checked exceptions
            throw (IOException) pe.getCause();
        }
    }
    /**
     * execute post commit commands.
     */
    if (command instanceof CompoundCommand) {
        logger.debug("post commit of compound command");
        CompoundCommand c = (CompoundCommand) command;
        List<Command> commands = c.getPostCommitCommands();
        if (commands != null) {
            for (Command c2 : commands) {
                // TODO - what about exceptions from post commit?
                executeInternal(sess, tree, c2, ret);
            }
        }
    }
    return ret;
}
Also used : RetryingTransactionHelper(org.alfresco.repo.transaction.RetryingTransactionHelper) TxnReadState(org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState) IOException(java.io.IOException) CompoundCommand(org.alfresco.filesys.repo.rules.commands.CompoundCommand) RetryingTransactionCallback(org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback) RenameFileCommand(org.alfresco.filesys.repo.rules.commands.RenameFileCommand) DoNothingCommand(org.alfresco.filesys.repo.rules.commands.DoNothingCommand) CloseFileCommand(org.alfresco.filesys.repo.rules.commands.CloseFileCommand) CreateFileCommand(org.alfresco.filesys.repo.rules.commands.CreateFileCommand) Command(org.alfresco.filesys.repo.rules.Command) MoveFileCommand(org.alfresco.filesys.repo.rules.commands.MoveFileCommand) RestoreFileCommand(org.alfresco.filesys.repo.rules.commands.RestoreFileCommand) CopyContentCommand(org.alfresco.filesys.repo.rules.commands.CopyContentCommand) DeleteFileCommand(org.alfresco.filesys.repo.rules.commands.DeleteFileCommand) CompoundCommand(org.alfresco.filesys.repo.rules.commands.CompoundCommand) ReturnValueCommand(org.alfresco.filesys.repo.rules.commands.ReturnValueCommand) OpenFileCommand(org.alfresco.filesys.repo.rules.commands.OpenFileCommand) ReduceQuotaCommand(org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand) RemoveTempFileCommand(org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand) List(java.util.List)

Aggregations

TxnReadState (org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState)4 RetryingTransactionCallback (org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback)3 UserTransaction (javax.transaction.UserTransaction)2 RetryingTransactionHelper (org.alfresco.repo.transaction.RetryingTransactionHelper)2 IOException (java.io.IOException)1 Serializable (java.io.Serializable)1 BatchUpdateException (java.sql.BatchUpdateException)1 SQLException (java.sql.SQLException)1 HashMap (java.util.HashMap)1 List (java.util.List)1 Map (java.util.Map)1 RollbackException (javax.transaction.RollbackException)1 AlfrescoRuntimeException (org.alfresco.error.AlfrescoRuntimeException)1 Command (org.alfresco.filesys.repo.rules.Command)1 CloseFileCommand (org.alfresco.filesys.repo.rules.commands.CloseFileCommand)1 CompoundCommand (org.alfresco.filesys.repo.rules.commands.CompoundCommand)1 CopyContentCommand (org.alfresco.filesys.repo.rules.commands.CopyContentCommand)1 CreateFileCommand (org.alfresco.filesys.repo.rules.commands.CreateFileCommand)1 DeleteFileCommand (org.alfresco.filesys.repo.rules.commands.DeleteFileCommand)1 DoNothingCommand (org.alfresco.filesys.repo.rules.commands.DoNothingCommand)1