Merge branch 'stable-3.11' into stable-3.12
* stable-3.11:
Fix rollback implementation
Change-Id: I9a67583e25c36df84a80eb54d427a602c1995255
diff --git a/metrics.md b/metrics.md
index 4e53f30..0df0c24 100644
--- a/metrics.md
+++ b/metrics.md
@@ -17,4 +17,7 @@
: the latency in milliseconds of the isUpToDate operation.
* global_refdb/remove_latency
- : the latency in milliseconds of the remove operation.
\ No newline at end of file
+ : the latency in milliseconds of the remove operation.
+
+* global_refdb/operation_failures
+ : cumulative number of failures when attempting to perform an operation on global-refdb.
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDBMetrics.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDBMetrics.java
index c907831..9100a6e 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDBMetrics.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDBMetrics.java
@@ -14,6 +14,7 @@
package com.gerritforge.gerrit.globalrefdb.validation;
+import com.google.gerrit.metrics.Counter0;
import com.google.gerrit.metrics.Description;
import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.metrics.Timer0;
@@ -27,6 +28,7 @@
private final Timer0 lockRefExecutionTime;
private final Timer0 getOperationExecutionTime;
private final Timer0 existsExecutionTime;
+ private final Counter0 operationFailures;
private Timer0 compareAndPutExecutionTime;
private Timer0 setExecutionTime;
private Timer0 removeExecutionTime;
@@ -77,6 +79,13 @@
new Description("Time spent on checking in global ref-db if ref is up-to-date.")
.setCumulative()
.setUnit(Description.Units.MILLISECONDS));
+ operationFailures =
+ metricMaker.newCounter(
+ "global_refdb/operation_failures",
+ new Description(
+ "Number of failures when attempting to perform an operation on global-refdb.")
+ .setCumulative()
+ .setUnit("failures"));
}
public Context startCompareAndPutExecutionTime() {
@@ -106,4 +115,8 @@
public Context startIsUpToDateExecutionTime() {
return isUpToDateExecutionTime.start();
}
+
+ public void incrementOperationFailures() {
+ operationFailures.increment();
+ }
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapper.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapper.java
index fd5d546..6cc3be1 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapper.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapper.java
@@ -21,13 +21,16 @@
import com.gerritforge.gerrit.globalrefdb.RefDbLockException;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.NoopSharedRefDatabase;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Supplier;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.metrics.Timer0.Context;
import com.google.inject.Inject;
import com.google.inject.Singleton;
+import java.util.Arrays;
import java.util.Optional;
+import java.util.stream.Collectors;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -74,35 +77,63 @@
@Override
public boolean isUpToDate(Project.NameKey project, Ref ref) throws GlobalRefDbLockException {
- try (Context context = metrics.startIsUpToDateExecutionTime()) {
- return sharedRefDb().isUpToDate(project, ref);
- }
+ return trackFailingOperation(
+ () -> sharedRefDb().isUpToDate(project, ref),
+ metrics::startIsUpToDateExecutionTime,
+ () ->
+ toString(project, project::get) + ":" + toString(ref, ref::getName) + " is up-to-date");
}
/** {@inheritDoc}. The operation is logged upon success. */
@Override
public boolean compareAndPut(Project.NameKey project, Ref currRef, ObjectId newRefValue)
throws GlobalRefDbSystemError {
- try (Context context = metrics.startCompareAndPutExecutionTime()) {
- boolean succeeded = sharedRefDb().compareAndPut(project, currRef, newRefValue);
- if (succeeded) {
- sharedRefLogger.logRefUpdate(project.get(), currRef, newRefValue);
- }
- return succeeded;
- }
+
+ return trackFailingOperation(
+ () -> {
+ boolean succeeded = sharedRefDb().compareAndPut(project, currRef, newRefValue);
+ if (succeeded) {
+ sharedRefLogger.logRefUpdate(project.get(), currRef, newRefValue);
+ }
+ return succeeded;
+ },
+ metrics::startCompareAndPutExecutionTime,
+ () ->
+ "compare "
+ + toString(project, project::get)
+ + ":"
+ + toString(currRef, currRef::getName)
+ + ":"
+ + toString(
+ currRef,
+ () -> toString(currRef.getObjectId(), () -> currRef.getObjectId().name()))
+ + " and put "
+ + toString(newRefValue, newRefValue::name));
}
/** {@inheritDoc} the operation is logged upon success. */
@Override
public <T> boolean compareAndPut(Project.NameKey project, String refName, T currValue, T newValue)
throws GlobalRefDbSystemError {
- try (Context context = metrics.startCompareAndPutExecutionTime()) {
- boolean succeeded = sharedRefDb().compareAndPut(project, refName, currValue, newValue);
- if (succeeded) {
- sharedRefLogger.logRefUpdate(project.get(), refName, currValue, newValue);
- }
- return succeeded;
- }
+
+ return trackFailingOperation(
+ () -> {
+ boolean succeeded = sharedRefDb().compareAndPut(project, refName, currValue, newValue);
+ if (succeeded) {
+ sharedRefLogger.logRefUpdate(project.get(), refName, currValue, newValue);
+ }
+ return succeeded;
+ },
+ metrics::startCompareAndPutExecutionTime,
+ () ->
+ "compare "
+ + toString(project, project::get)
+ + ":"
+ + toString(refName)
+ + ":"
+ + toString(currValue)
+ + " and put "
+ + toString(newValue));
}
@Override
@@ -112,10 +143,21 @@
throw new UnsupportedOperationException(
"GlobalRefDb implementation doesn't support set operation");
}
- try (Context context = metrics.startSetExecutionTime()) {
- ((ExtendedGlobalRefDatabase) sharedRefDb()).put(project, refName, newValue);
- sharedRefLogger.logRefUpdate(project.get(), refName, newValue);
- }
+
+ trackFailingOperation(
+ () -> {
+ ((ExtendedGlobalRefDatabase) sharedRefDb()).put(project, refName, newValue);
+ sharedRefLogger.logRefUpdate(project.get(), refName, newValue);
+ return null;
+ },
+ metrics::startSetExecutionTime,
+ () ->
+ "Put "
+ + toString(project, project::get)
+ + ":"
+ + toString(refName)
+ + " = "
+ + toString(newValue));
}
public boolean isSetOperationSupported() {
@@ -126,14 +168,16 @@
@Override
public AutoCloseable lockRef(Project.NameKey project, String refName)
throws GlobalRefDbLockException {
- try (Context context = metrics.startLockRefExecutionTime()) {
- return new LockWrapper(
- sharedRefLogger,
- project.get(),
- refName,
- sharedRefDb().lockRef(project, refName),
- SharedRefLogger.Scope.GLOBAL);
- }
+ return trackFailingOperation(
+ () ->
+ new LockWrapper(
+ sharedRefLogger,
+ project.get(),
+ refName,
+ sharedRefDb().lockRef(project, refName),
+ SharedRefLogger.Scope.GLOBAL),
+ metrics::startLockRefExecutionTime,
+ () -> "Lock " + toString(project, project::get) + ":" + toString(refName));
}
public AutoCloseable lockLocalRef(Project.NameKey project, String refName)
@@ -148,26 +192,38 @@
@Override
public boolean exists(Project.NameKey project, String refName) {
- try (Context context = metrics.startExistsExecutionTime()) {
- return sharedRefDb().exists(project, refName);
- }
+ return trackFailingOperation(
+ () -> sharedRefDb().exists(project, refName),
+ metrics::startExistsExecutionTime,
+ () -> toString(project, project::get) + ":" + toString(refName) + " exists");
}
/** {@inheritDoc}. The operation is logged. */
@Override
public void remove(Project.NameKey project) throws GlobalRefDbSystemError {
- try (Context context = metrics.startRemoveExecutionTime()) {
- sharedRefDb().remove(project);
- sharedRefLogger.logProjectDelete(project.get());
- }
+ trackFailingOperation(
+ () -> {
+ sharedRefDb().remove(project);
+ sharedRefLogger.logProjectDelete(project.get());
+ return null;
+ },
+ metrics::startRemoveExecutionTime,
+ () -> "Remove " + toString(project, project::get));
}
@Override
public <T> Optional<T> get(Project.NameKey nameKey, String s, Class<T> clazz)
throws GlobalRefDbSystemError {
- try (Context context = metrics.startGetExecutionTime()) {
- return sharedRefDb().get(nameKey, s, clazz);
- }
+ return trackFailingOperation(
+ () -> sharedRefDb().get(nameKey, s, clazz),
+ metrics::startGetExecutionTime,
+ () ->
+ "Get "
+ + toString(nameKey, nameKey::get)
+ + ":"
+ + toString(s)
+ + " of type "
+ + clazz.getSimpleName());
}
boolean isNoop() {
@@ -187,4 +243,42 @@
return NOOP_REFDB;
});
}
+
+ @FunctionalInterface
+ private interface ThrowingSupplier<T, E extends Exception> {
+ T get() throws E;
+ }
+
+ private <T, E extends Exception> T trackFailingOperation(
+ ThrowingSupplier<T, E> operation,
+ Supplier<Context> metricTimer,
+ Supplier<String> operationDetails)
+ throws E {
+
+ boolean completedWithoutExceptions = false;
+ try (Context ignore = metricTimer.get()) {
+ T result = operation.get();
+ completedWithoutExceptions = true;
+ return result;
+ } finally {
+ if (!completedWithoutExceptions) {
+ String callStack =
+ Arrays.stream(Thread.currentThread().getStackTrace())
+ .skip(2) /* Skip the getStackTrace() and trackFailingOperation() call stacks */
+ .map(StackTraceElement::toString)
+ .collect(Collectors.joining("\n at "));
+ log.atWarning().log(
+ "Global-refdb operation '%s' failed\n%s", operationDetails.get(), callStack);
+ metrics.incrementOperationFailures();
+ }
+ }
+ }
+
+ private static <T> String toString(T value) {
+ return String.valueOf(value);
+ }
+
+ private static <O, T> String toString(O object, Supplier<T> valueSupplier) {
+ return toString(object == null ? null : valueSupplier.get());
+ }
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbExceptionHook.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbExceptionHook.java
index a7f2740..e0495d5 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbExceptionHook.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbExceptionHook.java
@@ -16,7 +16,7 @@
import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.Nullable;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.server.ExceptionHook;
import java.util.Optional;
@@ -36,12 +36,14 @@
}
@Override
- public ImmutableList<String> getUserMessages(Throwable throwable, @Nullable String traceId) {
+ public ImmutableList<String> getUserMessages(Throwable throwable, ImmutableSet<String> traceIds) {
if (throwable instanceof GlobalRefDbLockException) {
ImmutableList.Builder<String> builder = new ImmutableList.Builder<>();
builder.add(throwable.getMessage());
- if (traceId != null && !traceId.isBlank()) {
- builder.add(String.format("Trace ID: %s", traceId));
+ for (String traceId : traceIds) {
+ if (traceId != null && !traceId.isBlank()) {
+ builder.add(String.format("Trace ID: %s", traceId));
+ }
}
return builder.build();
}
diff --git a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefDatabase.java b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefDatabase.java
index 8b51beb..6085627 100644
--- a/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefDatabase.java
+++ b/src/main/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbRefDatabase.java
@@ -26,6 +26,7 @@
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.ReflogReader;
/**
* Wraps an instance of {@link RefDatabase} with the intent of wrapping {@link RefUpdate} operations
@@ -170,6 +171,11 @@
}
@Override
+ public ReflogReader getReflogReader(Ref ref) throws IOException {
+ return refDatabase.getReflogReader(ref);
+ }
+
+ @Override
public boolean hasRefs() throws IOException {
return refDatabase.hasRefs();
}
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidatorTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidatorTest.java
index 435db8f..01018f2 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidatorTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/RefUpdateValidatorTest.java
@@ -15,6 +15,7 @@
package com.gerritforge.gerrit.globalrefdb.validation;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doReturn;
@@ -249,6 +250,7 @@
Result result =
refUpdateValidator.executeRefUpdate(
refUpdate, () -> doLocalRefUpdate(localRef.getName()), rollbackFunction);
+ assertEquals(Result.LOCK_FAILURE, result);
verify(rollbackFunction).invoke(any());
}
@@ -276,7 +278,7 @@
.compareAndPut(any(Project.NameKey.class), any(Ref.class), any(ObjectId.class));
}
- private Result defaultRollback(ObjectId objectId) {
+ private Result defaultRollback(ObjectId unused) {
return Result.NO_CHANGE;
}
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapperTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapperTest.java
index ce1fcea..dda7d9f 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapperTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDatabaseWrapperTest.java
@@ -14,11 +14,16 @@
package com.gerritforge.gerrit.globalrefdb.validation;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDatabase;
import com.gerritforge.gerrit.globalrefdb.GlobalRefDbLockException;
+import com.gerritforge.gerrit.globalrefdb.GlobalRefDbSystemError;
+import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.NoopSharedRefDatabase;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.metrics.Timer0.Context;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -95,4 +100,32 @@
verify(metrics).startRemoveExecutionTime();
verify(context).close();
}
+
+ @Test
+ public void shouldIncreaseNumberOfFailuresWhenCompareAndPutThrows() throws Exception {
+ DynamicItem<GlobalRefDatabase> couldNotConnectGlobalRefDB =
+ DynamicItem.itemOf(
+ GlobalRefDatabase.class,
+ new NoopSharedRefDatabase() {
+ @Override
+ public boolean compareAndPut(
+ Project.NameKey project, Ref currRef, ObjectId newRefValue)
+ throws GlobalRefDbSystemError {
+ throw new GlobalRefDbSystemError(
+ "Could not write to global-refdb", new Exception("Could not connect"));
+ }
+ });
+ objectUnderTest =
+ new SharedRefDatabaseWrapper(
+ couldNotConnectGlobalRefDB,
+ new DisabledSharedRefLogger(),
+ metrics,
+ NoOpRefLocker.INSTANCE);
+
+ assertThrows(
+ GlobalRefDbSystemError.class,
+ () -> objectUnderTest.compareAndPut(projectName, ref, ObjectId.zeroId()));
+
+ verify(metrics).incrementOperationFailures();
+ }
}
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdateTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdateTest.java
index e463cb5..4c6290d 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdateTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/SharedRefDbBatchRefUpdateTest.java
@@ -31,6 +31,7 @@
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.RefFixture;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.ObjectId;
@@ -101,7 +102,6 @@
when(projectsFilter.matches(anyString())).thenReturn(true);
}
- @SuppressWarnings("deprecation")
private void setMockRequiredReturnValues() throws IOException {
doReturn(batchRefUpdate).when(refDatabase).newBatchUpdate();
@@ -114,7 +114,6 @@
// and not supposed to be used if all tests are successful.
lenient().when(batchRefUpdate.addCommand(any(List.class))).thenReturn(batchRefUpdate);
- lenient().when(refDatabase.getRef(A_TEST_REF_NAME)).thenReturn(oldRef).thenReturn(newRef);
lenient().when(refDatabase.exactRef(A_TEST_REF_NAME)).thenReturn(oldRef).thenReturn(newRef);
lenient().when(refDatabase.findRef(A_TEST_REF_NAME)).thenReturn(oldRef).thenReturn(newRef);
@@ -132,7 +131,7 @@
doReturn(true)
.when(sharedRefDb)
.compareAndPut(eq(A_TEST_PROJECT_NAME_KEY), refEquals(oldRef), eq(newRef.getObjectId()));
- sharedRefDbRefUpdate.execute(revWalk, progressMonitor, EMPTY_LIST);
+ sharedRefDbRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
verify(sharedRefDb)
.compareAndPut(eq(A_TEST_PROJECT_NAME_KEY), refEquals(oldRef), eq(newRef.getObjectId()));
}
@@ -148,7 +147,7 @@
.when(batchRefUpdateValidator)
.executeBatchUpdateWithValidation(any(), any(), any());
- sharedRefDbRefUpdate.execute(revWalk, progressMonitor, EMPTY_LIST);
+ sharedRefDbRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
}
@Test
@@ -157,7 +156,7 @@
doReturn(true).when(sharedRefDb).exists(A_TEST_PROJECT_NAME_KEY, A_TEST_REF_NAME);
doReturn(false).when(sharedRefDb).isUpToDate(A_TEST_PROJECT_NAME_KEY, oldRef);
- sharedRefDbRefUpdate.execute(revWalk, progressMonitor, EMPTY_LIST);
+ sharedRefDbRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
verify(validationMetrics).incrementSplitBrainPrevention();
}
@@ -169,7 +168,7 @@
sharedRefDbRefUpdate = getSharedRefDbBatchRefUpdateWithDefaultPolicyEnforcement();
- sharedRefDbRefUpdate.execute(revWalk, progressMonitor, EMPTY_LIST);
+ sharedRefDbRefUpdate.execute(revWalk, progressMonitor, Collections.emptyList());
}
private SharedRefDbBatchRefUpdate getSharedRefDbBatchRefUpdateWithDefaultPolicyEnforcement() {
diff --git a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationModuleTest.java b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationModuleTest.java
index 3283994..1547bd3 100644
--- a/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationModuleTest.java
+++ b/src/test/java/com/gerritforge/gerrit/globalrefdb/validation/ValidationModuleTest.java
@@ -14,8 +14,6 @@
package com.gerritforge.gerrit.globalrefdb.validation;
-import static java.util.Collections.EMPTY_SET;
-
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.DefaultSharedRefEnforcement;
import com.gerritforge.gerrit.globalrefdb.validation.dfsrefdb.SharedRefEnforcement;
import com.google.common.collect.ImmutableSet;
@@ -25,6 +23,7 @@
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.inject.*;
import com.google.inject.name.Names;
+import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
@@ -61,7 +60,7 @@
public static class ValidationModuleWithEmptyIgnoredRefs extends ValidationModule {
@Inject
public ValidationModuleWithEmptyIgnoredRefs(@GerritServerConfig Config config) {
- super(config, Optional.of(EMPTY_SET));
+ super(config, Optional.of(Collections.emptySet()));
}
}