2using System.Collections.Generic;
3using System.Diagnostics.CodeAnalysis;
6using System.Runtime.CompilerServices;
9using System.Threading.Tasks;
11using Microsoft.EntityFrameworkCore;
12using Microsoft.Extensions.Logging;
130 ILogger<DmbFactory>
logger,
138 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
139 this.metadata =
metadata ??
throw new ArgumentNullException(nameof(
metadata));
159 ArgumentNullException.ThrowIfNull(job);
161 var (dmbProvider, lockManager) = await
FromCompileJobInternal(job,
"Compile job loading", cancellationToken);
162 if (dmbProvider ==
null)
165 if (lockManager ==
null)
166 throw new InvalidOperationException($
"We did not acquire the first lock for compile job {job.Id}!");
175 lockManager.CompileJob,
180 ValueTask dmbDisposeTask;
187 var temp = Interlocked.Exchange(ref
newerDmbTcs,
new TaskCompletionSource());
191 await dmbDisposeTask;
195 public IDmbProvider LockNextDmb(
string reason, [CallerFilePath]
string? callerFile =
null, [CallerLineNumber]
int callerLine =
default)
198 throw new InvalidOperationException(
"No .dmb available!");
204 public async Task
StartAsync(CancellationToken cancellationToken)
212 .Where(x => x.Job.Instance!.Id ==
metadata.Id)
213 .OrderByDescending(x => x.Job.StoppedAt)
214 .FirstOrDefaultAsync(cancellationToken));
232 public async Task
StopAsync(CancellationToken cancellationToken)
241 using (cancellationToken.Register(() =>
cleanupCts.Cancel()))
251#pragma warning disable CA1506
252 public async ValueTask<IDmbProvider?>
FromCompileJob(
CompileJob compileJob,
string reason, CancellationToken cancellationToken, [CallerFilePath]
string? callerFile =
null, [CallerLineNumber]
int callerLine =
default)
254 ArgumentNullException.ThrowIfNull(compileJob);
255 ArgumentNullException.ThrowIfNull(reason);
257 var (dmb, _) = await
FromCompileJobInternal(compileJob, reason, cancellationToken, callerFile, callerLine);
263#pragma warning disable CA1506
266 List<long> jobIdsToSkip;
272 List<string>? jobUidsToNotErase =
null;
275 if (jobIdsToSkip.Count > 0)
279 jobUidsToNotErase = (await db
283 x => x.Job.Instance!.Id ==
metadata.Id
284 && jobIdsToSkip.Contains(x.Id!.Value))
285 .Select(x => x.DirectoryName!.Value)
286 .ToListAsync(cancellationToken))
287 .Select(x => x.ToString())
292 jobUidsToNotErase =
new List<string>();
296 logger.LogTrace(
"We will not clean the following directories: {directoriesToNotClean}", String.Join(
", ", jobUidsToNotErase));
303 var tasks = directories.Select<string, ValueTask>(async x =>
306 if (jobUidsToNotErase.Contains(nameOnly))
308 logger.LogDebug(
"Cleaning unused game folder: {dirName}...", nameOnly);
314 catch (
Exception e) when (e is not OperationCanceledException)
316 logger.LogWarning(e,
"Error deleting directory {dirName}!", x);
322#pragma warning restore CA1506
332 return provider.CompileJob;
347 var compileJobId = compileJob.Require(x => x.Id);
350 return (
DmbProvider: lockManager.AddLock(reason, callerFile, callerLine), LockManager:
null);
352 logger.LogTrace(
"Loading compile job {id}...", compileJobId);
354 async db => compileJob = await db
357 .Where(x => x!.Id == compileJobId)
358 .Include(x => x.Job!)
359 .ThenInclude(x => x.StartedBy)
360 .Include(x => x.Job!)
361 .ThenInclude(x => x.Instance)
362 .Include(x => x.RevisionInformation!)
363 .ThenInclude(x => x.PrimaryTestMerge!)
364 .ThenInclude(x => x.MergedBy)
365 .Include(x => x.RevisionInformation!)
366 .ThenInclude(x => x.ActiveTestMerges!)
367 .ThenInclude(x => x.TestMerge!)
368 .ThenInclude(x => x.MergedBy)
369 .FirstAsync(cancellationToken));
374 logger.LogError(
"Error loading compile job, bad engine version: {engineVersion}", compileJob.
EngineVersion);
378 engineVersion = engineVersionNullable!;
385 logger.LogTrace(
"Setting missing StoppedAt for CompileJob.Job #{id}...", compileJob.
Job.
Id);
386 compileJob.Job.StoppedAt = DateTimeOffset.UtcNow;
389 var providerSubmitted =
false;
392 if (providerSubmitted)
399 const string LegacyADirectoryName =
"A";
400 const string LegacyBDirectoryName =
"B";
404 newProvider.Directory,
405 newProvider.DmbName),
408 if (!dmbExistsAtRoot)
410 logger.LogTrace(
"Didn't find .dmb at game directory root, checking A/B dirs...");
413 newProvider.Directory,
414 LegacyADirectoryName,
415 newProvider.DmbName),
419 newProvider.Directory,
420 LegacyBDirectoryName,
421 newProvider.DmbName),
424 if (!(await primaryCheckTask && await secondaryCheckTask))
426 logger.LogWarning(
"Error loading compile job, .dmb missing!");
432 logger.LogDebug(
"Creating legacy two folder .dmb provider targeting {aDirName} directory...", LegacyADirectoryName);
433#pragma warning disable CA2000
435#pragma warning restore CA2000
446 providerSubmitted =
true;
450 lockedProvider = lockManager.AddLock(reason, callerFile, callerLine);
454 return (
DmbProvider: lockedProvider, LockManager: lockManager);
459 if (!providerSubmitted)
460 await newProvider.DisposeAsync();
477 async Task WrapThrowableTasks()
485 var deploymentJob = remoteDeploymentManager.
MarkInactive(job, cancellationToken);
491 catch (
Exception ex) when (ex is not OperationCanceledException)
497 return Task.WhenAll(otherTask, WrapThrowableTasks());
523 logger.LogTrace(
"Entering lock logging loop");
524 CancellationToken cancellationToken =
lockLogCts.Token;
526 while (!cancellationToken.IsCancellationRequested)
529 var builder =
new StringBuilder();
533 lockManager.LogLockStats(builder);
535 logger.LogTrace(
"Periodic deployment log states report:{newLine}{report}", Environment.NewLine, builder);
539 catch (OperationCanceledException ex)
541 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 LogLockStates.
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 Task LogLockStates()
Lock all DeploymentLockManagers states.
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...
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...
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.