The search for reuse phase for *all* the objects scans *all* the packfiles, looking for the best candidate to serve back to the client. This can lead to an expensive operation when the number of packfiles and objects is high. Add parameter "pack.searchForReuseTimeout" to limit the time spent on this search. Change-Id: I54f5cddb6796fdc93ad9585c2ab4b44854fa6c48changes/55/181355/20
| `prunePreserved`, only via API of PackConfig | `false` | ⃞ | Whether to remove preserved pack files in a preserved directory. | | | `prunePreserved`, only via API of PackConfig | `false` | ⃞ | Whether to remove preserved pack files in a preserved directory. | | ||||
| `pack.reuseDeltas` | `true` |⃞ | Whether to reuse deltas existing in repository. | | | `pack.reuseDeltas` | `true` |⃞ | Whether to reuse deltas existing in repository. | | ||||
| `pack.reuseObjects` | `true` | ⃞ | Whether to reuse existing objects representation in repository. | | | `pack.reuseObjects` | `true` | ⃞ | Whether to reuse existing objects representation in repository. | | ||||
| `pack.searchForReuseTimeout` | | ⃞ | Search for reuse phase timeout. Expressed as a `Duration`, i.e.: `50sec`. | | |||||
| `pack.singlePack` | `false` | ⃞ | Whether all of `refs/*` should be packed in a single pack. | | | `pack.singlePack` | `false` | ⃞ | Whether all of `refs/*` should be packed in a single pack. | | ||||
| `pack.threads` | `0` (auto-detect number of processors) | ✅ | Number of threads to use for delta compression. | | | `pack.threads` | `0` (auto-detect number of processors) | ✅ | Number of threads to use for delta compression. | | ||||
| `pack.waitPreventRacyPack` | `false` | ⃞ | Whether we wait before opening a newly written pack to prevent its lastModified timestamp could be racy. | | | `pack.waitPreventRacyPack` | `false` | ⃞ | Whether we wait before opening a newly written pack to prevent its lastModified timestamp could be racy. | |
import static org.junit.Assert.assertNotNull; | import static org.junit.Assert.assertNotNull; | ||||
import static org.junit.Assert.assertTrue; | import static org.junit.Assert.assertTrue; | ||||
import static org.junit.Assert.fail; | import static org.junit.Assert.fail; | ||||
import static org.mockito.ArgumentMatchers.any; | |||||
import static org.mockito.Mockito.doNothing; | |||||
import static org.mockito.Mockito.times; | |||||
import static org.mockito.Mockito.verify; | |||||
import java.io.ByteArrayInputStream; | import java.io.ByteArrayInputStream; | ||||
import java.io.ByteArrayOutputStream; | import java.io.ByteArrayOutputStream; | ||||
import java.io.FileOutputStream; | import java.io.FileOutputStream; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.text.ParseException; | import java.text.ParseException; | ||||
import java.time.Duration; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Set; | import java.util.Set; | ||||
import org.eclipse.jgit.api.Git; | |||||
import org.eclipse.jgit.errors.MissingObjectException; | import org.eclipse.jgit.errors.MissingObjectException; | ||||
import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; | import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry; | ||||
import org.eclipse.jgit.internal.storage.pack.PackExt; | import org.eclipse.jgit.internal.storage.pack.PackExt; | ||||
import org.eclipse.jgit.lib.ObjectId; | import org.eclipse.jgit.lib.ObjectId; | ||||
import org.eclipse.jgit.lib.ObjectIdSet; | import org.eclipse.jgit.lib.ObjectIdSet; | ||||
import org.eclipse.jgit.lib.ObjectInserter; | import org.eclipse.jgit.lib.ObjectInserter; | ||||
import org.eclipse.jgit.lib.Ref; | |||||
import org.eclipse.jgit.lib.Repository; | import org.eclipse.jgit.lib.Repository; | ||||
import org.eclipse.jgit.lib.Sets; | import org.eclipse.jgit.lib.Sets; | ||||
import org.eclipse.jgit.revwalk.DepthWalk; | import org.eclipse.jgit.revwalk.DepthWalk; | ||||
import org.junit.After; | import org.junit.After; | ||||
import org.junit.Before; | import org.junit.Before; | ||||
import org.junit.Test; | import org.junit.Test; | ||||
import org.mockito.Mockito; | |||||
public class PackWriterTest extends SampleDataRepositoryTestCase { | public class PackWriterTest extends SampleDataRepositoryTestCase { | ||||
} | } | ||||
} | } | ||||
@Test | |||||
public void testTotalPackFilesScanWhenSearchForReuseTimeoutNotSet() | |||||
throws Exception { | |||||
FileRepository fileRepository = setUpRepoWithMultiplePackfiles(); | |||||
PackWriter mockedPackWriter = Mockito | |||||
.spy(new PackWriter(config, fileRepository.newObjectReader())); | |||||
doNothing().when(mockedPackWriter).select(any(), any()); | |||||
try (FileOutputStream packOS = new FileOutputStream( | |||||
getPackFileToWrite(fileRepository, mockedPackWriter))) { | |||||
mockedPackWriter.writePack(NullProgressMonitor.INSTANCE, | |||||
NullProgressMonitor.INSTANCE, packOS); | |||||
} | |||||
long numberOfPackFiles = new GC(fileRepository) | |||||
.getStatistics().numberOfPackFiles; | |||||
int expectedSelectCalls = | |||||
// Objects contained in multiple packfiles * number of packfiles | |||||
2 * (int) numberOfPackFiles + | |||||
// Objects in single packfile | |||||
1; | |||||
verify(mockedPackWriter, times(expectedSelectCalls)).select(any(), | |||||
any()); | |||||
} | |||||
@Test | |||||
public void testTotalPackFilesScanWhenSkippingSearchForReuseTimeoutCheck() | |||||
throws Exception { | |||||
FileRepository fileRepository = setUpRepoWithMultiplePackfiles(); | |||||
PackConfig packConfig = new PackConfig(); | |||||
packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1)); | |||||
PackWriter mockedPackWriter = Mockito.spy( | |||||
new PackWriter(packConfig, fileRepository.newObjectReader())); | |||||
doNothing().when(mockedPackWriter).select(any(), any()); | |||||
try (FileOutputStream packOS = new FileOutputStream( | |||||
getPackFileToWrite(fileRepository, mockedPackWriter))) { | |||||
mockedPackWriter.writePack(NullProgressMonitor.INSTANCE, | |||||
NullProgressMonitor.INSTANCE, packOS); | |||||
} | |||||
long numberOfPackFiles = new GC(fileRepository) | |||||
.getStatistics().numberOfPackFiles; | |||||
int expectedSelectCalls = | |||||
// Objects contained in multiple packfiles * number of packfiles | |||||
2 * (int) numberOfPackFiles + | |||||
// Objects contained in single packfile | |||||
1; | |||||
verify(mockedPackWriter, times(expectedSelectCalls)).select(any(), | |||||
any()); | |||||
} | |||||
@Test | |||||
public void testPartialPackFilesScanWhenDoingSearchForReuseTimeoutCheck() | |||||
throws Exception { | |||||
FileRepository fileRepository = setUpRepoWithMultiplePackfiles(); | |||||
PackConfig packConfig = new PackConfig(); | |||||
packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1)); | |||||
PackWriter mockedPackWriter = Mockito.spy( | |||||
new PackWriter(packConfig, fileRepository.newObjectReader())); | |||||
mockedPackWriter.enableSearchForReuseTimeout(); | |||||
doNothing().when(mockedPackWriter).select(any(), any()); | |||||
try (FileOutputStream packOS = new FileOutputStream( | |||||
getPackFileToWrite(fileRepository, mockedPackWriter))) { | |||||
mockedPackWriter.writePack(NullProgressMonitor.INSTANCE, | |||||
NullProgressMonitor.INSTANCE, packOS); | |||||
} | |||||
int expectedSelectCalls = 3; // Objects in packfiles | |||||
verify(mockedPackWriter, times(expectedSelectCalls)).select(any(), | |||||
any()); | |||||
} | |||||
/** | |||||
* Creates objects and packfiles in the following order: | |||||
* <ul> | |||||
* <li>Creates 2 objects (C1 = commit, T1 = tree) | |||||
* <li>Creates packfile P1 (containing C1, T1) | |||||
* <li>Creates 1 object (C2 commit) | |||||
* <li>Creates packfile P2 (containing C1, T1, C2) | |||||
* <li>Create 1 object (C3 commit) | |||||
* </ul> | |||||
* | |||||
* @throws Exception | |||||
*/ | |||||
private FileRepository setUpRepoWithMultiplePackfiles() throws Exception { | |||||
FileRepository fileRepository = createWorkRepository(); | |||||
try (Git git = new Git(fileRepository)) { | |||||
// Creates 2 objects (C1 = commit, T1 = tree) | |||||
git.commit().setMessage("First commit").call(); | |||||
GC gc = new GC(fileRepository); | |||||
gc.setPackExpireAgeMillis(Long.MAX_VALUE); | |||||
gc.setExpireAgeMillis(Long.MAX_VALUE); | |||||
// Creates packfile P1 (containing C1, T1) | |||||
gc.gc(); | |||||
// Creates 1 object (C2 commit) | |||||
git.commit().setMessage("Second commit").call(); | |||||
// Creates packfile P2 (containing C1, T1, C2) | |||||
gc.gc(); | |||||
// Create 1 object (C3 commit) | |||||
git.commit().setMessage("Third commit").call(); | |||||
} | |||||
return fileRepository; | |||||
} | |||||
private PackFile getPackFileToWrite(FileRepository fileRepository, | |||||
PackWriter mockedPackWriter) throws IOException { | |||||
File packdir = fileRepository.getObjectDatabase().getPackDirectory(); | |||||
PackFile packFile = new PackFile(packdir, | |||||
mockedPackWriter.computeName(), PackExt.PACK); | |||||
Set<ObjectId> all = new HashSet<>(); | |||||
for (Ref r : fileRepository.getRefDatabase().getRefs()) { | |||||
all.add(r.getObjectId()); | |||||
} | |||||
mockedPackWriter.preparePack(NullProgressMonitor.INSTANCE, all, | |||||
PackWriter.NONE); | |||||
return packFile; | |||||
} | |||||
private FileRepository setupRepoForShallowFetch() throws Exception { | private FileRepository setupRepoForShallowFetch() throws Exception { | ||||
FileRepository repo = createBareRepository(); | FileRepository repo = createBareRepository(); | ||||
// TestRepository will close the repo, but we need to return an open | // TestRepository will close the repo, but we need to return an open |
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
<component id="org.eclipse.jgit" version="2"> | <component id="org.eclipse.jgit" version="2"> | ||||
<resource path="src/org/eclipse/jgit/storage/pack/PackConfig.java" type="org.eclipse.jgit.storage.pack.PackConfig"> | |||||
<filter id="336658481"> | |||||
<message_arguments> | |||||
<message_argument value="org.eclipse.jgit.storage.pack.PackConfig"/> | |||||
<message_argument value="DEFAULT_SEARCH_FOR_REUSE_TIMEOUT"/> | |||||
</message_arguments> | |||||
</filter> | |||||
</resource> | |||||
<resource path="src/org/eclipse/jgit/transport/SshConstants.java" type="org.eclipse.jgit.transport.SshConstants"> | <resource path="src/org/eclipse/jgit/transport/SshConstants.java" type="org.eclipse.jgit.transport.SshConstants"> | ||||
<filter id="1142947843"> | <filter id="1142947843"> | ||||
<message_arguments> | <message_arguments> |
searchForReachableBranches=Finding reachable branches | searchForReachableBranches=Finding reachable branches | ||||
saveFileStoreAttributesFailed=Saving measured FileStore attributes to user config failed | saveFileStoreAttributesFailed=Saving measured FileStore attributes to user config failed | ||||
searchForReuse=Finding sources | searchForReuse=Finding sources | ||||
searchForReuseTimeout=Search for reuse timed out after {0} seconds | |||||
searchForSizes=Getting sizes | searchForSizes=Getting sizes | ||||
secondsAgo={0} seconds ago | secondsAgo={0} seconds ago | ||||
selectingCommits=Selecting commits | selectingCommits=Selecting commits |
/* | |||||
* Copyright (C) 2021, Fabio Ponciroli <ponch@gerritforge.com> | |||||
* | |||||
* This program and the accompanying materials are made available under the | |||||
* terms of the Eclipse Distribution License v. 1.0 which is available at | |||||
* https://www.eclipse.org/org/documents/edl-v10.php. | |||||
* | |||||
* SPDX-License-Identifier: BSD-3-Clause | |||||
*/ | |||||
package org.eclipse.jgit.errors; | |||||
import org.eclipse.jgit.internal.JGitText; | |||||
import java.io.IOException; | |||||
import java.text.MessageFormat; | |||||
import java.time.Duration; | |||||
/** | |||||
* Thrown when the search for reuse phase times out. | |||||
* | |||||
* @since 5.13 | |||||
*/ | |||||
public class SearchForReuseTimeout extends IOException { | |||||
private static final long serialVersionUID = 1L; | |||||
/** | |||||
* Construct a search for reuse timeout error. | |||||
* | |||||
* @param timeout | |||||
* time exceeded during the search for reuse phase. | |||||
*/ | |||||
public SearchForReuseTimeout(Duration timeout) { | |||||
super(MessageFormat.format(JGitText.get().searchForReuseTimeout, | |||||
Long.valueOf(timeout.getSeconds()))); | |||||
} | |||||
@Override | |||||
public synchronized Throwable fillInStackTrace() { | |||||
return this; | |||||
} | |||||
} |
/***/ public String saveFileStoreAttributesFailed; | /***/ public String saveFileStoreAttributesFailed; | ||||
/***/ public String searchForReachableBranches; | /***/ public String searchForReachableBranches; | ||||
/***/ public String searchForReuse; | /***/ public String searchForReuse; | ||||
/***/ public String searchForReuseTimeout; | |||||
/***/ public String searchForSizes; | /***/ public String searchForSizes; | ||||
/***/ public String secondsAgo; | /***/ public String secondsAgo; | ||||
/***/ public String selectingCommits; | /***/ public String selectingCommits; |
import org.eclipse.jgit.errors.CorruptObjectException; | import org.eclipse.jgit.errors.CorruptObjectException; | ||||
import org.eclipse.jgit.errors.PackInvalidException; | import org.eclipse.jgit.errors.PackInvalidException; | ||||
import org.eclipse.jgit.errors.PackMismatchException; | import org.eclipse.jgit.errors.PackMismatchException; | ||||
import org.eclipse.jgit.errors.SearchForReuseTimeout; | |||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
import org.eclipse.jgit.internal.storage.pack.ObjectToPack; | import org.eclipse.jgit.internal.storage.pack.ObjectToPack; | ||||
import org.eclipse.jgit.internal.storage.pack.PackExt; | import org.eclipse.jgit.internal.storage.pack.PackExt; | ||||
p.resetTransientErrorCount(); | p.resetTransientErrorCount(); | ||||
if (rep != null) { | if (rep != null) { | ||||
packer.select(otp, rep); | packer.select(otp, rep); | ||||
packer.checkSearchForReuseTimeout(); | |||||
} | } | ||||
} catch (SearchForReuseTimeout e) { | |||||
break SEARCH; | |||||
} catch (PackMismatchException e) { | } catch (PackMismatchException e) { | ||||
// Pack was modified; refresh the entire pack list. | // Pack was modified; refresh the entire pack list. | ||||
// | // |
import java.lang.ref.WeakReference; | import java.lang.ref.WeakReference; | ||||
import java.security.MessageDigest; | import java.security.MessageDigest; | ||||
import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
import java.time.Duration; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
import java.util.Collection; | import java.util.Collection; | ||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; | import org.eclipse.jgit.errors.IncorrectObjectTypeException; | ||||
import org.eclipse.jgit.errors.LargeObjectException; | import org.eclipse.jgit.errors.LargeObjectException; | ||||
import org.eclipse.jgit.errors.MissingObjectException; | import org.eclipse.jgit.errors.MissingObjectException; | ||||
import org.eclipse.jgit.errors.SearchForReuseTimeout; | |||||
import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; | import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; | ||||
import org.eclipse.jgit.internal.JGitText; | import org.eclipse.jgit.internal.JGitText; | ||||
import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; | import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder; | ||||
private boolean indexDisabled; | private boolean indexDisabled; | ||||
private boolean checkSearchForReuseTimeout = false; | |||||
private final Duration searchForReuseTimeout; | |||||
private long searchForReuseStartTimeEpoc; | |||||
private int depth; | private int depth; | ||||
private Collection<? extends ObjectId> unshallowObjects; | private Collection<? extends ObjectId> unshallowObjects; | ||||
deltaBaseAsOffset = config.isDeltaBaseAsOffset(); | deltaBaseAsOffset = config.isDeltaBaseAsOffset(); | ||||
reuseDeltas = config.isReuseDeltas(); | reuseDeltas = config.isReuseDeltas(); | ||||
searchForReuseTimeout = config.getSearchForReuseTimeout(); | |||||
reuseValidate = true; // be paranoid by default | reuseValidate = true; // be paranoid by default | ||||
stats = statsAccumulator != null ? statsAccumulator | stats = statsAccumulator != null ? statsAccumulator | ||||
: new PackStatistics.Accumulator(); | : new PackStatistics.Accumulator(); | ||||
return deltaBaseAsOffset; | return deltaBaseAsOffset; | ||||
} | } | ||||
/** | |||||
* Check whether the search for reuse phase is taking too long. This could | |||||
* be the case when the number of objects and pack files is high and the | |||||
* system is under pressure. If that's the case and | |||||
* checkSearchForReuseTimeout is true abort the search. | |||||
* | |||||
* @throws SearchForReuseTimeout | |||||
* if the search for reuse is taking too long. | |||||
*/ | |||||
public void checkSearchForReuseTimeout() throws SearchForReuseTimeout { | |||||
if (checkSearchForReuseTimeout | |||||
&& Duration.ofMillis(System.currentTimeMillis() | |||||
- searchForReuseStartTimeEpoc) | |||||
.compareTo(searchForReuseTimeout) > 0) { | |||||
throw new SearchForReuseTimeout(searchForReuseTimeout); | |||||
} | |||||
} | |||||
/** | /** | ||||
* Set writer delta base format. Delta base can be written as an offset in a | * Set writer delta base format. Delta base can be written as an offset in a | ||||
* pack file (new approach reducing file size) or as an object id (legacy | * pack file (new approach reducing file size) or as an object id (legacy | ||||
this.deltaBaseAsOffset = deltaBaseAsOffset; | this.deltaBaseAsOffset = deltaBaseAsOffset; | ||||
} | } | ||||
/** | |||||
* Set the writer to check for long search for reuse, exceeding the timeout. | |||||
* Selecting an object representation can be an expensive operation. It is | |||||
* possible to set a max search for reuse time (see | |||||
* PackConfig#CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT for more details). | |||||
* | |||||
* However some operations, i.e.: GC, need to find the best candidate | |||||
* regardless how much time the operation will need to finish. | |||||
* | |||||
* This method enables the search for reuse timeout check, otherwise | |||||
* disabled. | |||||
*/ | |||||
public void enableSearchForReuseTimeout() { | |||||
this.checkSearchForReuseTimeout = true; | |||||
} | |||||
/** | /** | ||||
* Check if the writer will reuse commits that are already stored as deltas. | * Check if the writer will reuse commits that are already stored as deltas. | ||||
* | * | ||||
cnt += objectsLists[OBJ_TAG].size(); | cnt += objectsLists[OBJ_TAG].size(); | ||||
long start = System.currentTimeMillis(); | long start = System.currentTimeMillis(); | ||||
searchForReuseStartTimeEpoc = start; | |||||
beginPhase(PackingPhase.FINDING_SOURCES, monitor, cnt); | beginPhase(PackingPhase.FINDING_SOURCES, monitor, cnt); | ||||
if (cnt <= 4096) { | if (cnt <= 4096) { | ||||
// For small object counts, do everything as one list. | // For small object counts, do everything as one list. |
* @since 5.11 | * @since 5.11 | ||||
*/ | */ | ||||
public static final String CONFIG_KEY_DEFAULT_BRANCH = "defaultbranch"; | public static final String CONFIG_KEY_DEFAULT_BRANCH = "defaultbranch"; | ||||
/** | |||||
* The "pack.searchForReuseTimeout" key | |||||
* | |||||
* @since 5.13 | |||||
*/ | |||||
public static final String CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT = "searchforreusetimeout"; | |||||
} | } |
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_MIN_SIZE_PREVENT_RACYPACK; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_MIN_SIZE_PREVENT_RACYPACK; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_DELTAS; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_DELTAS; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_OBJECTS; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_REUSE_OBJECTS; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT; | |||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SINGLE_PACK; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_SINGLE_PACK; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_THREADS; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_THREADS; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WAIT_PREVENT_RACYPACK; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WAIT_PREVENT_RACYPACK; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WINDOW_MEMORY; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_WINDOW_MEMORY; | ||||
import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_PACK_SECTION; | import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_PACK_SECTION; | ||||
import java.time.Duration; | |||||
import java.util.concurrent.Executor; | import java.util.concurrent.Executor; | ||||
import java.util.concurrent.TimeUnit; | |||||
import java.util.zip.Deflater; | import java.util.zip.Deflater; | ||||
import org.eclipse.jgit.internal.storage.file.PackIndexWriter; | import org.eclipse.jgit.internal.storage.file.PackIndexWriter; | ||||
*/ | */ | ||||
public static final int DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS = 90; | public static final int DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS = 90; | ||||
/** | |||||
* Default max time to spend during the search for reuse phase. This | |||||
* optimization is disabled by default: {@value} | |||||
* | |||||
* @see #setSearchForReuseTimeout(Duration) | |||||
* @since 5.13 | |||||
*/ | |||||
public static final Duration DEFAULT_SEARCH_FOR_REUSE_TIMEOUT = Duration | |||||
.ofSeconds(Integer.MAX_VALUE); | |||||
private int compressionLevel = Deflater.DEFAULT_COMPRESSION; | private int compressionLevel = Deflater.DEFAULT_COMPRESSION; | ||||
private boolean reuseDeltas = DEFAULT_REUSE_DELTAS; | private boolean reuseDeltas = DEFAULT_REUSE_DELTAS; | ||||
private int bitmapInactiveBranchAgeInDays = DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS; | private int bitmapInactiveBranchAgeInDays = DEFAULT_BITMAP_INACTIVE_BRANCH_AGE_IN_DAYS; | ||||
private Duration searchForReuseTimeout = DEFAULT_SEARCH_FOR_REUSE_TIMEOUT; | |||||
private boolean cutDeltaChains; | private boolean cutDeltaChains; | ||||
private boolean singlePack; | private boolean singlePack; | ||||
this.bitmapInactiveBranchAgeInDays = cfg.bitmapInactiveBranchAgeInDays; | this.bitmapInactiveBranchAgeInDays = cfg.bitmapInactiveBranchAgeInDays; | ||||
this.cutDeltaChains = cfg.cutDeltaChains; | this.cutDeltaChains = cfg.cutDeltaChains; | ||||
this.singlePack = cfg.singlePack; | this.singlePack = cfg.singlePack; | ||||
this.searchForReuseTimeout = cfg.searchForReuseTimeout; | |||||
} | } | ||||
/** | /** | ||||
return bitmapInactiveBranchAgeInDays; | return bitmapInactiveBranchAgeInDays; | ||||
} | } | ||||
/** | |||||
* Get the max time to spend during the search for reuse phase. | |||||
* | |||||
* Default setting: {@value #DEFAULT_SEARCH_FOR_REUSE_TIMEOUT} | |||||
* | |||||
* @return the maximum time to spend during the search for reuse phase. | |||||
* @since 5.13 | |||||
*/ | |||||
public Duration getSearchForReuseTimeout() { | |||||
return searchForReuseTimeout; | |||||
} | |||||
/** | /** | ||||
* Set the age in days that marks a branch as "inactive". | * Set the age in days that marks a branch as "inactive". | ||||
* | * | ||||
bitmapInactiveBranchAgeInDays = ageInDays; | bitmapInactiveBranchAgeInDays = ageInDays; | ||||
} | } | ||||
/** | |||||
* Set the max time to spend during the search for reuse phase. | |||||
* | |||||
* @param timeout | |||||
* max time allowed during the search for reuse phase | |||||
* | |||||
* Default setting: {@value #DEFAULT_SEARCH_FOR_REUSE_TIMEOUT} | |||||
* @since 5.13 | |||||
*/ | |||||
public void setSearchForReuseTimeout(Duration timeout) { | |||||
searchForReuseTimeout = timeout; | |||||
} | |||||
/** | /** | ||||
* Update properties by setting fields from the configuration. | * Update properties by setting fields from the configuration. | ||||
* | * | ||||
setBitmapInactiveBranchAgeInDays(rc.getInt(CONFIG_PACK_SECTION, | setBitmapInactiveBranchAgeInDays(rc.getInt(CONFIG_PACK_SECTION, | ||||
CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS, | CONFIG_KEY_BITMAP_INACTIVE_BRANCH_AGE_INDAYS, | ||||
getBitmapInactiveBranchAgeInDays())); | getBitmapInactiveBranchAgeInDays())); | ||||
setSearchForReuseTimeout(Duration.ofSeconds(rc.getTimeUnit( | |||||
CONFIG_PACK_SECTION, null, | |||||
CONFIG_KEY_SEARCH_FOR_REUSE_TIMEOUT, | |||||
getSearchForReuseTimeout().getSeconds(), TimeUnit.SECONDS))); | |||||
setWaitPreventRacyPack(rc.getBoolean(CONFIG_PACK_SECTION, | setWaitPreventRacyPack(rc.getBoolean(CONFIG_PACK_SECTION, | ||||
CONFIG_KEY_WAIT_PREVENT_RACYPACK, isWaitPreventRacyPack())); | CONFIG_KEY_WAIT_PREVENT_RACYPACK, isWaitPreventRacyPack())); | ||||
setMinSizePreventRacyPack(rc.getLong(CONFIG_PACK_SECTION, | setMinSizePreventRacyPack(rc.getLong(CONFIG_PACK_SECTION, | ||||
.append(getBitmapExcessiveBranchCount()); | .append(getBitmapExcessiveBranchCount()); | ||||
b.append(", bitmapInactiveBranchAge=") //$NON-NLS-1$ | b.append(", bitmapInactiveBranchAge=") //$NON-NLS-1$ | ||||
.append(getBitmapInactiveBranchAgeInDays()); | .append(getBitmapInactiveBranchAgeInDays()); | ||||
b.append(", searchForReuseTimeout") //$NON-NLS-1$ | |||||
.append(getSearchForReuseTimeout()); | |||||
b.append(", singlePack=").append(getSinglePack()); //$NON-NLS-1$ | b.append(", singlePack=").append(getSinglePack()); //$NON-NLS-1$ | ||||
return b.toString(); | return b.toString(); | ||||
} | } |
GitProtocolConstants.SECTION_PACKFILE + '\n'); | GitProtocolConstants.SECTION_PACKFILE + '\n'); | ||||
} | } | ||||
} | } | ||||
pw.enableSearchForReuseTimeout(); | |||||
pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut); | pw.writePack(pm, NullProgressMonitor.INSTANCE, packOut); | ||||
if (msgOut != NullOutputStream.INSTANCE) { | if (msgOut != NullOutputStream.INSTANCE) { |