Search in sources :

Example 1 with RetryException

use of com.github.rholder.retry.RetryException in project graylog2-server by Graylog2.

the class MongoDbSessionDAO method doUpdate.

@Override
protected void doUpdate(Session session) {
    final MongoDbSession dbSession = mongoDBSessionService.load(session.getId().toString());
    if (null == dbSession) {
        throw new RuntimeException("Couldn't load session <" + session.getId() + ">");
    }
    LOG.debug("Updating session {}", session);
    dbSession.setHost(session.getHost());
    dbSession.setTimeout(session.getTimeout());
    dbSession.setStartTimestamp(session.getStartTimestamp());
    dbSession.setLastAccessTime(session.getLastAccessTime());
    if (session instanceof SimpleSession) {
        final SimpleSession simpleSession = (SimpleSession) session;
        dbSession.setAttributes(simpleSession.getAttributes());
        dbSession.setExpired(simpleSession.isExpired());
    } else {
        throw new RuntimeException("Unsupported session type: " + session.getClass().getCanonicalName());
    }
    // Due to https://jira.mongodb.org/browse/SERVER-14322 upserts can fail under concurrency.
    // We need to retry the update, and stagger them a bit, so no all of the retries attempt it at the same time again.
    // Usually this should succeed the first time, though
    final Retryer<Object> retryer = RetryerBuilder.newBuilder().retryIfExceptionOfType(DuplicateKeyException.class).withWaitStrategy(WaitStrategies.randomWait(5, TimeUnit.MILLISECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(10)).build();
    try {
        retryer.call(() -> mongoDBSessionService.saveWithoutValidation(dbSession));
    } catch (ExecutionException e) {
        LOG.warn("Unexpected exception when saving session to MongoDB. Failed to update session.", e);
        throw new RuntimeException(e.getCause());
    } catch (RetryException e) {
        LOG.warn("Tried to update session 10 times, but still failed. This is likely because of https://jira.mongodb.org/browse/SERVER-14322", e);
        throw new RuntimeException(e.getCause());
    }
}
Also used : SimpleSession(org.apache.shiro.session.mgt.SimpleSession) ExecutionException(java.util.concurrent.ExecutionException) RetryException(com.github.rholder.retry.RetryException) DuplicateKeyException(com.mongodb.DuplicateKeyException)

Example 2 with RetryException

use of com.github.rholder.retry.RetryException in project gerrit by GerritCodeReview.

the class ExternalIdsUpdate method updateNoteMap.

private RefsMetaExternalIdsUpdate updateNoteMap(MyConsumer<OpenRepo> update) throws IOException, ConfigInvalidException, OrmException {
    try {
        return retryer.call(() -> {
            try (Repository repo = repoManager.openRepository(allUsersName);
                ObjectInserter ins = repo.newObjectInserter()) {
                ObjectId rev = readRevision(repo);
                afterReadRevision.run();
                try (RevWalk rw = new RevWalk(repo)) {
                    NoteMap noteMap = readNoteMap(rw, rev);
                    update.accept(OpenRepo.create(repo, rw, ins, noteMap));
                    return commit(repo, rw, ins, rev, noteMap);
                }
            }
        });
    } catch (ExecutionException | RetryException e) {
        if (e.getCause() != null) {
            Throwables.throwIfInstanceOf(e.getCause(), IOException.class);
            Throwables.throwIfInstanceOf(e.getCause(), ConfigInvalidException.class);
            Throwables.throwIfInstanceOf(e.getCause(), OrmException.class);
        }
        throw new OrmException(e);
    }
}
Also used : Repository(org.eclipse.jgit.lib.Repository) ObjectInserter(org.eclipse.jgit.lib.ObjectInserter) ConfigInvalidException(org.eclipse.jgit.errors.ConfigInvalidException) ObjectId(org.eclipse.jgit.lib.ObjectId) OrmException(com.google.gwtorm.server.OrmException) NoteMap(org.eclipse.jgit.notes.NoteMap) ExternalIdReader.readNoteMap(com.google.gerrit.server.account.externalids.ExternalIdReader.readNoteMap) IOException(java.io.IOException) RevWalk(org.eclipse.jgit.revwalk.RevWalk) ExecutionException(java.util.concurrent.ExecutionException) RetryException(com.github.rholder.retry.RetryException)

Example 3 with RetryException

use of com.github.rholder.retry.RetryException in project gerrit by GerritCodeReview.

the class PrimaryStorageMigrator method migrateToNoteDbPrimary.

/**
   * Migrate a change's primary storage from ReviewDb to NoteDb.
   *
   * <p>This method will return only if the primary storage of the change is NoteDb afterwards. (It
   * may return early if the primary storage was already NoteDb.)
   *
   * <p>If this method throws an exception, then the primary storage of the change is probably not
   * NoteDb. (It is possible that the primary storage of the change is NoteDb in this case, but
   * there was an error reading the state.) Moreover, after an exception, the change may be
   * read-only until a lease expires. If the caller chooses to retry, they should wait until the
   * read-only lease expires; this method will fail relatively quickly if called on a read-only
   * change.
   *
   * <p>Note that if the change is read-only after this method throws an exception, that does not
   * necessarily guarantee that the read-only lease was acquired during that particular method
   * invocation; this call may have in fact failed because another thread acquired the lease first.
   *
   * @param id change ID.
   * @throws OrmException if a ReviewDb-level error occurs.
   * @throws IOException if a repo-level error occurs.
   */
public void migrateToNoteDbPrimary(Change.Id id) throws OrmException, IOException {
    // Since there are multiple non-atomic steps in this method, we need to
    // consider what happens when there is another writer concurrent with the
    // thread executing this method.
    //
    // Let:
    // * OR = other writer writes noteDbState & new data to ReviewDb (in one
    //        transaction)
    // * ON = other writer writes to NoteDb
    // * MRO = migrator sets state to read-only
    // * MR = ensureRebuilt writes rebuilt noteDbState to ReviewDb (but does not
    //        otherwise update ReviewDb in this transaction)
    // * MN = ensureRebuilt writes rebuilt state to NoteDb
    //
    // Consider all the interleavings of these operations.
    //
    // * OR,ON,MRO,...
    //   Other writer completes before migrator begins; this is not a concurrent
    //   write.
    // * MRO,...,OR,...
    //   OR will fail, since it atomically checks that the noteDbState is not
    //   read-only before proceeding. This results in an exception, but not a
    //   concurrent write.
    //
    // Thus all the "interesting" interleavings start with OR,MRO, and differ on
    // where ON falls relative to MR/MN.
    //
    // * OR,MRO,ON,MR,MN
    //   The other NoteDb write succeeds despite the noteDbState being
    //   read-only. Because the read-only state from MRO includes the update
    //   from OR, the change is up-to-date at this point. Thus MR,MN is a no-op.
    //   The end result is an up-to-date, read-only change.
    //
    // * OR,MRO,MR,ON,MN
    //   The change is out-of-date when ensureRebuilt begins, because OR
    //   succeeded but the corresponding ON has not happened yet. ON will
    //   succeed, because there have been no intervening NoteDb writes. MN will
    //   fail, because ON updated the state in NoteDb to something other than
    //   what MR claimed. This leaves the change in an out-of-date, read-only
    //   state.
    //
    //   If this method threw an exception in this case, the change would
    //   eventually switch back to read-write when the read-only lease expires,
    //   so this situation is recoverable. However, it would be inconvenient for
    //   a change to be read-only for so long.
    //
    //   Thus, as an optimization, we have a retry loop that attempts
    //   ensureRebuilt while still holding the same read-only lease. This
    //   effectively results in the interleaving OR,MR,ON,MR,MN; in contrast
    //   with the previous case, here, MR/MN actually rebuilds the change. In
    //   the case of a write failure, MR/MN might fail and get retried again. If
    //   it exceeds the maximum number of retries, an exception is thrown.
    //
    // * OR,MRO,MR,MN,ON
    //   The change is out-of-date when ensureRebuilt begins. The change is
    //   rebuilt, leaving a new state in NoteDb. ON will fail, because the old
    //   NoteDb state has changed since the ref state was read when the update
    //   began (prior to OR). This results in an exception from ON, but the end
    //   result is still an up-to-date, read-only change. The end user that
    //   initiated the other write observes an error, but this is no different
    //   from other errors that need retrying, e.g. due to a backend write
    //   failure.
    Stopwatch sw = Stopwatch.createStarted();
    // MRO
    Change readOnlyChange = setReadOnlyInReviewDb(id);
    if (readOnlyChange == null) {
        // Already migrated.
        return;
    }
    NoteDbChangeState rebuiltState;
    try {
        // MR,MN
        rebuiltState = ensureRebuiltRetryer(sw).call(() -> ensureRebuilt(readOnlyChange.getProject(), id, NoteDbChangeState.parse(readOnlyChange)));
    } catch (RetryException | ExecutionException e) {
        throw new OrmException(e);
    }
    // At this point, the noteDbState in ReviewDb is read-only, and it is
    // guaranteed to match the state actually in NoteDb. Now it is safe to set
    // the primary storage to NoteDb.
    setPrimaryStorageNoteDb(id, rebuiltState);
    log.info("Migrated change {} to NoteDb primary in {}ms", id, sw.elapsed(MILLISECONDS));
}
Also used : OrmException(com.google.gwtorm.server.OrmException) Stopwatch(com.google.common.base.Stopwatch) Change(com.google.gerrit.reviewdb.client.Change) RetryException(com.github.rholder.retry.RetryException) ExecutionException(java.util.concurrent.ExecutionException)

Example 4 with RetryException

use of com.github.rholder.retry.RetryException in project gerrit by GerritCodeReview.

the class RepoSequence method acquire.

private void acquire(int count) throws OrmException {
    try (Repository repo = repoManager.openRepository(projectName);
        RevWalk rw = new RevWalk(repo)) {
        TryAcquire attempt = new TryAcquire(repo, rw, count);
        checkResult(retryer.call(attempt));
        counter = attempt.next;
        limit = counter + count;
        acquireCount++;
    } catch (ExecutionException | RetryException e) {
        if (e.getCause() != null) {
            Throwables.throwIfInstanceOf(e.getCause(), OrmException.class);
        }
        throw new OrmException(e);
    } catch (IOException e) {
        throw new OrmException(e);
    }
}
Also used : Repository(org.eclipse.jgit.lib.Repository) OrmException(com.google.gwtorm.server.OrmException) IOException(java.io.IOException) RevWalk(org.eclipse.jgit.revwalk.RevWalk) ExecutionException(java.util.concurrent.ExecutionException) RetryException(com.github.rholder.retry.RetryException)

Aggregations

RetryException (com.github.rholder.retry.RetryException)4 ExecutionException (java.util.concurrent.ExecutionException)4 OrmException (com.google.gwtorm.server.OrmException)3 IOException (java.io.IOException)2 Repository (org.eclipse.jgit.lib.Repository)2 RevWalk (org.eclipse.jgit.revwalk.RevWalk)2 Stopwatch (com.google.common.base.Stopwatch)1 Change (com.google.gerrit.reviewdb.client.Change)1 ExternalIdReader.readNoteMap (com.google.gerrit.server.account.externalids.ExternalIdReader.readNoteMap)1 DuplicateKeyException (com.mongodb.DuplicateKeyException)1 SimpleSession (org.apache.shiro.session.mgt.SimpleSession)1 ConfigInvalidException (org.eclipse.jgit.errors.ConfigInvalidException)1 ObjectId (org.eclipse.jgit.lib.ObjectId)1 ObjectInserter (org.eclipse.jgit.lib.ObjectInserter)1 NoteMap (org.eclipse.jgit.notes.NoteMap)1