2using System.Collections.Generic;
3using System.Diagnostics.CodeAnalysis;
7using System.Threading.Tasks;
9using Microsoft.EntityFrameworkCore;
10using Microsoft.Extensions.Logging;
116 ILogger<DmbFactory>
logger,
123 this.logger =
logger ??
throw new ArgumentNullException(nameof(
logger));
124 this.metadata =
metadata ??
throw new ArgumentNullException(nameof(
metadata));
138 ArgumentNullException.ThrowIfNull(job);
141 if (newProvider ==
null)
151 newProvider.CompileJob,
156 ValueTask dmbDisposeTask;
159 dmbDisposeTask =
nextDmbProvider?.DisposeAsync() ?? ValueTask.CompletedTask;
163 var temp = Interlocked.Exchange(ref
newerDmbTcs,
new TaskCompletionSource());
167 await dmbDisposeTask;
174 throw new InvalidOperationException(
"No .dmb available!");
176 throw new ArgumentOutOfRangeException(nameof(lockCount), lockCount,
"lockCount must be greater than or equal to 0!");
181 logger.LogTrace(
"Compile job {jobId} lock count now: {lockCount}", jobId, incremented);
187 public async Task
StartAsync(CancellationToken cancellationToken)
195 .Where(x => x.Job.Instance!.Id ==
metadata.Id)
196 .OrderByDescending(x => x.Job.StoppedAt)
197 .FirstOrDefaultAsync(cancellationToken));
214 public async Task
StopAsync(CancellationToken cancellationToken)
221 using (cancellationToken.Register(() =>
cleanupCts.Cancel()))
231#pragma warning disable CA1506
234 ArgumentNullException.ThrowIfNull(compileJob);
237 var compileJobId = compileJob.Require(x => x.Id);
238 logger.LogTrace(
"Loading compile job {id}...", compileJobId);
240 async db => compileJob = await db
243 .Where(x => x!.Id == compileJobId)
244 .Include(x => x.Job!)
245 .ThenInclude(x => x.StartedBy)
246 .Include(x => x.Job!)
247 .ThenInclude(x => x.Instance)
248 .Include(x => x.RevisionInformation!)
249 .ThenInclude(x => x.PrimaryTestMerge!)
250 .ThenInclude(x => x.MergedBy)
251 .Include(x => x.RevisionInformation!)
252 .ThenInclude(x => x.ActiveTestMerges!)
253 .ThenInclude(x => x.TestMerge!)
254 .ThenInclude(x => x.MergedBy)
255 .FirstAsync(cancellationToken));
260 logger.LogWarning(
"Error loading compile job, bad engine version: {engineVersion}", compileJob.
EngineVersion);
264 engineVersion = engineVersionNullable!;
271 logger.LogTrace(
"Setting missing StoppedAt for CompileJob.Job #{id}...", compileJob.
Job.
Id);
272 compileJob.Job.StoppedAt = DateTimeOffset.UtcNow;
275 var providerSubmitted =
false;
279 if (providerSubmitted)
286 const string LegacyADirectoryName =
"A";
287 const string LegacyBDirectoryName =
"B";
291 newProvider.Directory,
292 newProvider.DmbName),
295 if (!dmbExistsAtRoot)
297 logger.LogTrace(
"Didn't find .dmb at game directory root, checking A/B dirs...");
300 newProvider.Directory,
301 LegacyADirectoryName,
302 newProvider.DmbName),
306 newProvider.Directory,
307 LegacyBDirectoryName,
308 newProvider.DmbName),
311 if (!(await primaryCheckTask && await secondaryCheckTask))
313 logger.LogWarning(
"Error loading compile job, .dmb missing!");
319 logger.LogDebug(
"Creating legacy two folder .dmb provider targeting {aDirName} directory...", LegacyADirectoryName);
333 providerSubmitted =
true;
335 logger.LogTrace(
"Compile job {id} lock count now: {lockCount}", compileJobId, value);
341 if (!providerSubmitted)
342 await newProvider.DisposeAsync();
345#pragma warning restore CA1506
348#pragma warning disable CA1506
351 List<long> jobIdsToSkip;
357 List<string>? jobUidsToNotErase =
null;
360 if (jobIdsToSkip.Count > 0)
364 jobUidsToNotErase = (await db
368 x => x.Job.Instance!.Id ==
metadata.Id
369 && jobIdsToSkip.Contains(x.Id!.Value))
370 .Select(x => x.DirectoryName!.Value)
371 .ToListAsync(cancellationToken))
372 .Select(x => x.ToString())
377 jobUidsToNotErase =
new List<string>();
381 logger.LogTrace(
"We will not clean the following directories: {directoriesToNotClean}", String.Join(
", ", jobUidsToNotErase));
388 var tasks = directories.Select(async x =>
391 if (jobUidsToNotErase.Contains(nameOnly))
393 logger.LogDebug(
"Cleaning unused game folder: {dirName}...", nameOnly);
397 await DeleteCompileJobContent(x, cancellationToken);
399 catch (OperationCanceledException)
405 logger.LogWarning(e,
"Error deleting directory {dirName}!", x);
409 await Task.WhenAll(tasks);
411#pragma warning restore CA1506
427 async Task HandleCleanup()
433 var deploymentJob = remoteDeploymentManager.
MarkInactive(job, CancellationToken.None);
438 async Task WrapThrowableTasks()
450 await Task.WhenAll(otherTask, WrapThrowableTasks());
455 var jobId = job.Require(x => x.Id);
460 logger.LogDebug(
"Cleaning lock-free compile job {id} => {dirName}", jobId, job.
DirectoryName);
466 logger.LogTrace(
"Compile job {id} lock count now: {lockCount}", jobId, decremented);
469 logger.LogError(
"Extra Dispose of DmbProvider for CompileJob {compileJobId}!", jobId);
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 .
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 o...
readonly Dictionary< long, int > jobLockCounts
Map of CompileJob.JobIds to locks on them.
readonly IIOManager ioManager
The IIOManager for the DmbFactory.
CompileJob? LatestCompileJob()
Gets the latest CompileJob. The latest CompileJob or null if none are available.
async ValueTask LoadCompileJob(CompileJob job, Action< bool >? activationAction, CancellationToken cancellationToken)
Load a new job into the ICompileJobSink. A ValueTask representing the running operation.
readonly IEventConsumer eventConsumer
The IEventConsumer for DmbFactory.
DmbFactory(IDatabaseContextFactory databaseContextFactory, IIOManager ioManager, IRemoteDeploymentManagerFactory remoteDeploymentManagerFactory, IEventConsumer eventConsumer, ILogger< DmbFactory > logger, Api.Models.Instance metadata)
Initializes a new instance of the DmbFactory class.
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.
IDmbProvider? nextDmbProvider
The latest DmbProvider.
IDmbProvider LockNextDmb(int lockCount)
Gets the next IDmbProvider. DmbAvailable is a precondition. A new IDmbProvider.
Task OnNewerDmb
Get a Task that completes when the result of a call to LockNextDmb will be different than the previou...
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, CancellationToken cancellationToken)
Gets a IDmbProvider for a given CompileJob. A ValueTask<TResult> resulting in a new IDmbProvider repr...
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.
Models.CompileJob CompileJob
The CompileJob of the .dmb.
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.
EventType
Types of events. Mirror in tgs.dm. Prefer last listed name for script.