summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java')
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java345
1 files changed, 0 insertions, 345 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java
deleted file mode 100644
index 11266f9318..0000000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java
+++ /dev/null
@@ -1,345 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.notedb;
-
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS;
-import static com.google.gerrit.reviewdb.client.RefNames.REFS_SEQUENCES;
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-
-import com.github.rholder.retry.RetryException;
-import com.github.rholder.retry.Retryer;
-import com.github.rholder.retry.RetryerBuilder;
-import com.github.rholder.retry.StopStrategies;
-import com.github.rholder.retry.WaitStrategies;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.CharMatcher;
-import com.google.common.base.Predicates;
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.primitives.Ints;
-import com.google.common.util.concurrent.Runnables;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.server.OrmException;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.transport.ReceiveCommand;
-
-/**
- * Class for managing an incrementing sequence backed by a git repository.
- *
- * <p>The current sequence number is stored as UTF-8 text in a blob pointed to by a ref in the
- * {@code refs/sequences/*} namespace. Multiple processes can share the same sequence by
- * incrementing the counter using normal git ref updates. To amortize the cost of these ref updates,
- * processes can increment the counter by a larger number and hand out numbers from that range in
- * memory until they run out. This means concurrent processes will hand out somewhat non-monotonic
- * numbers.
- */
-public class RepoSequence {
- @FunctionalInterface
- public interface Seed {
- int get() throws OrmException;
- }
-
- @VisibleForTesting
- static RetryerBuilder<RefUpdate.Result> retryerBuilder() {
- return RetryerBuilder.<RefUpdate.Result>newBuilder()
- .retryIfResult(Predicates.equalTo(RefUpdate.Result.LOCK_FAILURE))
- .withWaitStrategy(
- WaitStrategies.join(
- WaitStrategies.exponentialWait(5, TimeUnit.SECONDS),
- WaitStrategies.randomWait(50, TimeUnit.MILLISECONDS)))
- .withStopStrategy(StopStrategies.stopAfterDelay(30, TimeUnit.SECONDS));
- }
-
- private static final Retryer<RefUpdate.Result> RETRYER = retryerBuilder().build();
-
- private final GitRepositoryManager repoManager;
- private final GitReferenceUpdated gitRefUpdated;
- private final Project.NameKey projectName;
- private final String refName;
- private final Seed seed;
- private final int floor;
- private final int batchSize;
- private final Runnable afterReadRef;
- private final Retryer<RefUpdate.Result> retryer;
-
- // Protects all non-final fields.
- private final Lock counterLock;
-
- private int limit;
- private int counter;
-
- @VisibleForTesting int acquireCount;
-
- public RepoSequence(
- GitRepositoryManager repoManager,
- GitReferenceUpdated gitRefUpdated,
- Project.NameKey projectName,
- String name,
- Seed seed,
- int batchSize) {
- this(
- repoManager,
- gitRefUpdated,
- projectName,
- name,
- seed,
- batchSize,
- Runnables.doNothing(),
- RETRYER,
- 0);
- }
-
- public RepoSequence(
- GitRepositoryManager repoManager,
- GitReferenceUpdated gitRefUpdated,
- Project.NameKey projectName,
- String name,
- Seed seed,
- int batchSize,
- int floor) {
- this(
- repoManager,
- gitRefUpdated,
- projectName,
- name,
- seed,
- batchSize,
- Runnables.doNothing(),
- RETRYER,
- floor);
- }
-
- @VisibleForTesting
- RepoSequence(
- GitRepositoryManager repoManager,
- GitReferenceUpdated gitRefUpdated,
- Project.NameKey projectName,
- String name,
- Seed seed,
- int batchSize,
- Runnable afterReadRef,
- Retryer<RefUpdate.Result> retryer) {
- this(repoManager, gitRefUpdated, projectName, name, seed, batchSize, afterReadRef, retryer, 0);
- }
-
- RepoSequence(
- GitRepositoryManager repoManager,
- GitReferenceUpdated gitRefUpdated,
- Project.NameKey projectName,
- String name,
- Seed seed,
- int batchSize,
- Runnable afterReadRef,
- Retryer<RefUpdate.Result> retryer,
- int floor) {
- this.repoManager = checkNotNull(repoManager, "repoManager");
- this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
- this.projectName = checkNotNull(projectName, "projectName");
-
- checkArgument(
- name != null
- && !name.startsWith(REFS)
- && !name.startsWith(REFS_SEQUENCES.substring(REFS.length())),
- "name should be a suffix to follow \"refs/sequences/\", got: %s",
- name);
- this.refName = RefNames.REFS_SEQUENCES + name;
-
- this.seed = checkNotNull(seed, "seed");
- this.floor = floor;
-
- checkArgument(batchSize > 0, "expected batchSize > 0, got: %s", batchSize);
- this.batchSize = batchSize;
- this.afterReadRef = checkNotNull(afterReadRef, "afterReadRef");
- this.retryer = checkNotNull(retryer, "retryer");
-
- counterLock = new ReentrantLock(true);
- }
-
- public int next() throws OrmException {
- counterLock.lock();
- try {
- if (counter >= limit) {
- acquire(batchSize);
- }
- return counter++;
- } finally {
- counterLock.unlock();
- }
- }
-
- public ImmutableList<Integer> next(int count) throws OrmException {
- if (count == 0) {
- return ImmutableList.of();
- }
- checkArgument(count > 0, "count is negative: %s", count);
- counterLock.lock();
- try {
- List<Integer> ids = new ArrayList<>(count);
- while (counter < limit) {
- ids.add(counter++);
- if (ids.size() == count) {
- return ImmutableList.copyOf(ids);
- }
- }
- acquire(Math.max(count - ids.size(), batchSize));
- while (ids.size() < count) {
- ids.add(counter++);
- }
- return ImmutableList.copyOf(ids);
- } finally {
- counterLock.unlock();
- }
- }
-
- @VisibleForTesting
- public void set(int val) throws OrmException {
- // Don't bother spinning. This is only for tests, and a test that calls set
- // concurrently with other writes is doing it wrong.
- counterLock.lock();
- try {
- try (Repository repo = repoManager.openRepository(projectName);
- RevWalk rw = new RevWalk(repo)) {
- checkResult(store(repo, rw, null, val));
- counter = limit;
- } catch (IOException e) {
- throw new OrmException(e);
- }
- } finally {
- counterLock.unlock();
- }
- }
-
- 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);
- }
- }
-
- private void checkResult(RefUpdate.Result result) throws OrmException {
- if (!refUpdated(result)) {
- throw new OrmException("failed to update " + refName + ": " + result);
- }
- }
-
- private boolean refUpdated(RefUpdate.Result result) {
- return result == RefUpdate.Result.NEW || result == RefUpdate.Result.FORCED;
- }
-
- private class TryAcquire implements Callable<RefUpdate.Result> {
- private final Repository repo;
- private final RevWalk rw;
- private final int count;
-
- private int next;
-
- private TryAcquire(Repository repo, RevWalk rw, int count) {
- this.repo = repo;
- this.rw = rw;
- this.count = count;
- }
-
- @Override
- public RefUpdate.Result call() throws Exception {
- Ref ref = repo.exactRef(refName);
- int nextCandidate;
- afterReadRef.run();
- ObjectId oldId;
- if (ref == null) {
- oldId = ObjectId.zeroId();
- nextCandidate = seed.get();
- } else {
- oldId = ref.getObjectId();
- nextCandidate = parse(oldId);
- }
- next = Math.max(floor, nextCandidate);
- return store(repo, rw, oldId, next + count);
- }
-
- private int parse(ObjectId id) throws IOException, OrmException {
- ObjectLoader ol = rw.getObjectReader().open(id, OBJ_BLOB);
- if (ol.getType() != OBJ_BLOB) {
- // In theory this should be thrown by open but not all implementations
- // may do it properly (certainly InMemoryRepository doesn't).
- throw new IncorrectObjectTypeException(id, OBJ_BLOB);
- }
- String str = CharMatcher.whitespace().trimFrom(new String(ol.getCachedBytes(), UTF_8));
- Integer val = Ints.tryParse(str);
- if (val == null) {
- throw new OrmException("invalid value in " + refName + " blob at " + id.name());
- }
- return val;
- }
- }
-
- private RefUpdate.Result store(Repository repo, RevWalk rw, @Nullable ObjectId oldId, int val)
- throws IOException {
- ObjectId newId;
- try (ObjectInserter ins = repo.newObjectInserter()) {
- newId = ins.insert(OBJ_BLOB, Integer.toString(val).getBytes(UTF_8));
- ins.flush();
- }
- RefUpdate ru = repo.updateRef(refName);
- if (oldId != null) {
- ru.setExpectedOldObjectId(oldId);
- }
- ru.setNewObjectId(newId);
- ru.setForceUpdate(true); // Required for non-commitish updates.
- RefUpdate.Result result = ru.update(rw);
- if (refUpdated(result)) {
- gitRefUpdated.fire(projectName, ru, null);
- }
- return result;
- }
-
- public static ReceiveCommand storeNew(ObjectInserter ins, String name, int val)
- throws IOException {
- ObjectId newId = ins.insert(OBJ_BLOB, Integer.toString(val).getBytes(UTF_8));
- return new ReceiveCommand(ObjectId.zeroId(), newId, RefNames.REFS_SEQUENCES + name);
- }
-}