116 protected override async ValueTask
DoSwap(CancellationToken cancellationToken)
118 logger.LogTrace(
"Begin DoSwap, mirroring task complete: {complete}...",
mirroringTask.IsCompleted);
119 var mirroredDir = await
mirroringTask.WaitAsync(cancellationToken);
120 if (mirroredDir ==
null)
123 cancellationToken.ThrowIfCancellationRequested();
124 throw new InvalidOperationException(
"mirroringTask was cancelled without us being cancelled?");
127 var goAheadTcs =
new TaskCompletionSource();
130 async
void DisposeOfOldDirectory()
132 var directoryMoved =
false;
133 var disposeGuid = Guid.NewGuid();
134 var disposePath = disposeGuid.ToString();
135 logger.LogTrace(
"Moving Live directory to {path} for deletion...", disposeGuid);
139 directoryMoved =
true;
140 goAheadTcs.SetResult();
141 logger.LogTrace(
"Deleting old Live directory {path}...", disposePath);
143 logger.LogTrace(
"Completed async cleanup of old Live directory: {disposePath}", disposePath);
145 catch (DirectoryNotFoundException ex)
147 logger.LogDebug(ex,
"Live directory appears to not exist");
149 goAheadTcs.SetResult();
153 logger.LogWarning(ex,
"Failed to delete hard linked directory: {disposePath}", disposePath);
155 goAheadTcs.SetException(ex);
159 DisposeOfOldDirectory();
160 await goAheadTcs.Task;
161 logger.LogTrace(
"Moving mirror directory {path} to Live...", mirroredDir);
163 logger.LogTrace(
"Swap complete!");
175 if (taskThrottle.HasValue && taskThrottle < 1)
176 throw new ArgumentOutOfRangeException(nameof(taskThrottle), taskThrottle,
"taskThrottle must be at least 1!");
181 var stopwatch = Stopwatch.StartNew();
182 var mirrorGuid = Guid.NewGuid();
184 logger.LogDebug(
"Starting to mirror {sourceDir} as hard links to {mirrorGuid}...",
CompileJob.DirectoryName, mirrorGuid);
189 using var semaphore = taskThrottle.HasValue ?
new SemaphoreSlim(taskThrottle.Value) :
null;
199 "Finished mirror of {sourceDir} to {mirrorGuid} in {seconds}s...",
202 stopwatch.Elapsed.TotalSeconds.ToString(
"0.##", CultureInfo.InvariantCulture));
204 catch (OperationCanceledException ex)
206 logger.LogDebug(ex,
"Cancelled while mirroring");
210 logger.LogError(ex,
"Could not mirror!");
215 logger.LogDebug(
"Cleaning up mirror attempt: {dest}", dest);
218 catch (OperationCanceledException ex2)
220 logger.LogDebug(ex2,
"Errored cleanup cancellation edge case!");
242 var dir =
new DirectoryInfo(src);
243 Task? subdirCreationTask =
null;
244 var dreamDaemonWillAcceptOutOfDirectorySymlinks = securityLevel ==
DreamDaemonSecurity.Trusted;
245 foreach (var subDirectory
in dir.EnumerateDirectories())
247 var mirroredName = Path.Combine(dest, subDirectory.Name);
250 if (subDirectory.Attributes.HasFlag(FileAttributes.ReparsePoint))
251 if (dreamDaemonWillAcceptOutOfDirectorySymlinks)
253 var target = subDirectory.ResolveLinkTarget(
false)
254 ??
throw new InvalidOperationException($
"\"{subDirectory.FullName}\" was incorrectly identified as a symlinked directory!");
255 logger.LogDebug(
"Recreating directory {name} as symlink to {target}", subDirectory.Name, target);
256 if (subdirCreationTask ==
null)
259 yield
return subdirCreationTask;
262 async Task CopyLink()
264 await subdirCreationTask.WaitAsync(cancellationToken);
265 using var lockContext = semaphore !=
null
271 yield
return CopyLink();
275 logger.LogDebug(
"Recreating symlinked directory {name} as hard links...", subDirectory.Name);
277 var checkingSubdirCreationTask =
true;
278 foreach (var copyTask
in MirrorDirectoryImpl(subDirectory.FullName, mirroredName, semaphore, securityLevel, cancellationToken))
280 if (subdirCreationTask ==
null)
282 subdirCreationTask = copyTask;
283 yield
return subdirCreationTask;
285 else if (!checkingSubdirCreationTask)
286 yield
return copyTask;
288 checkingSubdirCreationTask =
false;
292 foreach (var fileInfo
in dir.EnumerateFiles())
294 if (subdirCreationTask ==
null)
297 yield
return subdirCreationTask;
300 var sourceFile = fileInfo.FullName;
303 async Task LinkThisFile()
305 await subdirCreationTask.WaitAsync(cancellationToken);
306 using var lockContext = semaphore !=
null
310 if (fileInfo.Attributes.HasFlag(FileAttributes.ReparsePoint))
313 var target = fileInfo.ResolveLinkTarget(!dreamDaemonWillAcceptOutOfDirectorySymlinks)
314 ??
throw new InvalidOperationException($
"\"{fileInfo.FullName}\" was incorrectly identified as a symlinked file!");
316 if (dreamDaemonWillAcceptOutOfDirectorySymlinks)
318 logger.LogDebug(
"Recreating symlinked file {name} as symlink to {target}", fileInfo.Name, target.FullName);
323 logger.LogDebug(
"Recreating symlinked file {name} as hard link to {target}", fileInfo.Name, target.FullName);
331 yield
return LinkThisFile();