mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-26 06:27:19 +01:00
Merge pull request #1462 from tgstation/1460-CopyDirectoryCustomThrottle
Copy Directory Custom Throttle
This commit is contained in:
@@ -139,6 +139,11 @@ namespace Tgstation.Server.Host.Components
|
||||
/// </summary>
|
||||
readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="InstanceFactory"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SessionConfiguration"/> for the <see cref="InstanceFactory"/>.
|
||||
/// </summary>
|
||||
@@ -177,6 +182,7 @@ namespace Tgstation.Server.Host.Components
|
||||
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
|
||||
/// <param name="gitRemoteFeaturesFactory">The value of <see cref="gitRemoteFeaturesFactory"/>.</param>
|
||||
/// <param name="remoteDeploymentManagerFactory">The value of <see cref="remoteDeploymentManagerFactory"/>.</param>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="sessionConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="sessionConfiguration"/>.</param>
|
||||
public InstanceFactory(
|
||||
IIOManager ioManager,
|
||||
@@ -201,6 +207,7 @@ namespace Tgstation.Server.Host.Components
|
||||
IFileTransferTicketProvider fileTransferService,
|
||||
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
|
||||
IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory,
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IOptions<SessionConfiguration> sessionConfigurationOptions)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
@@ -225,6 +232,7 @@ namespace Tgstation.Server.Host.Components
|
||||
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
|
||||
this.gitRemoteFeaturesFactory = gitRemoteFeaturesFactory ?? throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory));
|
||||
this.remoteDeploymentManagerFactory = remoteDeploymentManagerFactory ?? throw new ArgumentNullException(nameof(remoteDeploymentManagerFactory));
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
|
||||
}
|
||||
#pragma warning restore CA1502
|
||||
@@ -266,7 +274,8 @@ namespace Tgstation.Server.Host.Components
|
||||
postWriteHandler,
|
||||
platformIdentifier,
|
||||
fileTransferService,
|
||||
loggerFactory.CreateLogger<StaticFiles.Configuration>());
|
||||
loggerFactory.CreateLogger<StaticFiles.Configuration>(),
|
||||
generalConfiguration);
|
||||
var eventConsumer = new EventConsumer(configuration);
|
||||
var repoManager = new RepositoryManager(
|
||||
repositoryFactory,
|
||||
@@ -276,7 +285,8 @@ namespace Tgstation.Server.Host.Components
|
||||
postWriteHandler,
|
||||
gitRemoteFeaturesFactory,
|
||||
loggerFactory.CreateLogger<Repository.Repository>(),
|
||||
loggerFactory.CreateLogger<RepositoryManager>());
|
||||
loggerFactory.CreateLogger<RepositoryManager>(),
|
||||
generalConfiguration);
|
||||
try
|
||||
{
|
||||
var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger<ByondManager>());
|
||||
|
||||
@@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
@@ -64,7 +66,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
public string Reference => libGitRepo.Head.FriendlyName;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Uri Origin => new Uri(libGitRepo.Network.Remotes.First().Url);
|
||||
public Uri Origin => new (libGitRepo.Network.Remotes.First().Url);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="LibGit2Sharp.IRepository"/> for the <see cref="Repository"/>.
|
||||
@@ -106,6 +108,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly ILogger<Repository> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="Repository"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Action"/> to be taken when <see cref="Dispose"/> is called.
|
||||
/// </summary>
|
||||
@@ -127,6 +134,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="postWriteHandler">The value of <see cref="postWriteHandler"/>.</param>
|
||||
/// <param name="gitRemoteFeaturesFactory">The <see cref="IGitRemoteFeaturesFactory"/> to provide the value of <see cref="gitRemoteFeatures"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="generalConfiguration">The value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="onDispose">The value if <see cref="onDispose"/>.</param>
|
||||
public Repository(
|
||||
LibGit2Sharp.IRepository libGitRepo,
|
||||
@@ -137,6 +145,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
|
||||
ILogger<Repository> logger,
|
||||
GeneralConfiguration generalConfiguration,
|
||||
Action onDispose)
|
||||
{
|
||||
this.libGitRepo = libGitRepo ?? throw new ArgumentNullException(nameof(libGitRepo));
|
||||
@@ -149,6 +158,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory));
|
||||
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration));
|
||||
this.onDispose = onDispose ?? throw new ArgumentNullException(nameof(onDispose));
|
||||
|
||||
gitRemoteFeatures = gitRemoteFeaturesFactory.CreateGitRemoteFeatures(this);
|
||||
@@ -192,9 +202,9 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
logger.LogDebug(
|
||||
"Begin AddTestMerge: #{0} at {1} ({2}) by <{3} ({4})>",
|
||||
"Begin AddTestMerge: #{prNumber} at {targetSha} ({comment}) by <{committerName} ({committerEmail})>",
|
||||
testMergeParameters.Number,
|
||||
testMergeParameters.TargetCommitSha?.Substring(0, 7),
|
||||
testMergeParameters.TargetCommitSha?[..7],
|
||||
testMergeParameters.Comment,
|
||||
committerName,
|
||||
committerEmail);
|
||||
@@ -233,7 +243,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
try
|
||||
{
|
||||
logger.LogTrace("Fetching refspec {0}...", refSpec);
|
||||
logger.LogTrace("Fetching refspec {refSpec}...", refSpec);
|
||||
|
||||
var remote = libGitRepo.Network.Remotes.First();
|
||||
commands.Fetch(
|
||||
@@ -267,15 +277,13 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var objectName = testMergeParameters.TargetCommitSha ?? localBranchName;
|
||||
var gitObject = libGitRepo.Lookup(objectName);
|
||||
if (gitObject == null)
|
||||
throw new JobException($"Could not find object to merge: {objectName}");
|
||||
var gitObject = libGitRepo.Lookup(objectName) ?? throw new JobException($"Could not find object to merge: {objectName}");
|
||||
|
||||
testMergeParameters.TargetCommitSha = gitObject.Sha;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
logger.LogTrace("Merging {0} into {1}...", testMergeParameters.TargetCommitSha[..7], Reference);
|
||||
logger.LogTrace("Merging {targetCommitSha} into {currentReference}...", testMergeParameters.TargetCommitSha[..7], Reference);
|
||||
|
||||
result = libGitRepo.Merge(testMergeParameters.TargetCommitSha, sig, new MergeOptions
|
||||
{
|
||||
@@ -303,7 +311,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
conflictedPaths.Add(file.FilePath);
|
||||
|
||||
var revertTo = originalCommit.CanonicalName ?? originalCommit.Tip.Sha;
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {0}", revertTo);
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {revertTarget}", revertTo);
|
||||
progressReporter.ReportProgress(0);
|
||||
RawCheckout(revertTo, progressReporter.CreateSection("Hard Reset to {revertTo}", 1.0), cancellationToken);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -340,7 +348,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
if (result.Status != MergeStatus.UpToDate)
|
||||
{
|
||||
logger.LogTrace("Committing merge: \"{0}\"...", commitMessage);
|
||||
logger.LogTrace("Committing merge: \"{commitMessage}\"...", commitMessage);
|
||||
await Task.Factory.StartNew(
|
||||
() => libGitRepo.Commit(commitMessage, sig, sig, new CommitOptions
|
||||
{
|
||||
@@ -391,7 +399,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
throw new ArgumentNullException(nameof(committish));
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
logger.LogDebug("Checkout object: {0}...", committish);
|
||||
logger.LogDebug("Checkout object: {committish}...", committish);
|
||||
await eventConsumer.HandleEvent(EventType.RepoCheckout, new List<string> { committish }, cancellationToken);
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
@@ -497,7 +505,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
logger.LogDebug("Reset to sha: {0}", sha.Substring(0, 7));
|
||||
logger.LogDebug("Reset to sha: {sha}", sha[..7]);
|
||||
|
||||
libGitRepo.RemoveUntrackedFiles();
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
@@ -522,10 +530,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
logger.LogTrace("Copying to {0}...", path);
|
||||
logger.LogTrace("Copying to {path}...", path);
|
||||
await ioMananger.CopyDirectory(
|
||||
ioMananger.ResolvePath(),
|
||||
path,
|
||||
new List<string> { ".git" },
|
||||
(src, dest) =>
|
||||
{
|
||||
@@ -534,6 +540,9 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
ioMananger.ResolvePath(),
|
||||
path,
|
||||
generalConfiguration.GetCopyDirectoryTaskThrottle(),
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
@@ -580,7 +589,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
trackedBranch = libGitRepo.Head.TrackedBranch;
|
||||
logger.LogDebug(
|
||||
"Merge origin/{0}: <{1} ({2})>",
|
||||
"Merge origin/{trackedBranch}: <{committerName} ({committerEmail})>",
|
||||
trackedBranch.FriendlyName,
|
||||
committerName,
|
||||
committerEmail);
|
||||
@@ -597,7 +606,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
if (result.Status == MergeStatus.Conflicts)
|
||||
{
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {0}", oldHead.FriendlyName);
|
||||
logger.LogDebug("Merge conflict, aborting and reverting to {oldHeadFriendlyName}", oldHead.FriendlyName);
|
||||
progressReporter.ReportProgress(0);
|
||||
libGitRepo.Reset(ResetMode.Hard, oldTip, new CheckoutOptions
|
||||
{
|
||||
@@ -654,7 +663,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
|
||||
var startHead = Head;
|
||||
|
||||
logger.LogTrace("Configuring <{0} ({1})> as author/committer", committerName, committerEmail);
|
||||
logger.LogTrace("Configuring <{committerName} ({committerEmail})> as author/committer", committerName, committerEmail);
|
||||
await Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
@@ -711,7 +720,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
var sameHead = Head == startHead;
|
||||
if (sameHead || !Tracking)
|
||||
{
|
||||
logger.LogTrace("Aborted synchronize due to {0}!", sameHead ? "lack of changes" : "not being on tracked reference");
|
||||
logger.LogTrace("Aborted synchronize due to {abortReason}!", sameHead ? "lack of changes" : "not being on tracked reference");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -785,7 +794,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
var targetCommit = libGitRepo.Lookup<Commit>(sha);
|
||||
if (targetCommit == null)
|
||||
{
|
||||
logger.LogTrace("Commit {0} not found in repository", sha);
|
||||
logger.LogTrace("Commit {sha} not found in repository", sha);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -836,10 +845,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (sha == null)
|
||||
throw new ArgumentNullException(nameof(sha));
|
||||
|
||||
var commit = libGitRepo.Lookup<Commit>(sha);
|
||||
if (commit == null)
|
||||
throw new JobException($"Commit {sha} does not exist in the repository!");
|
||||
|
||||
var commit = libGitRepo.Lookup<Commit>(sha) ?? throw new JobException($"Commit {sha} does not exist in the repository!");
|
||||
return commit.Committer.When;
|
||||
},
|
||||
cancellationToken,
|
||||
@@ -854,7 +860,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
void RawCheckout(string committish, JobProgressReporter progressReporter, CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Checkout: {0}", committish);
|
||||
logger.LogTrace("Checkout: {committish}", committish);
|
||||
|
||||
var stage = $"Checkout {committish}";
|
||||
progressReporter = progressReporter.CreateSection(stage, 1.0);
|
||||
@@ -887,7 +893,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (remoteBranch == default)
|
||||
throw;
|
||||
|
||||
logger.LogDebug("Creating local branch for {0}...", remoteBranch.FriendlyName);
|
||||
logger.LogDebug("Creating local branch for {remoteBranchFriendlyName}...", remoteBranch.FriendlyName);
|
||||
var branch = libGitRepo.CreateBranch(committish, remoteBranch.Tip);
|
||||
|
||||
libGitRepo.Branches.Update(branch, branchUpdate => branchUpdate.TrackedBranch = remoteBranch.CanonicalName);
|
||||
@@ -995,7 +1001,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogTrace("Updating submodules with{0} credentials...", username == null ? "out" : String.Empty);
|
||||
logger.LogTrace("Updating submodules with{orWithout} credentials...", username == null ? "out" : String.Empty);
|
||||
|
||||
var factor = 1.0 / submoduleCount / 2;
|
||||
foreach (var submodule in libGitRepo.Submodules)
|
||||
@@ -1013,7 +1019,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
progressReporter.CreateSection($"Checkout submodule {submodule.Name}", factor)),
|
||||
};
|
||||
|
||||
logger.LogDebug("Updating submodule {0}...", submodule.Name);
|
||||
logger.LogDebug("Updating submodule {submoduleName}...", submodule.Name);
|
||||
Task RawSubModuleUpdate() => Task.Factory.StartNew(
|
||||
() => libGitRepo.Submodules.Update(submodule.Name, submoduleUpdateOptions),
|
||||
cancellationToken,
|
||||
@@ -1029,14 +1035,14 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
// kill off the modules/ folder in .git and try again
|
||||
progressReporter.ReportProgress(null);
|
||||
credentialsProvider.CheckBadCredentialsException(ex);
|
||||
logger.LogWarning(ex, "Initial update of submodule {0} failed. Deleting submodule directories and re-attempting...", submodule.Name);
|
||||
logger.LogWarning(ex, "Initial update of submodule {submoduleName} failed. Deleting submodule directories and re-attempting...", submodule.Name);
|
||||
|
||||
await Task.WhenAll(
|
||||
ioMananger.DeleteDirectory($".git/modules/{submodule.Path}", cancellationToken),
|
||||
ioMananger.DeleteDirectory(submodule.Path, cancellationToken))
|
||||
;
|
||||
|
||||
logger.LogTrace("Second update attempt for submodule {0}...", submodule.Name);
|
||||
logger.LogTrace("Second update attempt for submodule {submoduleName}...", submodule.Name);
|
||||
try
|
||||
{
|
||||
await RawSubModuleUpdate();
|
||||
@@ -1048,7 +1054,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
catch (LibGit2SharpException ex2)
|
||||
{
|
||||
credentialsProvider.CheckBadCredentialsException(ex2);
|
||||
logger.LogTrace(ex2, "Retried update of submodule {0} failed!", submodule.Name);
|
||||
logger.LogTrace(ex2, "Retried update of submodule {submoduleName} failed!", submodule.Name);
|
||||
throw new AggregateException(ex, ex2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
@@ -62,6 +63,11 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly ILogger<RepositoryManager> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="RepositoryManager"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// Used for controlling single access to the <see cref="IRepository"/>.
|
||||
/// </summary>
|
||||
@@ -78,6 +84,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="gitRemoteFeaturesFactory">The value of <see cref="gitRemoteFeaturesFactory"/>.</param>
|
||||
/// <param name="repositoryLogger">The value of <see cref="repositoryLogger"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="generalConfiguration">The value of <see cref="generalConfiguration"/>.</param>
|
||||
public RepositoryManager(
|
||||
ILibGit2RepositoryFactory repositoryFactory,
|
||||
ILibGit2Commands commands,
|
||||
@@ -86,7 +93,8 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IGitRemoteFeaturesFactory gitRemoteFeaturesFactory,
|
||||
ILogger<Repository> repositoryLogger,
|
||||
ILogger<RepositoryManager> logger)
|
||||
ILogger<RepositoryManager> logger,
|
||||
GeneralConfiguration generalConfiguration)
|
||||
{
|
||||
this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
|
||||
this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
|
||||
@@ -96,6 +104,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
this.gitRemoteFeaturesFactory = gitRemoteFeaturesFactory ?? throw new ArgumentNullException(nameof(gitRemoteFeaturesFactory));
|
||||
this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration));
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
}
|
||||
|
||||
@@ -121,7 +130,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
logger.LogInformation("Begin clone {0} (Branch: {1})", url, initialBranch);
|
||||
logger.LogInformation("Begin clone {url} (Branch: {initialBranch})", url, initialBranch);
|
||||
lock (semaphore)
|
||||
{
|
||||
if (CloneInProgress)
|
||||
@@ -217,6 +226,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
postWriteHandler,
|
||||
gitRemoteFeaturesFactory,
|
||||
repositoryLogger,
|
||||
generalConfiguration,
|
||||
() =>
|
||||
{
|
||||
logger.LogTrace("Releasing semaphore due to Repository disposal...");
|
||||
|
||||
@@ -13,7 +13,9 @@ using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models.Response;
|
||||
using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Host.Security;
|
||||
@@ -111,6 +113,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// </summary>
|
||||
readonly ILogger<Configuration> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for <see cref="Configuration"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="SemaphoreSlim"/> for <see cref="Configuration"/>. Also used as a <see langword="lock"/> <see cref="object"/>.
|
||||
/// </summary>
|
||||
@@ -137,6 +144,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
/// <param name="platformIdentifier">The value of <see cref="platformIdentifier"/>.</param>
|
||||
/// <param name="fileTransferService">The value of <see cref="fileTransferService"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="logger"/>.</param>
|
||||
/// <param name="generalConfiguration">The value of <see cref="generalConfiguration"/>.</param>
|
||||
public Configuration(
|
||||
IIOManager ioManager,
|
||||
ISynchronousIOManager synchronousIOManager,
|
||||
@@ -145,7 +153,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
IPostWriteHandler postWriteHandler,
|
||||
IPlatformIdentifier platformIdentifier,
|
||||
IFileTransferTicketProvider fileTransferService,
|
||||
ILogger<Configuration> logger)
|
||||
ILogger<Configuration> logger,
|
||||
GeneralConfiguration generalConfiguration)
|
||||
{
|
||||
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
|
||||
this.synchronousIOManager = synchronousIOManager ?? throw new ArgumentNullException(nameof(synchronousIOManager));
|
||||
@@ -155,6 +164,7 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
this.platformIdentifier = platformIdentifier ?? throw new ArgumentNullException(nameof(platformIdentifier));
|
||||
this.fileTransferService = fileTransferService ?? throw new ArgumentNullException(nameof(fileTransferService));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.generalConfiguration = generalConfiguration ?? throw new ArgumentNullException(nameof(generalConfiguration));
|
||||
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
disposeCts = new CancellationTokenSource();
|
||||
@@ -180,7 +190,13 @@ namespace Tgstation.Server.Host.Components.StaticFiles
|
||||
var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken);
|
||||
var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken);
|
||||
var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken);
|
||||
var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, null, cancellationToken);
|
||||
var copyTask = ioManager.CopyDirectory(
|
||||
null,
|
||||
null,
|
||||
CodeModificationsSubdirectory,
|
||||
destination,
|
||||
generalConfiguration.GetCopyDirectoryTaskThrottle(),
|
||||
cancellationToken);
|
||||
|
||||
await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask, copyTask);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.Properties;
|
||||
using Tgstation.Server.Host.Setup;
|
||||
|
||||
@@ -116,6 +117,11 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// </summary>
|
||||
public bool SkipAddingByondFirewallException { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A limit on the amount of tasks used for asynchronous I/O when copying directories during the deployment process as a multiplier to the machine's <see cref="Environment.ProcessorCount"/>. Too few can significantly increase deployment times, too many can make TGS unresponsive and slowdown other I/O operations on the machine.
|
||||
/// </summary>
|
||||
public uint? DeploymentDirectoryCopyTasksPerCore { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GeneralConfiguration"/> class.
|
||||
/// </summary>
|
||||
@@ -147,6 +153,13 @@ namespace Tgstation.Server.Host.Configuration
|
||||
CurrentConfigVersion);
|
||||
else
|
||||
logger.LogWarning("Your `ConfigVersion` is out-of-date. Please follow migration instructions from the TGS release notes.");
|
||||
|
||||
if (DeploymentDirectoryCopyTasksPerCore == 0)
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(DeploymentDirectoryCopyTasksPerCore)} must be at least 1!");
|
||||
else if (this.GetCopyDirectoryTaskThrottle() < 1)
|
||||
throw new InvalidOperationException(
|
||||
$"{nameof(DeploymentDirectoryCopyTasksPerCore)} is too large for the CPU core count of {Environment.ProcessorCount} and overflows a 32-bit signed integer. Please lower the value!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for the <see cref="GeneralConfiguration"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
static class GeneralConfigurationExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the total number of tasks that may run simultaneously during an asynchronous directory copy operation.
|
||||
/// </summary>
|
||||
/// <param name="generalConfiguration">The <see cref="GeneralConfiguration"/> to read the <see cref="GeneralConfiguration.DeploymentDirectoryCopyTasksPerCore"/> from.</param>
|
||||
/// <returns>The total number of tasks that may run simultaneously during an asynchronous directory copy operation.</returns>
|
||||
public static int? GetCopyDirectoryTaskThrottle(this GeneralConfiguration generalConfiguration)
|
||||
{
|
||||
if (generalConfiguration == null)
|
||||
throw new ArgumentNullException(nameof(generalConfiguration));
|
||||
|
||||
var tasksPerCore = generalConfiguration.DeploymentDirectoryCopyTasksPerCore;
|
||||
if (!tasksPerCore.HasValue)
|
||||
return null;
|
||||
|
||||
var taskThrottle = (uint)Environment.ProcessorCount * tasksPerCore.Value;
|
||||
return (int)taskThrottle;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +62,11 @@ namespace Tgstation.Server.Host.IO
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task CopyDirectory(
|
||||
string src,
|
||||
string dest,
|
||||
IEnumerable<string> ignore,
|
||||
Func<string, string, Task> postCopyCallback,
|
||||
string src,
|
||||
string dest,
|
||||
int? taskThrottle,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (src == null)
|
||||
@@ -73,10 +74,13 @@ namespace Tgstation.Server.Host.IO
|
||||
if (dest == null)
|
||||
throw new ArgumentNullException(nameof(src));
|
||||
|
||||
if (taskThrottle.HasValue && taskThrottle < 1)
|
||||
throw new ArgumentOutOfRangeException(nameof(taskThrottle), taskThrottle, "taskThrottle must be at least 1!");
|
||||
|
||||
src = ResolvePath(src);
|
||||
dest = ResolvePath(dest);
|
||||
|
||||
using var semaphore = new SemaphoreSlim(100 * Environment.ProcessorCount);
|
||||
using var semaphore = taskThrottle.HasValue ? new SemaphoreSlim(taskThrottle.Value) : null;
|
||||
await Task.WhenAll(CopyDirectoryImpl(src, dest, ignore, postCopyCallback, semaphore, cancellationToken));
|
||||
}
|
||||
|
||||
@@ -327,7 +331,7 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <param name="dest">The destination directory path.</param>
|
||||
/// <param name="ignore">Files and folders to ignore at the root level.</param>
|
||||
/// <param name="postCopyCallback">The optional callback called for each source/dest file pair post copy.</param>
|
||||
/// <param name="semaphore"><see cref="SemaphoreSlim"/> used to limit degree of parallelism.</param>
|
||||
/// <param name="semaphore">Optional <see cref="SemaphoreSlim"/> used to limit degree of parallelism.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="IEnumerable{T}"/> of <see cref="Task"/>s representing the running operations. The first <see cref="Task"/> returned is always the necessary call to <see cref="CreateDirectory(string, CancellationToken)"/>.</returns>
|
||||
IEnumerable<Task> CopyDirectoryImpl(
|
||||
@@ -377,7 +381,9 @@ namespace Tgstation.Server.Host.IO
|
||||
async Task CopyThisFile()
|
||||
{
|
||||
await subdirCreationTask;
|
||||
using var lockContext = await SemaphoreSlimContext.Lock(semaphore, cancellationToken);
|
||||
using var lockContext = semaphore != null
|
||||
? await SemaphoreSlimContext.Lock(semaphore, cancellationToken)
|
||||
: null;
|
||||
await CopyFile(sourceFile, destFile, cancellationToken);
|
||||
if (postCopyCallback != null)
|
||||
await postCopyCallback(sourceFile, destFile);
|
||||
|
||||
@@ -48,17 +48,19 @@ namespace Tgstation.Server.Host.IO
|
||||
/// <summary>
|
||||
/// Copies a directory from <paramref name="src"/> to <paramref name="dest"/>.
|
||||
/// </summary>
|
||||
/// <param name="src">The source directory path.</param>
|
||||
/// <param name="dest">The destination directory path.</param>
|
||||
/// <param name="ignore">Files and folders to ignore at the root level.</param>
|
||||
/// <param name="postCopyCallback">The optional callback called for each source/dest file pair post copy.</param>
|
||||
/// <param name="src">The source directory path.</param>
|
||||
/// <param name="dest">The destination directory path.</param>
|
||||
/// <param name="taskThrottle">The optional maximum number of simultaneous tasks allowed to execute.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task CopyDirectory(
|
||||
string src,
|
||||
string dest,
|
||||
IEnumerable<string> ignore,
|
||||
Func<string, string, Task> postCopyCallback,
|
||||
string src,
|
||||
string dest,
|
||||
int? taskThrottle,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,6 +12,7 @@ General:
|
||||
ValidInstancePaths:
|
||||
HostApiDocumentation: false
|
||||
SkipAddingByondFirewallException: false
|
||||
DeploymentDirectoryCopyTasksPerCore: 100
|
||||
Session:
|
||||
HighPriorityLiveDreamDaemon: false
|
||||
LowPriorityDeploymentProcesses: true
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Tgstation.Server.Host.Extensions.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class TestGeneralConfigurationExtensions
|
||||
{
|
||||
[TestMethod]
|
||||
public void TestThrowsOnNullParameter()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => GeneralConfigurationExtensions.GetCopyDirectoryTaskThrottle(null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Remora.Discord.API.Objects;
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -71,5 +74,108 @@ namespace Tgstation.Server.Host.IO.Tests
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestCopyDirectoryThrows()
|
||||
{
|
||||
int? throttle = null;
|
||||
var tempPath1 = Guid.NewGuid().ToString();
|
||||
var tempPath2 = Guid.NewGuid().ToString();
|
||||
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => ioManager.CopyDirectory(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
tempPath2,
|
||||
throttle,
|
||||
default));
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => ioManager.CopyDirectory(
|
||||
null,
|
||||
null,
|
||||
tempPath1,
|
||||
null,
|
||||
throttle,
|
||||
default));
|
||||
await Assert.ThrowsExceptionAsync<ArgumentNullException>(() => ioManager.CopyDirectory(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
throttle,
|
||||
default));
|
||||
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(() => ioManager.CopyDirectory(
|
||||
null,
|
||||
null,
|
||||
tempPath1,
|
||||
tempPath2,
|
||||
-1,
|
||||
default));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestCopyDirectoryOneTask()
|
||||
{
|
||||
await TestCopyDirectory(1);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestCopyDirectoryMaxTasks()
|
||||
{
|
||||
await TestCopyDirectory(Int32.MaxValue);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestCopyDirectoryUnlimitedTasks()
|
||||
{
|
||||
await TestCopyDirectory(null);
|
||||
}
|
||||
|
||||
async Task TestCopyDirectory(int? throttle)
|
||||
{
|
||||
var tempPath = Path.GetTempFileName();
|
||||
File.Delete(tempPath);
|
||||
Directory.CreateDirectory(tempPath);
|
||||
try
|
||||
{
|
||||
var tempPath2 = Path.GetTempFileName();
|
||||
File.Delete(tempPath2);
|
||||
|
||||
await File.WriteAllTextAsync(Path.Combine(tempPath, "file.txt"), "asdf");
|
||||
var subDir = Path.Combine(tempPath, "subdir");
|
||||
Directory.CreateDirectory(subDir);
|
||||
await File.WriteAllTextAsync(Path.Combine(subDir, "file2.txt"), "fdsa");
|
||||
|
||||
try
|
||||
{
|
||||
await ioManager.CopyDirectory(
|
||||
null,
|
||||
null,
|
||||
tempPath,
|
||||
tempPath2,
|
||||
throttle,
|
||||
default);
|
||||
|
||||
Assert.IsTrue(Directory.Exists(tempPath2));
|
||||
var newFilePath = Path.Combine(tempPath2, "file.txt");
|
||||
Assert.IsTrue(File.Exists(newFilePath));
|
||||
var newFileText = await File.ReadAllTextAsync(newFilePath);
|
||||
Assert.AreEqual("asdf", newFileText);
|
||||
var newDirPath = Path.Combine(tempPath2, "subdir");
|
||||
Assert.IsTrue(Directory.Exists(newDirPath));
|
||||
var newFile2Path = Path.Combine(newDirPath, "file2.txt");
|
||||
Assert.IsTrue(File.Exists(newFile2Path));
|
||||
var newFile2Text = await File.ReadAllTextAsync(newFile2Path);
|
||||
Assert.AreEqual("fdsa", newFile2Text);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempPath2, true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(tempPath, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,15 +106,17 @@ namespace Tgstation.Server.Tests.Live.Instance
|
||||
var ioManager = new DefaultIOManager();
|
||||
return Task.WhenAll(
|
||||
ioManager.CopyDirectory(
|
||||
Enumerable.Empty<string>(),
|
||||
null,
|
||||
"../../../../DMAPI",
|
||||
ioManager.ConcatPath(instance.Path, "Repository", "tests", "DMAPI"),
|
||||
Enumerable.Empty<string>(),
|
||||
null,
|
||||
cancellationToken),
|
||||
ioManager.CopyDirectory(
|
||||
Enumerable.Empty<string>(),
|
||||
null,
|
||||
"../../../../../src/DMAPI",
|
||||
ioManager.ConcatPath(instance.Path, "Repository", "src", "DMAPI"),
|
||||
Enumerable.Empty<string>(),
|
||||
null,
|
||||
cancellationToken)
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ using Moq;
|
||||
|
||||
using Tgstation.Server.Host.Components.Events;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
using Tgstation.Server.Tests.Live;
|
||||
@@ -31,6 +32,7 @@ namespace Tgstation.Server.Tests
|
||||
Mock.Of<IPostWriteHandler>(),
|
||||
Mock.Of<IGitRemoteFeaturesFactory>(),
|
||||
Mock.Of<ILogger<Repository>>(),
|
||||
new GeneralConfiguration(),
|
||||
() => { });
|
||||
|
||||
const string StartSha = "af4da8beb9f9b374b04a3cc4d65acca662e8cc1a";
|
||||
|
||||
Reference in New Issue
Block a user