mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-30 08:33:19 +01:00
Jobs now take an IDatabaseContextFactory
As opposed to an IDatabaseContext
This commit is contained in:
@@ -534,7 +534,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
#pragma warning disable CA1506
|
||||
public async Task DeploymentProcess(
|
||||
Models.Job job,
|
||||
IDatabaseContext databaseContext,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
Action<int> progressReporter,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -542,44 +542,41 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
if (job == null)
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
#pragma warning restore IDE0016 // Use 'throw' expression
|
||||
if (databaseContext == null)
|
||||
throw new ArgumentNullException(nameof(databaseContext));
|
||||
if (databaseContextFactory == null)
|
||||
throw new ArgumentNullException(nameof(databaseContextFactory));
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
var averageSpan = await CalculateExpectedDeploymentTime(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var ddSettings = await databaseContext
|
||||
.DreamDaemonSettings
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.Select(x => new Models.DreamDaemonSettings
|
||||
{
|
||||
StartupTimeout = x.StartupTimeout,
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (ddSettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingDreamDaemonSettings);
|
||||
|
||||
var dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (dreamMakerSettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingDreamMakerSettings);
|
||||
|
||||
Models.RepositorySettings repositorySettings = null;
|
||||
string repoOwner = null;
|
||||
string repoName = null;
|
||||
Models.CompileJob compileJob;
|
||||
Models.RevisionInformation revInfo;
|
||||
|
||||
using (var repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
if (repo == null)
|
||||
throw new JobException(ErrorCode.RepoMissing);
|
||||
|
||||
if (repo.IsGitHubRepository)
|
||||
TimeSpan? averageSpan = null;
|
||||
Models.RepositorySettings repositorySettings = null;
|
||||
Models.DreamDaemonSettings ddSettings = null;
|
||||
DreamMakerSettings dreamMakerSettings = null;
|
||||
IRepository repo = null;
|
||||
Models.CompileJob compileJob = null;
|
||||
Models.RevisionInformation revInfo = null;
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
repoOwner = repo.GitHubOwner;
|
||||
repoName = repo.GitHubRepoName;
|
||||
averageSpan = await CalculateExpectedDeploymentTime(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ddSettings = await databaseContext
|
||||
.DreamDaemonSettings
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.Select(x => new Models.DreamDaemonSettings
|
||||
{
|
||||
StartupTimeout = x.StartupTimeout,
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (ddSettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingDreamDaemonSettings);
|
||||
|
||||
dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (dreamMakerSettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingDreamMakerSettings);
|
||||
|
||||
repositorySettings = await databaseContext
|
||||
.RepositorySettings
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
@@ -594,34 +591,55 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
.ConfigureAwait(false);
|
||||
if (repositorySettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingRepositorySettings);
|
||||
}
|
||||
|
||||
var repoSha = repo.Head;
|
||||
revInfo = await databaseContext
|
||||
.RevisionInformations
|
||||
.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id)
|
||||
.Include(x => x.ActiveTestMerges)
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ThenInclude(x => x.MergedBy)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (revInfo == default)
|
||||
{
|
||||
revInfo = new Models.RevisionInformation
|
||||
repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
CommitSha = repoSha,
|
||||
OriginCommitSha = repoSha,
|
||||
Instance = new Models.Instance
|
||||
if (repo == null)
|
||||
throw new JobException(ErrorCode.RepoMissing);
|
||||
|
||||
if (repo.IsGitHubRepository)
|
||||
{
|
||||
Id = metadata.Id
|
||||
repoOwner = repo.GitHubOwner;
|
||||
repoName = repo.GitHubRepoName;
|
||||
}
|
||||
};
|
||||
|
||||
logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
|
||||
databaseContext.Instances.Attach(revInfo.Instance);
|
||||
}
|
||||
var repoSha = repo.Head;
|
||||
revInfo = await databaseContext
|
||||
.RevisionInformations
|
||||
.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id)
|
||||
.Include(x => x.ActiveTestMerges)
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ThenInclude(x => x.MergedBy)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (revInfo == default)
|
||||
{
|
||||
revInfo = new Models.RevisionInformation
|
||||
{
|
||||
CommitSha = repoSha,
|
||||
OriginCommitSha = repoSha,
|
||||
Instance = new Models.Instance
|
||||
{
|
||||
Id = metadata.Id
|
||||
}
|
||||
};
|
||||
|
||||
logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
|
||||
databaseContext.Instances.Attach(revInfo.Instance);
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
repo.Dispose();
|
||||
throw;
|
||||
}
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
using (repo)
|
||||
compileJob = await Compile(
|
||||
revInfo,
|
||||
dreamMakerSettings,
|
||||
@@ -632,33 +650,46 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
compileJob.Job = job;
|
||||
|
||||
databaseContext.CompileJobs.Add(compileJob);
|
||||
|
||||
await PostDeploymentComments(compileJob, repositorySettings, repoOwner, repoName).ConfigureAwait(false);
|
||||
|
||||
// The difficulty with compile jobs is they have a two part commit
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
try
|
||||
{
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// So we need to un-commit the compile job if the above throws
|
||||
databaseContext.CompileJobs.Remove(compileJob);
|
||||
await databaseContext.Save(default).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await CleanupFailedCompile(compileJob, ex is OperationCanceledException, default).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
compileJob.Job = new Models.Job
|
||||
{
|
||||
Id = job.Id
|
||||
};
|
||||
compileJob.RevisionInformation = new Models.RevisionInformation
|
||||
{
|
||||
Id = revInfo.Id
|
||||
};
|
||||
|
||||
databaseContext.RevisionInformations.Attach(compileJob.RevisionInformation);
|
||||
databaseContext.Jobs.Attach(compileJob.Job);
|
||||
databaseContext.CompileJobs.Add(compileJob);
|
||||
|
||||
await PostDeploymentComments(compileJob, repositorySettings, repoOwner, repoName).ConfigureAwait(false);
|
||||
|
||||
// The difficulty with compile jobs is they have a two part commit
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// So we need to un-commit the compile job if the above throws
|
||||
databaseContext.CompileJobs.Remove(compileJob);
|
||||
await databaseContext.Save(default).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await CleanupFailedCompile(compileJob, ex is OperationCanceledException, default).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
await eventConsumer.HandleEvent(EventType.DeploymentComplete, null, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -15,13 +15,13 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// Create and a compile job and insert it into the database. Meant to be called by a <see cref="Jobs.IJobManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="job">The running <see cref="Job"/>.</param>
|
||||
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the operation.</param>
|
||||
/// <param name="databaseContextFactory">The <see cref="IDatabaseContextFactory"/> for the operation.</param>
|
||||
/// <param name="progressReporter">The <see cref="Action{T1}"/> to report compilation progress.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task DeploymentProcess(
|
||||
Job job,
|
||||
IDatabaseContext databaseContext,
|
||||
IDatabaseContextFactory databaseContextFactory,
|
||||
Action<int> progressReporter,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
@@ -179,148 +179,156 @@ namespace Tgstation.Server.Host.Components
|
||||
};
|
||||
|
||||
string deploySha = null;
|
||||
await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContext, progressReporter, jobCancellationToken) =>
|
||||
await jobManager.RegisterOperation(repositoryUpdateJob, async (paramJob, databaseContextFactory, progressReporter, jobCancellationToken) =>
|
||||
{
|
||||
var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
|
||||
|
||||
// assume 5 steps with synchronize
|
||||
const int ProgressSections = 7;
|
||||
const int ProgressStep = 100 / ProgressSections;
|
||||
string repoHead = null;
|
||||
|
||||
const int NumSteps = 3;
|
||||
var doneSteps = 0;
|
||||
|
||||
Action<int> NextProgressReporter()
|
||||
{
|
||||
var tmpDoneSteps = doneSteps;
|
||||
++doneSteps;
|
||||
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps);
|
||||
}
|
||||
|
||||
using var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false);
|
||||
if (repo == null)
|
||||
{
|
||||
logger.LogTrace("Aborting repo update, no repository!");
|
||||
return;
|
||||
}
|
||||
|
||||
var startSha = repo.Head;
|
||||
if (!repo.Tracking)
|
||||
{
|
||||
logger.LogTrace("Aborting repo update, active ref not tracking any remote branch!");
|
||||
deploySha = startSha;
|
||||
return;
|
||||
}
|
||||
|
||||
var repositorySettings = await repositorySettingsTask.ConfigureAwait(false);
|
||||
|
||||
// the main point of auto update is to pull the remote
|
||||
await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
RevisionInformation currentRevInfo = null;
|
||||
bool hasDbChanges = false;
|
||||
|
||||
Task<RevisionInformation> LoadRevInfo() => databaseContext.RevisionInformations
|
||||
.Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id)
|
||||
.Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
async Task UpdateRevInfo(string currentHead, bool onOrigin)
|
||||
{
|
||||
if (currentRevInfo == null)
|
||||
currentRevInfo = await LoadRevInfo().ConfigureAwait(false);
|
||||
|
||||
if (currentRevInfo == default)
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, currentHead);
|
||||
onOrigin = true;
|
||||
}
|
||||
var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
|
||||
|
||||
var attachedInstance = new Models.Instance
|
||||
{
|
||||
Id = metadata.Id
|
||||
};
|
||||
var oldRevInfo = currentRevInfo;
|
||||
currentRevInfo = new RevisionInformation
|
||||
{
|
||||
CommitSha = currentHead,
|
||||
OriginCommitSha = onOrigin ? currentHead : oldRevInfo.OriginCommitSha,
|
||||
Instance = attachedInstance
|
||||
};
|
||||
if (!onOrigin)
|
||||
currentRevInfo.ActiveTestMerges = new List<RevInfoTestMerge>(oldRevInfo.ActiveTestMerges);
|
||||
const int NumSteps = 3;
|
||||
var doneSteps = 0;
|
||||
|
||||
databaseContext.Instances.Attach(attachedInstance);
|
||||
databaseContext.RevisionInformations.Add(currentRevInfo);
|
||||
hasDbChanges = true;
|
||||
}
|
||||
Action<int> NextProgressReporter()
|
||||
{
|
||||
var tmpDoneSteps = doneSteps;
|
||||
++doneSteps;
|
||||
return progress => progressReporter((progress + (100 * tmpDoneSteps)) / NumSteps);
|
||||
}
|
||||
|
||||
// take appropriate auto update actions
|
||||
bool shouldSyncTracked;
|
||||
if (repositorySettings.AutoUpdatesKeepTestMerges.Value)
|
||||
{
|
||||
logger.LogTrace("Preserving test merges...");
|
||||
using var repo = await RepositoryManager.LoadRepository(jobCancellationToken).ConfigureAwait(false);
|
||||
if (repo == null)
|
||||
{
|
||||
logger.LogTrace("Aborting repo update, no repository!");
|
||||
return;
|
||||
}
|
||||
|
||||
var currentRevInfoTask = LoadRevInfo();
|
||||
var startSha = repo.Head;
|
||||
if (!repo.Tracking)
|
||||
{
|
||||
logger.LogTrace("Aborting repo update, active ref not tracking any remote branch!");
|
||||
deploySha = startSha;
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
var repositorySettings = await repositorySettingsTask.ConfigureAwait(false);
|
||||
|
||||
if (!result.HasValue)
|
||||
throw new JobException(Api.Models.ErrorCode.InstanceUpdateTestMergeConflict);
|
||||
// the main point of auto update is to pull the remote
|
||||
await repo.FetchOrigin(repositorySettings.AccessUser, repositorySettings.AccessToken, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
currentRevInfo = await currentRevInfoTask.ConfigureAwait(false);
|
||||
RevisionInformation currentRevInfo = null;
|
||||
bool hasDbChanges = false;
|
||||
|
||||
var lastRevInfoWasOriginCommit = currentRevInfo == default || currentRevInfo.CommitSha == currentRevInfo.OriginCommitSha;
|
||||
var stillOnOrigin = result.Value && lastRevInfoWasOriginCommit;
|
||||
Task<RevisionInformation> LoadRevInfo() => databaseContext.RevisionInformations
|
||||
.Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id)
|
||||
.Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
var currentHead = repo.Head;
|
||||
if (currentHead != startSha)
|
||||
{
|
||||
await UpdateRevInfo(currentHead, stillOnOrigin).ConfigureAwait(false);
|
||||
shouldSyncTracked = stillOnOrigin;
|
||||
}
|
||||
else
|
||||
shouldSyncTracked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogTrace("Not preserving test merges...");
|
||||
await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
async Task UpdateRevInfo(string currentHead, bool onOrigin)
|
||||
{
|
||||
if (currentRevInfo == null)
|
||||
currentRevInfo = await LoadRevInfo().ConfigureAwait(false);
|
||||
|
||||
var currentHead = repo.Head;
|
||||
if (currentRevInfo == default)
|
||||
{
|
||||
logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, currentHead);
|
||||
onOrigin = true;
|
||||
}
|
||||
|
||||
currentRevInfo = await databaseContext.RevisionInformations
|
||||
.Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id)
|
||||
.FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false);
|
||||
var attachedInstance = new Models.Instance
|
||||
{
|
||||
Id = metadata.Id
|
||||
};
|
||||
var oldRevInfo = currentRevInfo;
|
||||
currentRevInfo = new RevisionInformation
|
||||
{
|
||||
CommitSha = currentHead,
|
||||
OriginCommitSha = onOrigin ? currentHead : oldRevInfo.OriginCommitSha,
|
||||
Instance = attachedInstance
|
||||
};
|
||||
if (!onOrigin)
|
||||
currentRevInfo.ActiveTestMerges = new List<RevInfoTestMerge>(oldRevInfo.ActiveTestMerges);
|
||||
|
||||
if (currentHead != startSha && currentRevInfo != default)
|
||||
await UpdateRevInfo(currentHead, true).ConfigureAwait(false);
|
||||
databaseContext.Instances.Attach(attachedInstance);
|
||||
databaseContext.RevisionInformations.Add(currentRevInfo);
|
||||
hasDbChanges = true;
|
||||
}
|
||||
|
||||
shouldSyncTracked = true;
|
||||
}
|
||||
// take appropriate auto update actions
|
||||
bool shouldSyncTracked;
|
||||
if (repositorySettings.AutoUpdatesKeepTestMerges.Value)
|
||||
{
|
||||
logger.LogTrace("Preserving test merges...");
|
||||
|
||||
// synch if necessary
|
||||
if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head)
|
||||
{
|
||||
var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false);
|
||||
var currentHead = repo.Head;
|
||||
if (currentHead != currentRevInfo.CommitSha)
|
||||
await UpdateRevInfo(currentHead, pushedOrigin).ConfigureAwait(false);
|
||||
}
|
||||
var currentRevInfoTask = LoadRevInfo();
|
||||
|
||||
if (hasDbChanges)
|
||||
try
|
||||
{
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await repo.ResetToSha(startSha, progressReporter, default).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
var result = await repo.MergeOrigin(repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!result.HasValue)
|
||||
throw new JobException(Api.Models.ErrorCode.InstanceUpdateTestMergeConflict);
|
||||
|
||||
currentRevInfo = await currentRevInfoTask.ConfigureAwait(false);
|
||||
|
||||
var lastRevInfoWasOriginCommit = currentRevInfo == default || currentRevInfo.CommitSha == currentRevInfo.OriginCommitSha;
|
||||
var stillOnOrigin = result.Value && lastRevInfoWasOriginCommit;
|
||||
|
||||
var currentHead = repo.Head;
|
||||
if (currentHead != startSha)
|
||||
{
|
||||
await UpdateRevInfo(currentHead, stillOnOrigin).ConfigureAwait(false);
|
||||
shouldSyncTracked = stillOnOrigin;
|
||||
}
|
||||
else
|
||||
shouldSyncTracked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogTrace("Not preserving test merges...");
|
||||
await repo.ResetToOrigin(NextProgressReporter(), jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
var currentHead = repo.Head;
|
||||
|
||||
currentRevInfo = await databaseContext.RevisionInformations
|
||||
.Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id)
|
||||
.FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (currentHead != startSha && currentRevInfo != default)
|
||||
await UpdateRevInfo(currentHead, true).ConfigureAwait(false);
|
||||
|
||||
shouldSyncTracked = true;
|
||||
}
|
||||
|
||||
// synch if necessary
|
||||
if (repositorySettings.AutoUpdatesSynchronize.Value && startSha != repo.Head)
|
||||
{
|
||||
var pushedOrigin = await repo.Sychronize(repositorySettings.AccessUser, repositorySettings.AccessToken, repositorySettings.CommitterName, repositorySettings.CommitterEmail, NextProgressReporter(), shouldSyncTracked, jobCancellationToken).ConfigureAwait(false);
|
||||
var currentHead = repo.Head;
|
||||
if (currentHead != currentRevInfo.CommitSha)
|
||||
await UpdateRevInfo(currentHead, pushedOrigin).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
repoHead = repo.Head;
|
||||
|
||||
if (hasDbChanges)
|
||||
try
|
||||
{
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await repo.ResetToSha(startSha, progressReporter, default).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
progressReporter(5 * ProgressStep);
|
||||
deploySha = repo.Head;
|
||||
deploySha = repoHead;
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await jobManager.WaitForJobCompletion(repositoryUpdateJob, user, cancellationToken, default).ConfigureAwait(false);
|
||||
|
||||
@@ -211,8 +211,7 @@ namespace Tgstation.Server.Host.Components
|
||||
repoIoManager,
|
||||
eventConsumer,
|
||||
loggerFactory.CreateLogger<Repository.Repository>(),
|
||||
loggerFactory.CreateLogger<RepositoryManager>(),
|
||||
metadata.RepositorySettings);
|
||||
loggerFactory.CreateLogger<RepositoryManager>());
|
||||
try
|
||||
{
|
||||
var byond = new ByondManager(byondIOManager, byondInstaller, eventConsumer, loggerFactory.CreateLogger<ByondManager>());
|
||||
|
||||
@@ -3,9 +3,10 @@ using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Jobs;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Repository
|
||||
{
|
||||
@@ -48,11 +49,6 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// </summary>
|
||||
readonly ILogger<RepositoryManager> logger;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RepositorySettings"/> for the <see cref="RepositoryManager"/>
|
||||
/// </summary>
|
||||
readonly RepositorySettings repositorySettings;
|
||||
|
||||
/// <summary>
|
||||
/// Used for controlling single access to the <see cref="IRepository"/>
|
||||
/// </summary>
|
||||
@@ -67,15 +63,13 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="repositoryLogger">The value of <see cref="repositoryLogger"/></param>
|
||||
/// <param name="logger">The value of <see cref="logger"/></param>
|
||||
/// <param name="repositorySettings">The value of <see cref="repositorySettings"/></param>
|
||||
public RepositoryManager(
|
||||
ILibGit2RepositoryFactory repositoryFactory,
|
||||
ILibGit2Commands commands,
|
||||
IIOManager ioManager,
|
||||
IEventConsumer eventConsumer,
|
||||
ILogger<Repository> repositoryLogger,
|
||||
ILogger<RepositoryManager> logger,
|
||||
RepositorySettings repositorySettings)
|
||||
ILogger<RepositoryManager> logger)
|
||||
{
|
||||
this.repositoryFactory = repositoryFactory ?? throw new ArgumentNullException(nameof(repositoryFactory));
|
||||
this.commands = commands ?? throw new ArgumentNullException(nameof(commands));
|
||||
@@ -83,7 +77,6 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
this.repositoryLogger = repositoryLogger ?? throw new ArgumentNullException(nameof(repositoryLogger));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.repositorySettings = repositorySettings ?? throw new ArgumentNullException(nameof(repositorySettings));
|
||||
semaphore = new SemaphoreSlim(1);
|
||||
}
|
||||
|
||||
@@ -106,7 +99,7 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
lock (semaphore)
|
||||
{
|
||||
if (CloneInProgress)
|
||||
throw new InvalidOperationException("The repository is already being cloned!");
|
||||
throw new JobException(ErrorCode.RepoCloning);
|
||||
CloneInProgress = true;
|
||||
}
|
||||
|
||||
@@ -179,33 +172,33 @@ namespace Tgstation.Server.Host.Components.Repository
|
||||
logger.LogTrace("Begin LoadRepository...");
|
||||
lock (semaphore)
|
||||
if (CloneInProgress)
|
||||
throw new InvalidOperationException("The repository is being cloned!");
|
||||
using (var context = await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
|
||||
try
|
||||
{
|
||||
var libGitRepo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken).ConfigureAwait(false);
|
||||
throw new JobException(ErrorCode.RepoCloning);
|
||||
using var context = await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var libGitRepo = await repositoryFactory.CreateFromPath(ioManager.ResolvePath(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (libGitRepo == null)
|
||||
return null;
|
||||
|
||||
return new Repository(
|
||||
libGitRepo,
|
||||
commands,
|
||||
ioManager,
|
||||
eventConsumer,
|
||||
repositoryFactory,
|
||||
repositoryLogger, () =>
|
||||
{
|
||||
logger.LogTrace("Releasing semaphore due to Repository disposal...");
|
||||
semaphore.Release();
|
||||
});
|
||||
}
|
||||
catch (RepositoryNotFoundException e)
|
||||
{
|
||||
logger.LogDebug("Repository not found!");
|
||||
logger.LogTrace("Exception: {0}", e);
|
||||
if (libGitRepo == null)
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Repository(
|
||||
libGitRepo,
|
||||
commands,
|
||||
ioManager,
|
||||
eventConsumer,
|
||||
repositoryFactory,
|
||||
repositoryLogger, () =>
|
||||
{
|
||||
logger.LogTrace("Releasing semaphore due to Repository disposal...");
|
||||
semaphore.Release();
|
||||
});
|
||||
}
|
||||
catch (RepositoryNotFoundException e)
|
||||
{
|
||||
logger.LogDebug("Repository not found!");
|
||||
logger.LogTrace("Exception: {0}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -865,7 +865,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
CancelRight = (ulong)DreamDaemonRights.Shutdown,
|
||||
CancelRightsType = RightsType.DreamDaemon
|
||||
};
|
||||
await jobManager.RegisterOperation(job, async (j, databaseContext, progressFunction, ct) =>
|
||||
await jobManager.RegisterOperation(job, async (j, databaseContextFactory, progressFunction, ct) =>
|
||||
{
|
||||
using (await SemaphoreSlimContext.Lock(Semaphore, ct).ConfigureAwait(false))
|
||||
await LaunchImplNoLock(true, true, reattachInfo, ct).ConfigureAwait(false);
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
CancelRight = (ulong)ByondRights.CancelInstall,
|
||||
Instance = Instance
|
||||
};
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContextFactory, progressHandler, ct) => byondManager.ChangeVersion(installingVersion, ct), cancellationToken).ConfigureAwait(false);
|
||||
result.InstallJob = job.ToApi();
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Instance = Instance,
|
||||
StartedBy = AuthenticationContext.User
|
||||
};
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, innerCt) => instance.Watchdog.Launch(innerCt), cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContextFactory, progressHandler, innerCt) => instance.Watchdog.Launch(innerCt), cancellationToken).ConfigureAwait(false);
|
||||
return Accepted(job.ToApi());
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var watchdog = instanceManager.GetInstance(Instance).Watchdog;
|
||||
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressReporter, ct) => watchdog.Restart(false, ct), cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContextFactory, progressReporter, ct) => watchdog.Restart(false, ct), cancellationToken).ConfigureAwait(false);
|
||||
return Accepted(job.ToApi());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,7 +505,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
StartedBy = AuthenticationContext.User
|
||||
};
|
||||
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressHandler, ct) => instanceManager.MoveInstance(originalModel, rawPath, ct), cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContextFactory, progressHandler, ct) => instanceManager.MoveInstance(originalModel, rawPath, ct), cancellationToken).ConfigureAwait(false);
|
||||
api.MoveJob = job.ToApi();
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
// If the DB doesn't have it, check the local set
|
||||
if (revisionInfo == default)
|
||||
revisionInfo = databaseContext.RevisionInformations.Local.Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id).FirstOrDefault();
|
||||
revisionInfo = databaseContext
|
||||
.RevisionInformations
|
||||
.Local
|
||||
.Where(x => x.CommitSha == repoSha && x.Instance.Id == instance.Id)
|
||||
.FirstOrDefault();
|
||||
|
||||
var needsDbUpdate = revisionInfo == default;
|
||||
if (needsDbUpdate)
|
||||
@@ -193,7 +197,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Instance = Instance
|
||||
};
|
||||
var api = currentModel.ToApi();
|
||||
await jobManager.RegisterOperation(job, async (paramJob, databaseContext, progressReporter, ct) =>
|
||||
await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) =>
|
||||
{
|
||||
using var repos = await repoManager.CloneRepository(new Uri(origin), cloneBranch, currentModel.AccessUser, currentModel.AccessToken, progressReporter, ct).ConfigureAwait(false);
|
||||
if (repos == null)
|
||||
@@ -202,9 +206,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
Id = Instance.Id
|
||||
};
|
||||
databaseContext.Instances.Attach(instance);
|
||||
if (await PopulateApi(api, repos, databaseContext, instance, ct).ConfigureAwait(false))
|
||||
await databaseContext.Save(ct).ConfigureAwait(false);
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
databaseContext.Instances.Attach(instance);
|
||||
if (await PopulateApi(api, repos, databaseContext, instance, ct).ConfigureAwait(false))
|
||||
await databaseContext.Save(ct).ConfigureAwait(false);
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
api.Origin = model.Origin;
|
||||
@@ -246,7 +255,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
Instance = Instance
|
||||
};
|
||||
var api = currentModel.ToApi();
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContext, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.RegisterOperation(job, (paramJob, databaseContextFactory, progressReporter, ct) => instanceManager.GetInstance(Instance).RepositoryManager.DeleteRepository(cancellationToken), cancellationToken).ConfigureAwait(false);
|
||||
api.ActiveJob = job.ToApi();
|
||||
return Accepted(api);
|
||||
}
|
||||
@@ -435,7 +444,8 @@ namespace Tgstation.Server.Host.Controllers
|
||||
CancelRight = (ulong)RepositoryRights.CancelPendingChanges,
|
||||
};
|
||||
|
||||
await jobManager.RegisterOperation(job, async (paramJob, databaseContext, progressReporter, ct) =>
|
||||
// Time to access git, do it in a job
|
||||
await jobManager.RegisterOperation(job, async (paramJob, databaseContextFactory, progressReporter, ct) =>
|
||||
{
|
||||
using var repo = await repoManager.LoadRepository(ct).ConfigureAwait(false);
|
||||
if (repo == null)
|
||||
@@ -475,15 +485,57 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
Id = Instance.Id
|
||||
};
|
||||
databaseContext.Instances.Attach(attachedInstance);
|
||||
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
|
||||
Task CallLoadRevInfo(Models.TestMerge testMergeToAdd = null, string lastOriginCommitSha = null) => databaseContextFactory
|
||||
.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
databaseContext.Instances.Attach(attachedInstance);
|
||||
var needsUpdate = await LoadRevisionInformation(
|
||||
repo,
|
||||
databaseContext,
|
||||
attachedInstance,
|
||||
lastOriginCommitSha,
|
||||
x => lastRevisionInfo = x,
|
||||
ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (testMergeToAdd != null)
|
||||
{
|
||||
// rev info may have already loaded the user
|
||||
var mergedBy = databaseContext.Users.Local.FirstOrDefault(x => x.Id == AuthenticationContext.User.Id);
|
||||
if (mergedBy == default)
|
||||
{
|
||||
mergedBy = new Models.User
|
||||
{
|
||||
Id = AuthenticationContext.User.Id
|
||||
};
|
||||
|
||||
databaseContext.Users.Attach(mergedBy);
|
||||
}
|
||||
|
||||
testMergeToAdd.MergedBy = mergedBy;
|
||||
|
||||
lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
|
||||
{
|
||||
TestMerge = testMergeToAdd
|
||||
});
|
||||
lastRevisionInfo.PrimaryTestMerge = testMergeToAdd;
|
||||
|
||||
databaseContext.Users.Attach(testMergeToAdd.MergedBy);
|
||||
}
|
||||
|
||||
if (needsUpdate)
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
});
|
||||
|
||||
await CallLoadRevInfo().ConfigureAwait(false);
|
||||
|
||||
// apply new rev info, tracking applied test merges
|
||||
async Task UpdateRevInfo()
|
||||
async Task UpdateRevInfo(Models.TestMerge testMergeToAdd = null)
|
||||
{
|
||||
var last = lastRevisionInfo;
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, last.OriginCommitSha, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
|
||||
await CallLoadRevInfo(testMergeToAdd, last.OriginCommitSha).ConfigureAwait(false);
|
||||
lastRevisionInfo.ActiveTestMerges.AddRange(last.ActiveTestMerges);
|
||||
}
|
||||
|
||||
@@ -531,7 +583,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
throw new JobException(ErrorCode.RepoSwappedShaOrReference);
|
||||
|
||||
await repo.CheckoutObject(committish, NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
|
||||
await CallLoadRevInfo().ConfigureAwait(false); // we've either seen origin before or what we're checking out is on origin
|
||||
}
|
||||
else
|
||||
NextProgressReporter()(100);
|
||||
@@ -542,7 +594,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
throw new JobException(ErrorCode.RepoReferenceNotTracking);
|
||||
await repo.ResetToOrigin(NextProgressReporter(), ct).ConfigureAwait(false);
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), true, ct).ConfigureAwait(false);
|
||||
await LoadRevisionInformation(repo, databaseContext, attachedInstance, null, x => lastRevisionInfo = x, ct).ConfigureAwait(false);
|
||||
await CallLoadRevInfo().ConfigureAwait(false);
|
||||
|
||||
// repo head is on origin so force this
|
||||
// will update the db if necessary
|
||||
@@ -559,10 +611,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
I.PullRequestRevision = null;
|
||||
|
||||
var gitHubClient = currentModel.AccessToken != null
|
||||
? gitHubClientFactory.CreateClient(currentModel.AccessToken)
|
||||
: (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken)
|
||||
? gitHubClientFactory.CreateClient()
|
||||
: gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken));
|
||||
? gitHubClientFactory.CreateClient(currentModel.AccessToken)
|
||||
: (String.IsNullOrEmpty(generalConfiguration.GitHubAccessToken)
|
||||
? gitHubClientFactory.CreateClient()
|
||||
: gitHubClientFactory.CreateClient(generalConfiguration.GitHubAccessToken));
|
||||
|
||||
var repoOwner = repo.GitHubOwner;
|
||||
var repoName = repo.GitHubRepoName;
|
||||
@@ -600,14 +652,20 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (!cantSearch)
|
||||
{
|
||||
var dbPull = await databaseContext.RevisionInformations
|
||||
.Where(x => x.Instance.Id == Instance.Id
|
||||
&& x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
|
||||
&& x.ActiveTestMerges.Count <= model.NewTestMerges.Count
|
||||
&& x.ActiveTestMerges.Count > 0)
|
||||
.Include(x => x.ActiveTestMerges)
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
List<Models.RevisionInformation> dbPull = null;
|
||||
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
dbPull = await databaseContext.RevisionInformations
|
||||
.Where(x => x.Instance.Id == Instance.Id
|
||||
&& x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
|
||||
&& x.ActiveTestMerges.Count <= model.NewTestMerges.Count
|
||||
&& x.ActiveTestMerges.Count > 0)
|
||||
.Include(x => x.ActiveTestMerges)
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// split here cause this bit has to be done locally
|
||||
revInfoWereLookingFor = dbPull
|
||||
@@ -619,7 +677,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
&& (y.Comment?.Trim().ToUpperInvariant() == z.Comment?.Trim().ToUpperInvariant() || z.Comment == null))))
|
||||
.FirstOrDefault();
|
||||
|
||||
if (revInfoWereLookingFor == null && model.NewTestMerges.Count > 1)
|
||||
if (revInfoWereLookingFor == default && model.NewTestMerges.Count > 1)
|
||||
{
|
||||
// okay try to add at least SOME prs we've seen before
|
||||
var search = model.NewTestMerges.ToList();
|
||||
@@ -669,20 +727,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (needToApplyRemainingPrs)
|
||||
{
|
||||
// an invocation of LoadRevisionInformation could have already loaded this user
|
||||
var contextUser = databaseContext.Users.Local.Where(x => x.Id == AuthenticationContext.User.Id).FirstOrDefault();
|
||||
if (contextUser == default)
|
||||
{
|
||||
// No reason to call the DB, just attach it
|
||||
contextUser = new Models.User
|
||||
{
|
||||
Id = AuthenticationContext.User.Id
|
||||
};
|
||||
databaseContext.Users.Attach(contextUser);
|
||||
}
|
||||
else
|
||||
Logger.LogTrace("Skipping attaching the user to the database context as it is already loaded!");
|
||||
|
||||
foreach (var I in model.NewTestMerges)
|
||||
{
|
||||
Octokit.PullRequest pr = null;
|
||||
@@ -740,8 +784,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
++doneSteps;
|
||||
|
||||
var revInfoUpdateTask = UpdateRevInfo();
|
||||
|
||||
// MergedBy will be set later
|
||||
var tm = new Models.TestMerge
|
||||
{
|
||||
Author = pr?.User.Login ?? errorMessage,
|
||||
@@ -750,18 +793,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
TitleAtMerge = pr?.Title ?? errorMessage ?? String.Empty,
|
||||
Comment = I.Comment,
|
||||
Number = I.Number,
|
||||
MergedBy = contextUser,
|
||||
PullRequestRevision = I.PullRequestRevision,
|
||||
Url = pr?.HtmlUrl ?? errorMessage
|
||||
};
|
||||
|
||||
await revInfoUpdateTask.ConfigureAwait(false);
|
||||
|
||||
lastRevisionInfo.PrimaryTestMerge = tm;
|
||||
lastRevisionInfo.ActiveTestMerges.Add(new RevInfoTestMerge
|
||||
{
|
||||
TestMerge = tm
|
||||
});
|
||||
await UpdateRevInfo(tm).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -772,8 +808,6 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await repo.Sychronize(currentModel.AccessUser, currentModel.AccessToken, currentModel.CommitterName, currentModel.CommitterEmail, NextProgressReporter(), false, ct).ConfigureAwait(false);
|
||||
await UpdateRevInfo().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await databaseContext.Save(ct).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Database
|
||||
/// <summary>
|
||||
/// Factory for scoping usage of <see cref="IDatabaseContext"/>s. Meant for use by <see cref="Components"/>
|
||||
/// </summary>
|
||||
interface IDatabaseContextFactory
|
||||
public interface IDatabaseContextFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Run an <paramref name="operation"/> in the scope of an <see cref="IDatabaseContext"/>
|
||||
|
||||
@@ -23,10 +23,10 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// Registers a given <see cref="Job"/> and begins running it
|
||||
/// </summary>
|
||||
/// <param name="job">The <see cref="Job"/></param>
|
||||
/// <param name="operation">The operation to run taking the started <see cref="Job"/>, a <see cref="IDatabaseContext"/>, progress reporter <see cref="Action{T1}"/> and a <see cref="CancellationToken"/></param>
|
||||
/// <param name="operation">The operation to run taking the started <see cref="Job"/>, a <see cref="IDatabaseContextFactory"/>, progress reporter <see cref="Action{T1}"/> and a <see cref="CancellationToken"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing a running operation</returns>
|
||||
Task RegisterOperation(Job job, Func<Job, IDatabaseContext, Action<int>, CancellationToken, Task> operation, CancellationToken cancellationToken);
|
||||
Task RegisterOperation(Job job, Func<Job, IDatabaseContextFactory, Action<int>, CancellationToken, Task> operation, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Wait for a given <paramref name="job"/> to complete
|
||||
|
||||
@@ -75,50 +75,56 @@ namespace Tgstation.Server.Host.Jobs
|
||||
/// <param name="operation">The operation for the <paramref name="job"/></param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task RunJob(Job job, Func<Job, IDatabaseContext, CancellationToken, Task> operation, CancellationToken cancellationToken)
|
||||
async Task RunJob(Job job, Func<Job, IDatabaseContextFactory, CancellationToken, Task> operation, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails);
|
||||
try
|
||||
{
|
||||
var oldJob = job;
|
||||
job = new Job { Id = oldJob.Id };
|
||||
|
||||
await operation(job, databaseContextFactory, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogDebug("Job {0} completed!", job.Id);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.LogDebug("Job {0} cancelled!", job.Id);
|
||||
job.Cancelled = true;
|
||||
}
|
||||
catch (JobException e)
|
||||
{
|
||||
job.ErrorCode = e.ErrorCode;
|
||||
job.ExceptionDetails = e.Message;
|
||||
LogRegularException();
|
||||
if (e.InnerException != null)
|
||||
logger.LogDebug(
|
||||
"Inner exception for job {0}: {1}",
|
||||
job.Id,
|
||||
e.InnerException is JobException
|
||||
? e.InnerException.Message
|
||||
: e.InnerException.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
job.ExceptionDetails = e.ToString();
|
||||
LogRegularException();
|
||||
}
|
||||
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
void LogRegularException() => logger.LogDebug("Job {0} exited with error! Exception: {1}", job.Id, job.ExceptionDetails);
|
||||
try
|
||||
var attachedJob = new Job
|
||||
{
|
||||
var oldJob = job;
|
||||
job = new Job { Id = oldJob.Id };
|
||||
databaseContext.Jobs.Attach(job);
|
||||
Id = job.Id
|
||||
};
|
||||
|
||||
await operation(job, databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
logger.LogDebug("Job {0} completed!", job.Id);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.LogDebug("Job {0} cancelled!", job.Id);
|
||||
job.Cancelled = true;
|
||||
}
|
||||
catch (JobException e)
|
||||
{
|
||||
job.ErrorCode = e.ErrorCode;
|
||||
job.ExceptionDetails = e.Message;
|
||||
LogRegularException();
|
||||
if (e.InnerException != null)
|
||||
logger.LogDebug(
|
||||
"Inner exception for job {0}: {1}",
|
||||
job.Id,
|
||||
e.InnerException is JobException
|
||||
? e.InnerException.Message
|
||||
: e.InnerException.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
job.ExceptionDetails = e.ToString();
|
||||
LogRegularException();
|
||||
}
|
||||
finally
|
||||
{
|
||||
job.StoppedAt = DateTimeOffset.Now;
|
||||
}
|
||||
databaseContext.Jobs.Attach(attachedJob);
|
||||
attachedJob.StoppedAt = DateTimeOffset.Now;
|
||||
attachedJob.ExceptionDetails = job.ExceptionDetails;
|
||||
attachedJob.ErrorCode = job.ErrorCode;
|
||||
attachedJob.Cancelled = job.Cancelled;
|
||||
|
||||
await databaseContext.Save(default).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
@@ -135,7 +141,7 @@ namespace Tgstation.Server.Host.Jobs
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RegisterOperation(Job job, Func<Job, IDatabaseContext, Action<int>, CancellationToken, Task> operation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
|
||||
public Task RegisterOperation(Job job, Func<Job, IDatabaseContextFactory, Action<int>, CancellationToken, Task> operation, CancellationToken cancellationToken) => databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
if (job == null)
|
||||
throw new ArgumentNullException(nameof(job));
|
||||
|
||||
Reference in New Issue
Block a user