2using System.Collections.Generic;
3using System.Diagnostics.CodeAnalysis;
5using System.Runtime.CompilerServices;
8using System.Threading.Tasks;
10using Microsoft.EntityFrameworkCore;
11using Microsoft.Extensions.Logging;
129 ILogger<DmbFactory>
logger,
137 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
138 this.metadata =
metadata ??
throw new ArgumentNullException(nameof(
metadata));
158 ArgumentNullException.ThrowIfNull(job);
160 var (dmbProvider, lockManager) = await
FromCompileJobInternal(job,
"Compile job loading", cancellationToken);
161 if (dmbProvider ==
null)
164 if (lockManager ==
null)
165 throw new InvalidOperationException($
"We did not acquire the first lock for compile job {job.Id}!");
174 lockManager.CompileJob,
179 ValueTask dmbDisposeTask;
186 var temp = Interlocked.Exchange(ref
newerDmbTcs,
new TaskCompletionSource());
190 await dmbDisposeTask;
194 public IDmbProvider LockNextDmb(
string reason, [CallerFilePath]
string? callerFile =
null, [CallerLineNumber]
int callerLine =
default)
197 throw new InvalidOperationException(
"No .dmb available!");
203 public async Task
StartAsync(CancellationToken cancellationToken)
211 .Where(x => x.Job.Instance!.Id ==
metadata.Id)
212 .OrderByDescending(x => x.Job.StoppedAt)
213 .FirstOrDefaultAsync(cancellationToken));
231 public async Task
StopAsync(CancellationToken cancellationToken)
240 using (cancellationToken.Register(() =>
cleanupCts.Cancel()))
250#pragma warning disable CA1506
251 public async ValueTask<IDmbProvider?>
FromCompileJob(
CompileJob compileJob,
string reason, CancellationToken cancellationToken, [CallerFilePath]
string? callerFile =
null, [CallerLineNumber]
int callerLine =
default)
253 ArgumentNullException.ThrowIfNull(compileJob);
254 ArgumentNullException.ThrowIfNull(reason);
256 var (dmb, _) = await
FromCompileJobInternal(compileJob, reason, cancellationToken, callerFile, callerLine);
262#pragma warning disable CA1506
265 List<long> jobIdsToSkip;
271 List<string>? jobUidsToNotErase =
null;
274 if (jobIdsToSkip.Count > 0)
278 jobUidsToNotErase = (await db
282 x => x.Job.Instance!.Id ==
metadata.Id
283 && jobIdsToSkip.Contains(x.Id!.Value))
284 .Select(x => x.DirectoryName!.Value)
285 .ToListAsync(cancellationToken))
286 .Select(x => x.ToString())
291 jobUidsToNotErase =
new List<string>();
295 logger.LogTrace(
"We will not clean the following directories: {directoriesToNotClean}", String.Join(
", ", jobUidsToNotErase));
302 var tasks = directories.Select<string, ValueTask>(async x =>
305 if (jobUidsToNotErase.Contains(nameOnly))
307 logger.LogDebug(
"Cleaning unused game folder: {dirName}...", nameOnly);
313 catch (
Exception e) when (e is not OperationCanceledException)
315 logger.LogWarning(e,
"Error deleting directory {dirName}!", x);
321#pragma warning restore CA1506
331 return provider.CompileJob;
337 var builder =
new StringBuilder();
341 lockManager.LogLockStats(builder);
343 logger.LogTrace(
"Periodic deployment log states report:{newLine}{report}", Environment.NewLine, builder);
358 var compileJobId = compileJob.Require(x => x.Id);
361 return (
DmbProvider: lockManager.AddLock(reason, callerFile, callerLine), LockManager:
null);
363 logger.LogTrace(
"Loading compile job {id}...", compileJobId);
365 async db => compileJob = await db
368 .Where(x => x!.Id == compileJobId)
369 .Include(x => x.Job!)
370 .ThenInclude(x => x.StartedBy)
371 .Include(x => x.Job!)
372 .ThenInclude(x => x.Instance)
373 .Include(x => x.RevisionInformation!)
374 .ThenInclude(x => x.PrimaryTestMerge!)
375 .ThenInclude(x => x.MergedBy)
376 .Include(x => x.RevisionInformation!)
377 .ThenInclude(x => x.ActiveTestMerges!)
378 .ThenInclude(x => x.TestMerge!)
379 .ThenInclude(x => x.MergedBy)
380 .FirstAsync(cancellationToken));
385 logger.LogError(
"Error loading compile job, bad engine version: {engineVersion}", compileJob.
EngineVersion);
389 engineVersion = engineVersionNullable!;
396 logger.LogTrace(
"Setting missing StoppedAt for CompileJob.Job #{id}...", compileJob.
Job.
Id);
397 compileJob.Job.StoppedAt = DateTimeOffset.UtcNow;
400 var providerSubmitted =
false;
403 if (providerSubmitted)
410 const string LegacyADirectoryName =
"A";
411 const string LegacyBDirectoryName =
"B";
415 newProvider.Directory,
416 newProvider.DmbName),
419 if (!dmbExistsAtRoot)
421 logger.LogTrace(
"Didn't find .dmb at game directory root, checking A/B dirs...");
424 newProvider.Directory,
425 LegacyADirectoryName,
426 newProvider.DmbName),
430 newProvider.Directory,
431 LegacyBDirectoryName,
432 newProvider.DmbName),
435 if (!(await primaryCheckTask && await secondaryCheckTask))
437 logger.LogWarning(
"Error loading compile job, .dmb missing!");
443 logger.LogDebug(
"Creating legacy two folder .dmb provider targeting {aDirName} directory...", LegacyADirectoryName);
444#pragma warning disable CA2000
446#pragma warning restore CA2000
457 providerSubmitted =
true;
461 lockedProvider = lockManager.AddLock(reason, callerFile, callerLine);
465 return (
DmbProvider: lockedProvider, LockManager: lockManager);
470 if (!providerSubmitted)
471 await newProvider.DisposeAsync();
488 async Task WrapThrowableTasks()
496 var deploymentJob = remoteDeploymentManager.
MarkInactive(job, cancellationToken);
502 catch (
Exception ex) when (ex is not OperationCanceledException)
508 return Task.WhenAll(otherTask, WrapThrowableTasks());
534 logger.LogTrace(
"Entering lock logging loop");
535 CancellationToken cancellationToken =
lockLogCts.Token;
537 while (!cancellationToken.IsCancellationRequested)
543 catch (OperationCanceledException ex)
545 logger.LogTrace(ex,
"Exiting lock logging loop");
Information about an engine installation.
static bool TryParse(string input, out EngineVersion? engineVersion)
Attempts to parse a stringified EngineVersion.
virtual ? long Id
The ID of the entity.
Metadata about a server instance.
Guid? DirectoryName
The Game folder the results were compiled into.
DateTimeOffset? StoppedAt
When the Job stopped.
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
Manages locks on a given IDmbProvider.
IDmbProvider AddLock(string reason, [CallerFilePath] string? callerFile=null, [CallerLineNumber]int callerLine=default)
Add a lock to the managed IDmbProvider.
static DeploymentLockManager Create(IDmbProvider dmbProvider, ILogger logger, string initialLockReason, out IDmbProvider firstLock, [CallerFilePath] string? callerFile=null, [CallerLineNumber] int callerLine=default)
Create a DeploymentLockManager.
readonly IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory
The IRemoteDeploymentManagerFactory for the DmbFactory.
async ValueTask CleanUnusedCompileJobs(CancellationToken cancellationToken)
Deletes all compile jobs that are inactive in the Game folder.A ValueTask representing the running op...
DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IEventConsumer eventConsumer, IAsyncDelayer asyncDelayer, ILogger< DmbFactory > logger, Api.Models.Instance metadata)
Initializes a new instance of the DmbFactory class.
readonly IIOManager ioManager
The IIOManager for the DmbFactory.
IDmbProvider LockNextDmb(string reason, [CallerFilePath] string? callerFile=null, [CallerLineNumber] int callerLine=default)
Gets the next IDmbProvider. DmbAvailable is a precondition.A new IDmbProvider.
async ValueTask LoadCompileJob(CompileJob job, Action< bool >? activationAction, CancellationToken cancellationToken)
Load a new job into the ICompileJobSink.A ValueTask representing the running operation.
readonly IAsyncDelayer asyncDelayer
The IAsyncDelayer for the DmbFactory.
readonly IEventConsumer eventConsumer
The IEventConsumer for the DmbFactory.
readonly CancellationTokenSource lockLogCts
The CancellationTokenSource for LogLockStatesLoop.
bool DmbAvailable
If LockNextDmb will succeed.
volatile TaskCompletionSource newerDmbTcs
TaskCompletionSource resulting in the latest DmbProvider yet to exist.
readonly ILogger< DmbFactory > logger
The ILogger for the DmbFactory.
void CleanRegisteredCompileJob(CompileJob job)
Delete the Api.Models.Internal.CompileJob.DirectoryName of job .
async Task StopAsync(CancellationToken cancellationToken)
readonly Api.Models.Instance metadata
The Api.Models.Instance for the DmbFactory.
async ValueTask<(IDmbProvider? DmbProvider, DeploymentLockManager? LockManager)> FromCompileJobInternal(CompileJob compileJob, string reason, CancellationToken cancellationToken, [CallerFilePath] string? callerFile=null, [CallerLineNumber] int callerLine=default)
Gets a IDmbProvider and potentially the DeploymentLockManager for a given CompileJob.
readonly Dictionary< long, DeploymentLockManager > jobLockManagers
Map of CompileJob.JobIds to locks on them.
DeploymentLockManager? nextLockManager
The DeploymentLockManager for the latest DmbProvider.
Task OnNewerDmb
Get a Task that completes when the result of a call to LockNextDmb will be different than the previou...
async ValueTask< CompileJob?> LatestCompileJob()
Gets the latest CompileJob.A ValueTask<TResult> resulting in the latest CompileJob or null if none ar...
void LogLockStates()
Log the states of all active IDmbProviders.
readonly IDatabaseContextFactory databaseContextFactory
The IDatabaseContextFactory for the DmbFactory.
readonly CancellationTokenSource cleanupCts
The CancellationTokenSource for cleanupTask.
bool started
If the DmbFactory is "started" via IComponentService.
async ValueTask DeleteCompileJobContent(string directory, CancellationToken cancellationToken)
Handles cleaning the resources of a CompileJob.
async Task StartAsync(CancellationToken cancellationToken)
async ValueTask< IDmbProvider?> FromCompileJob(CompileJob compileJob, string reason, CancellationToken cancellationToken, [CallerFilePath] string? callerFile=null, [CallerLineNumber] int callerLine=default)
Gets a IDmbProvider for a given CompileJob.A ValueTask<TResult> resulting in a new IDmbProvider repre...
async Task LogLockStatesLoop()
Lock all DeploymentLockManagers states.
Task cleanupTask
Task representing calls to CleanRegisteredCompileJob(CompileJob).
A IDmbProvider that uses filesystem links to change directory structure underneath the server process...
const string LiveGameDirectory
The directory where the BaseProvider is symlinked to.
Job Job
See CompileJobResponse.Job.
string EngineVersion
The Version the CompileJob was made with in string form.
Runs a given disposeAction on Dispose.
Factory for IDmbProviders.
Provides absolute paths to the latest compiled .dmbs.
Factory for creating IRemoteDeploymentManagers.
IRemoteDeploymentManager CreateRemoteDeploymentManager(Api.Models.Instance metadata, RemoteGitProvider remoteGitProvider)
Creates a IRemoteDeploymentManager for a given remoteGitProvider .
void ForgetLocalStateForCompileJobs(IEnumerable< long > compileJobsIds)
Cause the IRemoteDeploymentManagerFactory to drop any local state is has for the given compileJobsIds...
ValueTask MarkInactive(CompileJob compileJob, CancellationToken cancellationToken)
Mark the deplotment for a given compileJob as inactive.
ValueTask StageDeployment(CompileJob compileJob, Action< bool >? activationCallback, CancellationToken cancellationToken)
Stage a given compileJob 's deployment.
Consumes EventTypes and takes the appropriate actions.
ValueTask HandleEvent(EventType eventType, IEnumerable< string?> parameters, bool deploymentPipeline, CancellationToken cancellationToken)
Handle a given eventType .
Factory for scoping usage of IDatabaseContexts. Meant for use by Components.
ValueTask UseContext(Func< IDatabaseContext, ValueTask > operation)
Run an operation in the scope of an IDatabaseContext.
Interface for using filesystems.
string GetFileName(string path)
Gets the file name portion of a path .
string ResolvePath()
Retrieve the full path of the current working directory.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task< IReadOnlyList< string > > GetDirectories(string path, CancellationToken cancellationToken)
Returns full directory names in a given path .
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
Task DeleteDirectory(string path, CancellationToken cancellationToken)
Recursively delete a directory, removes and does not enter any symlinks encounterd.
Task< bool > FileExists(string path, CancellationToken cancellationToken)
Check that the file at path exists.
For waiting asynchronously.
ValueTask Delay(TimeSpan timeSpan, CancellationToken cancellationToken)
Create a Task that completes after a given timeSpan .
EventType
Types of events. Mirror in tgs.dm. Prefer last listed name for script.