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());
}
}
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);
}
}
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));
}
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);
}
}
Aggregations