Line data Source code
1 : // Copyright (C) 2019 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.notedb; 16 : 17 : import static com.google.common.base.MoreObjects.firstNonNull; 18 : import static com.google.common.base.Preconditions.checkState; 19 : 20 : import com.google.common.collect.Iterables; 21 : import com.google.common.collect.ListMultimap; 22 : import com.google.common.collect.MultimapBuilder; 23 : import com.google.common.flogger.FluentLogger; 24 : import com.google.gerrit.git.RefUpdateUtil; 25 : import com.google.gerrit.server.FanOutExecutor; 26 : import com.google.gerrit.server.config.AllUsersName; 27 : import com.google.gerrit.server.git.GitRepositoryManager; 28 : import com.google.inject.Inject; 29 : import java.io.IOException; 30 : import java.util.Map; 31 : import java.util.concurrent.ExecutorService; 32 : import java.util.concurrent.Future; 33 : import org.eclipse.jgit.lib.BatchRefUpdate; 34 : import org.eclipse.jgit.lib.PersonIdent; 35 : import org.eclipse.jgit.transport.PushCertificate; 36 : 37 : /** 38 : * Performs an update on {@code All-Users} asynchronously if required. No-op in case no updates were 39 : * scheduled for asynchronous execution. 40 : */ 41 : public class AllUsersAsyncUpdate { 42 110 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 43 : 44 : private final ExecutorService executor; 45 : private final AllUsersName allUsersName; 46 : private final GitRepositoryManager repoManager; 47 : private final ListMultimap<String, ChangeDraftUpdate> draftUpdates; 48 : 49 : private PersonIdent serverIdent; 50 : 51 : @Inject 52 : AllUsersAsyncUpdate( 53 : @FanOutExecutor ExecutorService executor, 54 : AllUsersName allUsersName, 55 110 : GitRepositoryManager repoManager) { 56 110 : this.executor = executor; 57 110 : this.allUsersName = allUsersName; 58 110 : this.repoManager = repoManager; 59 110 : this.draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build(); 60 110 : } 61 : 62 : void setDraftUpdates(ListMultimap<String, ChangeDraftUpdate> draftUpdates) { 63 26 : checkState(isEmpty(), "attempted to set draft comment updates for async execution twice"); 64 26 : boolean allPublishOnly = 65 26 : draftUpdates.values().stream().allMatch(ChangeDraftUpdate::canRunAsync); 66 26 : checkState(allPublishOnly, "not all updates can be run asynchronously"); 67 : // Add deep copies to avoid any threading issues. 68 26 : for (Map.Entry<String, ChangeDraftUpdate> entry : draftUpdates.entries()) { 69 26 : this.draftUpdates.put(entry.getKey(), entry.getValue().copy()); 70 26 : } 71 26 : if (draftUpdates.size() > 0) { 72 : // Save the PersonIdent for later so that we get consistent time stamps in the commit and ref 73 : // log. 74 26 : serverIdent = Iterables.get(draftUpdates.entries(), 0).getValue().serverIdent; 75 : } 76 26 : } 77 : 78 : /** Returns true if no operations should be performed on the repo. */ 79 : boolean isEmpty() { 80 110 : return draftUpdates.isEmpty(); 81 : } 82 : 83 : /** Executes repository update asynchronously. No-op in case no updates were scheduled. */ 84 : void execute(PersonIdent refLogIdent, String refLogMessage, PushCertificate pushCert) { 85 109 : if (isEmpty()) { 86 109 : return; 87 : } 88 : 89 : @SuppressWarnings("unused") 90 26 : Future<?> possiblyIgnoredError = 91 26 : executor.submit( 92 : () -> { 93 26 : try (OpenRepo allUsersRepo = OpenRepo.open(repoManager, allUsersName)) { 94 26 : allUsersRepo.addUpdatesNoLimits(draftUpdates); 95 26 : allUsersRepo.flush(); 96 26 : BatchRefUpdate bru = allUsersRepo.repo.getRefDatabase().newBatchUpdate(); 97 26 : bru.setPushCertificate(pushCert); 98 26 : if (refLogMessage != null) { 99 4 : bru.setRefLogMessage(refLogMessage, false); 100 : } else { 101 23 : bru.setRefLogMessage( 102 23 : firstNonNull(NoteDbUtil.guessRestApiHandler(), "Update NoteDb refs async"), 103 : false); 104 : } 105 26 : bru.setRefLogIdent(refLogIdent != null ? refLogIdent : serverIdent); 106 26 : bru.setAtomic(true); 107 26 : allUsersRepo.cmds.addTo(bru); 108 26 : bru.setAllowNonFastForwards(true); 109 26 : RefUpdateUtil.executeChecked(bru, allUsersRepo.rw); 110 0 : } catch (IOException e) { 111 0 : logger.atSevere().withCause(e).log( 112 : "Failed to delete draft comments asynchronously after publishing them"); 113 26 : } 114 26 : }); 115 26 : } 116 : }