| // Copyright (C) 2019 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.MoreObjects.firstNonNull; |
| import static com.google.common.base.Preconditions.checkState; |
| |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.ListMultimap; |
| import com.google.common.collect.MultimapBuilder; |
| import com.google.common.flogger.FluentLogger; |
| import com.google.gerrit.git.RefUpdateUtil; |
| import com.google.gerrit.server.FanOutExecutor; |
| import com.google.gerrit.server.config.AllUsersName; |
| import com.google.gerrit.server.git.GitRepositoryManager; |
| import com.google.inject.Inject; |
| import java.io.IOException; |
| import java.util.Map; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Future; |
| import org.eclipse.jgit.lib.BatchRefUpdate; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.transport.PushCertificate; |
| |
| /** |
| * Performs an update on {@code All-Users} asynchronously if required. No-op in case no updates were |
| * scheduled for asynchronous execution. |
| */ |
| public class AllUsersAsyncUpdate { |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| |
| private final ExecutorService executor; |
| private final AllUsersName allUsersName; |
| private final GitRepositoryManager repoManager; |
| private final ListMultimap<String, ChangeDraftUpdate> draftUpdates; |
| |
| private PersonIdent serverIdent; |
| |
| @Inject |
| AllUsersAsyncUpdate( |
| @FanOutExecutor ExecutorService executor, |
| AllUsersName allUsersName, |
| GitRepositoryManager repoManager) { |
| this.executor = executor; |
| this.allUsersName = allUsersName; |
| this.repoManager = repoManager; |
| this.draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build(); |
| } |
| |
| void setDraftUpdates(ListMultimap<String, ChangeDraftUpdate> draftUpdates) { |
| checkState(isEmpty(), "attempted to set draft comment updates for async execution twice"); |
| boolean allPublishOnly = |
| draftUpdates.values().stream().allMatch(ChangeDraftUpdate::canRunAsync); |
| checkState(allPublishOnly, "not all updates can be run asynchronously"); |
| // Add deep copies to avoid any threading issues. |
| for (Map.Entry<String, ChangeDraftUpdate> entry : draftUpdates.entries()) { |
| this.draftUpdates.put(entry.getKey(), entry.getValue().copy()); |
| } |
| if (draftUpdates.size() > 0) { |
| // Save the PersonIdent for later so that we get consistent time stamps in the commit and ref |
| // log. |
| serverIdent = Iterables.get(draftUpdates.entries(), 0).getValue().serverIdent; |
| } |
| } |
| |
| /** Returns true if no operations should be performed on the repo. */ |
| boolean isEmpty() { |
| return draftUpdates.isEmpty(); |
| } |
| |
| /** Executes repository update asynchronously. No-op in case no updates were scheduled. */ |
| void execute(PersonIdent refLogIdent, String refLogMessage, PushCertificate pushCert) { |
| if (isEmpty()) { |
| return; |
| } |
| |
| @SuppressWarnings("unused") |
| Future<?> possiblyIgnoredError = |
| executor.submit( |
| () -> { |
| try (OpenRepo allUsersRepo = OpenRepo.open(repoManager, allUsersName)) { |
| allUsersRepo.addUpdatesNoLimits(draftUpdates); |
| allUsersRepo.flush(); |
| BatchRefUpdate bru = allUsersRepo.repo.getRefDatabase().newBatchUpdate(); |
| bru.setPushCertificate(pushCert); |
| if (refLogMessage != null) { |
| bru.setRefLogMessage(refLogMessage, false); |
| } else { |
| bru.setRefLogMessage( |
| firstNonNull(NoteDbUtil.guessRestApiHandler(), "Update NoteDb refs async"), |
| false); |
| } |
| bru.setRefLogIdent(refLogIdent != null ? refLogIdent : serverIdent); |
| bru.setAtomic(true); |
| allUsersRepo.cmds.addTo(bru); |
| bru.setAllowNonFastForwards(true); |
| RefUpdateUtil.executeChecked(bru, allUsersRepo.rw); |
| } catch (IOException e) { |
| logger.atSevere().withCause(e).log( |
| "Failed to delete draft comments asynchronously after publishing them"); |
| } |
| }); |
| } |
| } |