diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index e1ed76a4c9..a6408f3c74 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -130,11 +130,12 @@ namespace Tgstation.Server.Host.Components.Repository if (disposed) return; - logger.LogTrace("Disposing..."); disposed = true; - repository.Dispose(); - onDispose(); } + + logger.LogTrace("Disposing..."); + repository.Dispose(); + onDispose(); } void GetRepositoryOwnerName(string remote, out string owner, out string name) diff --git a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs index 5274cadd2d..d8c806cf74 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/SessionController.cs @@ -549,6 +549,12 @@ namespace Tgstation.Server.Host.Components.Watchdog /// public void SetHighPriority() => process.SetHighPriority(); + /// + public void Suspend() => process.Suspend(); + + /// + public void Resume() => process.Resume(); + /// public void ReplaceDmbProvider(IDmbProvider dmbProvider) { diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs index 795ba9c2e2..652b372efa 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WindowsWatchdog.cs @@ -120,7 +120,6 @@ namespace Tgstation.Server.Host.Components.Watchdog if (pendingSwappable != null) { Logger.LogTrace("Replacing activeSwappable with pendingSwappable"); - Server.ReplaceDmbProvider(pendingSwappable); activeSwappable = pendingSwappable; pendingSwappable = null; @@ -139,7 +138,9 @@ namespace Tgstation.Server.Host.Components.Watchdog windowsProvider = new WindowsSwappableDmbProvider(compileJobProvider, ioManager, symlinkFactory); Logger.LogDebug("Swapping to compile job {0}...", windowsProvider.CompileJob.Id); + Server.Suspend(); await windowsProvider.MakeActive(cancellationToken).ConfigureAwait(false); + Server.Resume(); } catch { diff --git a/src/Tgstation.Server.Host/NativeMethods.cs b/src/Tgstation.Server.Host/NativeMethods.cs index 3686e79f9e..98a92c2eac 100644 --- a/src/Tgstation.Server.Host/NativeMethods.cs +++ b/src/Tgstation.Server.Host/NativeMethods.cs @@ -24,6 +24,14 @@ namespace Tgstation.Server.Host AllowUnprivilegedCreate = 2 } + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686769(v=vs.85).aspx + /// + public enum ThreadAccess : int + { + SuspendResume = 0x0002, + } + /// /// See https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getwindowthreadprocessid /// @@ -70,5 +78,29 @@ namespace Tgstation.Server.Host /// [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, CreateSymbolicLinkFlags dwFlags); + + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms684335(v=vs.85).aspx + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); + + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211(v=vs.85).aspx + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern bool CloseHandle(IntPtr hObject); + + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686345(v=vs.85).aspx + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint SuspendThread(IntPtr hThread); + + /// + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms685086(v=vs.85).aspx + /// + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + public static extern uint ResumeThread(IntPtr hThread); } } diff --git a/src/Tgstation.Server.Host/System/IProcessBase.cs b/src/Tgstation.Server.Host/System/IProcessBase.cs index 63d414e554..a9013d567a 100644 --- a/src/Tgstation.Server.Host/System/IProcessBase.cs +++ b/src/Tgstation.Server.Host/System/IProcessBase.cs @@ -17,5 +17,15 @@ namespace Tgstation.Server.Host.System /// Set's the owned to /// void SetHighPriority(); + + /// + /// Suspends the process. + /// + void Suspend(); + + /// + /// Resumes the process. + /// + void Resume(); } } diff --git a/src/Tgstation.Server.Host/System/Process.cs b/src/Tgstation.Server.Host/System/Process.cs index d3738e94b0..c064733d1f 100644 --- a/src/Tgstation.Server.Host/System/Process.cs +++ b/src/Tgstation.Server.Host/System/Process.cs @@ -1,5 +1,7 @@ -using Microsoft.Extensions.Logging; +using BetterWin32Errors; +using Microsoft.Extensions.Logging; using System; +using System.Diagnostics; using System.Text; using System.Threading.Tasks; @@ -133,13 +135,65 @@ namespace Tgstation.Server.Host.System { try { - handle.PriorityClass = global::System.Diagnostics.ProcessPriorityClass.AboveNormal; - logger.LogTrace("Set to above normal priority", handle.Id); + handle.PriorityClass = ProcessPriorityClass.AboveNormal; + logger.LogTrace("Set PID {0} to above normal priority", Id); } catch (Exception e) { logger.LogWarning("Unable to raise process priority! Exception: {0}", e); } } + + /// + public void Suspend() + { + try + { + foreach (ProcessThread thread in handle.Threads) + { + var pOpenThread = NativeMethods.OpenThread(NativeMethods.ThreadAccess.SuspendResume, false, (uint)thread.Id); + if (pOpenThread == IntPtr.Zero) + continue; + + if (NativeMethods.SuspendThread(pOpenThread) == UInt32.MaxValue) + throw new Win32Exception(); + + NativeMethods.CloseHandle(pOpenThread); + } + + logger.LogTrace("Suspended PID {0}", Id); + } + catch (Exception e) + { + logger.LogError(e, "Failed to suspend PID {0}!", Id); + throw; + } + } + + /// + public void Resume() + { + try + { + foreach (ProcessThread thread in handle.Threads) + { + var pOpenThread = NativeMethods.OpenThread(NativeMethods.ThreadAccess.SuspendResume, false, (uint)thread.Id); + if (pOpenThread == IntPtr.Zero) + continue; + + if (NativeMethods.ResumeThread(pOpenThread) == UInt32.MaxValue) + throw new Win32Exception(); + + NativeMethods.CloseHandle(pOpenThread); + } + + logger.LogTrace("Resumed PID {0}", Id); + } + catch (Exception e) + { + logger.LogError(e, "Failed to resume PID {0}!", Id); + throw; + } + } } }