mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-22 20:47:28 +01:00
PosixWatchdog doesn't need hard links
- Added ISymlinkFactory.SymlinkedDirectoriesAreFiles because they are files on Linux. - Removed crazy-ass hard link junk from PosixWatchdog. - Added symlink unit tests.
This commit is contained in:
@@ -80,9 +80,11 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
if (Interlocked.Exchange(ref swapped, 1) != 0)
|
||||
throw new InvalidOperationException("Already swapped!");
|
||||
|
||||
// Note this comment from TGS3:
|
||||
// These next two lines should be atomic but this is the best we can do
|
||||
await ioManager.DeleteDirectory(LiveGameDirectory, cancellationToken);
|
||||
if (symlinkFactory.SymlinkedDirectoriesAreDeletedAsFiles)
|
||||
await ioManager.DeleteFile(LiveGameDirectory, cancellationToken);
|
||||
else
|
||||
await ioManager.DeleteDirectory(LiveGameDirectory, cancellationToken);
|
||||
|
||||
await symlinkFactory.CreateSymbolicLink(
|
||||
ioManager.ResolvePath(baseProvider.Directory),
|
||||
ioManager.ResolvePath(LiveGameDirectory),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -22,11 +21,6 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
sealed class PosixWatchdog : WindowsWatchdog
|
||||
{
|
||||
/// <summary>
|
||||
/// If the swappable game directory is currently a rename of the compile job.
|
||||
/// </summary>
|
||||
IDmbProvider hardLinkedDmb;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PosixWatchdog"/> class.
|
||||
/// </summary>
|
||||
@@ -84,77 +78,10 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task ApplyInitialDmb(CancellationToken cancellationToken) => Task.CompletedTask; // not necessary to hold initial .dmb on Linux because of based inode deletes
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task InitialLink(CancellationToken cancellationToken)
|
||||
protected override Task ApplyInitialDmb(CancellationToken cancellationToken)
|
||||
{
|
||||
// The logic to check for an active live directory is in SwappableDmbProvider, so we just do it again here for safety
|
||||
Logger.LogTrace("Hard linking compile job...");
|
||||
|
||||
// Symlinks are counted as a file on linux??
|
||||
if (await GameIOManager.DirectoryExists(ActiveSwappable.Directory, cancellationToken))
|
||||
await GameIOManager.DeleteDirectory(ActiveSwappable.Directory, cancellationToken);
|
||||
else
|
||||
await GameIOManager.DeleteFile(ActiveSwappable.Directory, cancellationToken);
|
||||
|
||||
// Instead of symlinking to begin with we actually rename the directory
|
||||
await GameIOManager.MoveDirectory(
|
||||
ActiveSwappable.CompileJob.DirectoryName.ToString(),
|
||||
ActiveSwappable.Directory,
|
||||
cancellationToken);
|
||||
|
||||
hardLinkedDmb = ActiveSwappable;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task InitController(Task chatTask, ReattachInformation reattachInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var suspended = false;
|
||||
try
|
||||
{
|
||||
await base.InitController(chatTask, reattachInfo, cancellationToken);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Then we move it back and apply the symlink
|
||||
if (hardLinkedDmb != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Logger.LogTrace("Unhardlinking compile job...");
|
||||
Server?.Suspend();
|
||||
suspended = true;
|
||||
var hardLink = hardLinkedDmb.Directory;
|
||||
var originalPosition = hardLinkedDmb.CompileJob.DirectoryName.ToString();
|
||||
await GameIOManager.MoveDirectory(
|
||||
hardLink,
|
||||
originalPosition,
|
||||
default);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(
|
||||
ex,
|
||||
"Failed to un-hard link compile job #{compileJobId} ({compileJobDirectory})",
|
||||
hardLinkedDmb.CompileJob.Id,
|
||||
hardLinkedDmb.CompileJob.DirectoryName);
|
||||
}
|
||||
|
||||
hardLinkedDmb = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (reattachInfo != null)
|
||||
{
|
||||
Logger.LogTrace("Skipping symlink due to reattach");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.LogTrace("Symlinking compile job...");
|
||||
await ActiveSwappable.MakeActive(cancellationToken);
|
||||
if (suspended)
|
||||
Server.Resume();
|
||||
// not necessary to hold initial .dmb on Linux because of based inode deletes
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
protected virtual Task InitialLink(CancellationToken cancellationToken)
|
||||
Task InitialLink(CancellationToken cancellationToken)
|
||||
{
|
||||
Logger.LogTrace("Symlinking compile job...");
|
||||
return ActiveSwappable.MakeActive(cancellationToken);
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
interface ISymlinkFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// If directory symlinks must be deleted as files would in the current environment.
|
||||
/// </summary>
|
||||
/// <remarks>This is because Linux symlinked directories must be deleted with <see cref="global::System.IO.File.Delete(string)"/>.</remarks>
|
||||
bool SymlinkedDirectoriesAreDeletedAsFiles { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a symbolic link.
|
||||
/// </summary>
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
sealed class PosixSymlinkFactory : ISymlinkFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool SymlinkedDirectoriesAreDeletedAsFiles => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
() =>
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace Tgstation.Server.Host.IO
|
||||
/// </summary>
|
||||
sealed class WindowsSymlinkFactory : ISymlinkFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public bool SymlinkedDirectoriesAreDeletedAsFiles => false;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task CreateSymbolicLink(string targetPath, string linkPath, CancellationToken cancellationToken) => Task.Factory.StartNew(
|
||||
() =>
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
using Tgstation.Server.Host.IO;
|
||||
|
||||
namespace Tgstation.Server.Host.System.Tests
|
||||
{
|
||||
[TestClass]
|
||||
public sealed class TestSymlinkFactory
|
||||
{
|
||||
readonly ISymlinkFactory factory = new PlatformIdentifier().IsWindows
|
||||
? new WindowsSymlinkFactory()
|
||||
: new PosixSymlinkFactory();
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestSymlinks()
|
||||
{
|
||||
var cancellationToken = CancellationToken.None;
|
||||
var cwd = Path.GetTempFileName();
|
||||
File.Delete(cwd);
|
||||
Directory.CreateDirectory(cwd);
|
||||
try
|
||||
{
|
||||
var realDir = Path.Combine(cwd, "RealDir");
|
||||
var symDir = Path.Combine(cwd, "SymDir");
|
||||
var realFile = Path.Combine(cwd, "RealFile.txt");
|
||||
var symFile = Path.Combine(realDir, "RealFile.txt");
|
||||
|
||||
var subRealFile = Path.Combine(realDir, "test.txt");
|
||||
var subSymFile = Path.Combine(symDir, "test.txt");
|
||||
|
||||
try
|
||||
{
|
||||
await factory.CreateSymbolicLink(subRealFile, subSymFile, cancellationToken);
|
||||
Assert.Fail("Expected Exception!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(realDir);
|
||||
Directory.CreateDirectory(symDir);
|
||||
|
||||
await File.WriteAllBytesAsync(realFile, Array.Empty<byte>(), cancellationToken);
|
||||
await File.WriteAllBytesAsync(symFile, Array.Empty<byte>(), cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
await factory.CreateSymbolicLink(realFile, symFile, cancellationToken);
|
||||
Assert.Fail("Expected Exception!");
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
Directory.Delete(symDir);
|
||||
File.Delete(symFile);
|
||||
|
||||
await factory.CreateSymbolicLink(realFile, symFile, cancellationToken);
|
||||
Assert.IsTrue(File.Exists(symFile));
|
||||
Assert.IsFalse(Directory.Exists(symFile));
|
||||
|
||||
await File.WriteAllTextAsync(realFile, "test", cancellationToken);
|
||||
var symFileContents = await File.ReadAllTextAsync(symFile, cancellationToken);
|
||||
Assert.AreEqual("test", symFileContents);
|
||||
File.Delete(symFile);
|
||||
File.Delete(realFile);
|
||||
|
||||
try
|
||||
{
|
||||
await factory.CreateSymbolicLink(realDir, symDir, cancellationToken);
|
||||
|
||||
Assert.IsFalse(File.Exists(symDir));
|
||||
Assert.IsTrue(Directory.Exists(symDir));
|
||||
|
||||
await File.WriteAllTextAsync(subRealFile, "test", cancellationToken);
|
||||
Assert.IsTrue(File.Exists(subSymFile));
|
||||
|
||||
File.Delete(subSymFile);
|
||||
Assert.IsFalse(File.Exists(subRealFile));
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (factory.SymlinkedDirectoriesAreDeletedAsFiles)
|
||||
File.Delete(symDir);
|
||||
else
|
||||
Directory.Delete(symDir);
|
||||
}
|
||||
|
||||
if (factory.SymlinkedDirectoriesAreDeletedAsFiles)
|
||||
Assert.IsFalse(File.Exists(symDir));
|
||||
else
|
||||
Assert.IsFalse(Directory.Exists(symDir));
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(cwd, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user