Browse Source
When running an automatic GC on a FileRepository, when the caller passes a NullProgressMonitor, run the GC in a background thread. Use a thread pool of size 1 to limit the number of background threads spawned for background gc in the same application. In the next minor release we can make the thread pool configurable. In some cases, the auto GC limit is lower than the true number of unreachable loose objects, so auto GC will run after every (e.g) fetch operation. This leads to the appearance of poor fetch performance. Since these GCs will never make progress (until either the objects become referenced, or the two week timeout expires), blocking on them simply reduces throughput. In the event that an auto GC would make progress, it's still OK if it runs in the background. The progress will still happen. This matches the behavior of regular git. Git (and now jgit) uses the lock file for gc.log to prevent simultaneous runs of background gc. Further, it writes errors to gc.log, and won't run background gc if that file is present and recent. If gc.log is too old (according to the config gc.logexpiry), it will be ignored. Change-Id: I3870cadb4a0a6763feff252e6eaef99f4aa8d0df Signed-off-by: David Turner <dturner@twosigma.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>stable-4.7
David Turner
8 years ago
committed by
Matthias Sohn
9 changed files with 320 additions and 2 deletions
@ -0,0 +1,191 @@
|
||||
/* |
||||
* Copyright (C) 2017 Two Sigma Open Source |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.internal.storage.file; |
||||
|
||||
import org.eclipse.jgit.api.errors.JGitInternalException; |
||||
import org.eclipse.jgit.internal.JGitText; |
||||
import org.eclipse.jgit.lib.ConfigConstants; |
||||
import org.eclipse.jgit.util.GitDateParser; |
||||
import org.eclipse.jgit.util.SystemReader; |
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8; |
||||
|
||||
import java.io.BufferedReader; |
||||
import java.io.File; |
||||
import java.io.IOException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.NoSuchFileException; |
||||
import java.nio.file.attribute.FileTime; |
||||
import java.text.MessageFormat; |
||||
import java.text.ParseException; |
||||
import java.time.Instant; |
||||
|
||||
/** |
||||
* This class manages the gc.log file for a {@link FileRepository}. |
||||
*/ |
||||
class GcLog { |
||||
private final FileRepository repo; |
||||
|
||||
private final File logFile; |
||||
|
||||
private final LockFile lock; |
||||
|
||||
private Instant gcLogExpire; |
||||
|
||||
private static final String LOG_EXPIRY_DEFAULT = "1.day.ago"; //$NON-NLS-1$
|
||||
|
||||
private boolean nonEmpty = false; |
||||
|
||||
/** |
||||
* Construct a GcLog object for a {@link FileRepository} |
||||
* |
||||
* @param repo |
||||
* the repository |
||||
*/ |
||||
GcLog(FileRepository repo) { |
||||
this.repo = repo; |
||||
logFile = new File(repo.getDirectory(), "gc.log"); //$NON-NLS-1$
|
||||
lock = new LockFile(logFile); |
||||
} |
||||
|
||||
private Instant getLogExpiry() throws ParseException { |
||||
if (gcLogExpire == null) { |
||||
String logExpiryStr = repo.getConfig().getString( |
||||
ConfigConstants.CONFIG_GC_SECTION, null, |
||||
ConfigConstants.CONFIG_KEY_LOGEXPIRY); |
||||
if (logExpiryStr == null) { |
||||
logExpiryStr = LOG_EXPIRY_DEFAULT; |
||||
} |
||||
gcLogExpire = GitDateParser.parse(logExpiryStr, null, |
||||
SystemReader.getInstance().getLocale()).toInstant(); |
||||
} |
||||
return gcLogExpire; |
||||
} |
||||
|
||||
private boolean autoGcBlockedByOldLockFile(boolean background) { |
||||
try { |
||||
FileTime lastModified = Files.getLastModifiedTime(logFile.toPath()); |
||||
if (lastModified.toInstant().compareTo(getLogExpiry()) > 0) { |
||||
// There is an existing log file, which is too recent to ignore
|
||||
if (!background) { |
||||
try (BufferedReader reader = Files |
||||
.newBufferedReader(logFile.toPath())) { |
||||
char[] buf = new char[1000]; |
||||
int len = reader.read(buf, 0, 1000); |
||||
String oldError = new String(buf, 0, len); |
||||
|
||||
throw new JGitInternalException(MessageFormat.format( |
||||
JGitText.get().gcLogExists, oldError, logFile)); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
} catch (NoSuchFileException e) { |
||||
// No existing log file, OK.
|
||||
} catch (IOException | ParseException e) { |
||||
throw new JGitInternalException(e.getMessage(), e); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* Lock the GC log file for updates |
||||
* |
||||
* @param background |
||||
* If true, and if gc.log already exists, unlock and return false |
||||
* @return {@code true} if we hold the lock |
||||
*/ |
||||
boolean lock(boolean background) { |
||||
try { |
||||
if (!lock.lock()) { |
||||
return false; |
||||
} |
||||
} catch (IOException e) { |
||||
throw new JGitInternalException(e.getMessage(), e); |
||||
} |
||||
if (autoGcBlockedByOldLockFile(background)) { |
||||
lock.unlock(); |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Unlock (roll back) the GC log lock |
||||
*/ |
||||
void unlock() { |
||||
lock.unlock(); |
||||
} |
||||
|
||||
/** |
||||
* Commit changes to the gc log, if there have been any writes. Otherwise, |
||||
* just unlock and delete the existing file (if any) |
||||
* |
||||
* @return true if committing (or unlocking/deleting) succeeds. |
||||
*/ |
||||
boolean commit() { |
||||
if (nonEmpty) { |
||||
return lock.commit(); |
||||
} else { |
||||
logFile.delete(); |
||||
lock.unlock(); |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Write to the pending gc log. Content will be committed upon a call to |
||||
* commit() |
||||
* |
||||
* @param content |
||||
* The content to write |
||||
* @throws IOException |
||||
*/ |
||||
void write(String content) throws IOException { |
||||
if (content.length() > 0) { |
||||
nonEmpty = true; |
||||
} |
||||
lock.write(content.getBytes(UTF_8)); |
||||
} |
||||
} |
Loading…
Reference in new issue