Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Models/PersistentProcessRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ public sealed record PersistentProcessRule

public ProcessPriorityClass? Priority { get; init; }

public ProcessMemoryPriority? MemoryPriority { get; init; }

public bool ApplyAffinityOnStart { get; init; }

public bool ApplyPriorityOnStart { get; init; }

public bool ApplyMemoryPriorityOnStart { get; init; }

public DateTime CreatedAt { get; init; } = DateTime.UtcNow;

public DateTime UpdatedAt { get; init; } = DateTime.UtcNow;
Expand All @@ -49,6 +53,8 @@ public sealed record PersistentRuleApplyResult

public bool PriorityApplied { get; init; }

public bool MemoryPriorityApplied { get; init; }

public string? ErrorCode { get; init; }

public string UserMessage { get; init; } = string.Empty;
Expand Down
19 changes: 19 additions & 0 deletions Models/ProcessMemoryPriority.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* ThreadPilot - process memory priority model.
*/
namespace ThreadPilot.Models
{
/// <summary>
/// Documented Windows process memory priority levels.
/// CPU priority influences CPU scheduling; memory priority influences how aggressively
/// Windows may reclaim or page a process's memory under pressure.
/// </summary>
public enum ProcessMemoryPriority
{
VeryLow = 1,
Low = 2,
Medium = 3,
BelowNormal = 4,
Normal = 5,
}
}
1 change: 1 addition & 0 deletions Platforms/Windows/CpuSetNativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ internal static partial class CpuSetNativeMethods
[Flags]
public enum ProcessAccessFlags : uint
{
PROCESS_SET_INFORMATION = 0x00000200,
PROCESS_QUERY_LIMITED_INFORMATION = 0x00001000,
PROCESS_SET_LIMITED_INFORMATION = 0x00002000,
}
Expand Down
88 changes: 88 additions & 0 deletions Platforms/Windows/IProcessMemoryPriorityNativeApi.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* ThreadPilot - Windows process memory priority native API abstraction.
*/
namespace ThreadPilot.Platforms.Windows
{
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

public interface IProcessMemoryPriorityNativeApi
{
bool IsSupported { get; }

SafeProcessHandle OpenProcess(ProcessAccessFlags access, bool inheritHandle, uint processId);

bool GetProcessInformation(
SafeProcessHandle process,
ProcessInformationClass processInformationClass,
ref MemoryPriorityInformation processInformation,
uint processInformationSize);

bool SetProcessInformation(
SafeProcessHandle process,
ProcessInformationClass processInformationClass,
ref MemoryPriorityInformation processInformation,
uint processInformationSize);

int GetLastWin32Error();
}

public sealed class ProcessMemoryPriorityNativeApi : IProcessMemoryPriorityNativeApi
{
public static ProcessMemoryPriorityNativeApi Instance { get; } = new();

private ProcessMemoryPriorityNativeApi()
{
}

public bool IsSupported => OperatingSystem.IsWindowsVersionAtLeast(6, 2);

public SafeProcessHandle OpenProcess(ProcessAccessFlags access, bool inheritHandle, uint processId)
{
return ProcessMemoryPriorityNativeMethods.OpenProcess(access, inheritHandle, processId);
}

public bool GetProcessInformation(
SafeProcessHandle process,
ProcessInformationClass processInformationClass,
ref MemoryPriorityInformation processInformation,
uint processInformationSize)
{
return ProcessMemoryPriorityNativeMethods.GetProcessInformation(
process,
processInformationClass,
ref processInformation,
processInformationSize);
}

public bool SetProcessInformation(
SafeProcessHandle process,
ProcessInformationClass processInformationClass,
ref MemoryPriorityInformation processInformation,
uint processInformationSize)
{
return ProcessMemoryPriorityNativeMethods.SetProcessInformation(
process,
processInformationClass,
ref processInformation,
processInformationSize);
}

public int GetLastWin32Error()
{
return Marshal.GetLastWin32Error();
}
}

public enum ProcessInformationClass
{
ProcessMemoryPriority = 0,
}

[StructLayout(LayoutKind.Sequential)]
public struct MemoryPriorityInformation
{
public uint MemoryPriority;
}
}
33 changes: 33 additions & 0 deletions Platforms/Windows/ProcessMemoryPriorityNativeMethods.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* ThreadPilot - Windows process memory priority P/Invoke declarations.
*/
namespace ThreadPilot.Platforms.Windows
{
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;

internal static partial class ProcessMemoryPriorityNativeMethods
{
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial SafeProcessHandle OpenProcess(
ProcessAccessFlags access,
[MarshalAs(UnmanagedType.Bool)] bool inheritHandle,
uint processId);

[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool GetProcessInformation(
SafeProcessHandle process,
ProcessInformationClass processInformationClass,
ref MemoryPriorityInformation processInformation,
uint processInformationSize);

[LibraryImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static partial bool SetProcessInformation(
SafeProcessHandle process,
ProcessInformationClass processInformationClass,
ref MemoryPriorityInformation processInformation,
uint processInformationSize);
}
}
14 changes: 14 additions & 0 deletions Services/IProcessMemoryPriorityService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* ThreadPilot - process memory priority service contract.
*/
namespace ThreadPilot.Services
{
using ThreadPilot.Models;

public interface IProcessMemoryPriorityService
{
Task<ProcessMemoryPriority?> GetMemoryPriorityAsync(ProcessModel process);

Task<ProcessOperationResult> SetMemoryPriorityAsync(ProcessModel process, ProcessMemoryPriority priority);
}
}
62 changes: 61 additions & 1 deletion Services/PersistentRulesEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ Task<IReadOnlyList<PersistentRuleApplyResult>> ApplyMatchingRulesAsync(
public sealed class PersistentRulesEngine : IPersistentRulesEngine
{
private const string MissingAffinityErrorCode = "PersistentRuleMissingAffinity";
private const string MissingMemoryPriorityErrorCode = "PersistentRuleMissingMemoryPriority";
private const string MissingPriorityErrorCode = "PersistentRuleMissingPriority";
private const string MemoryPriorityApplyFailedErrorCode = "MemoryPriorityApplyFailed";
private const string NoActionsErrorCode = "PersistentRuleNoActions";
private const string PriorityApplyFailedErrorCode = "PriorityApplyFailed";
private const string RealtimePriorityBlockedErrorCode = "RealtimePriorityBlocked";
Expand All @@ -26,19 +28,22 @@ public sealed class PersistentRulesEngine : IPersistentRulesEngine
private readonly IPersistentProcessRuleMatcher matcher;
private readonly IAffinityApplyService affinityApplyService;
private readonly IProcessService processService;
private readonly IProcessMemoryPriorityService memoryPriorityService;
private readonly ILogger<PersistentRulesEngine> logger;

public PersistentRulesEngine(
IPersistentProcessRuleStore ruleStore,
IPersistentProcessRuleMatcher matcher,
IAffinityApplyService affinityApplyService,
IProcessService processService,
IProcessMemoryPriorityService memoryPriorityService,
ILogger<PersistentRulesEngine> logger)
{
this.ruleStore = ruleStore ?? throw new ArgumentNullException(nameof(ruleStore));
this.matcher = matcher ?? throw new ArgumentNullException(nameof(matcher));
this.affinityApplyService = affinityApplyService ?? throw new ArgumentNullException(nameof(affinityApplyService));
this.processService = processService ?? throw new ArgumentNullException(nameof(processService));
this.memoryPriorityService = memoryPriorityService ?? throw new ArgumentNullException(nameof(memoryPriorityService));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

Expand Down Expand Up @@ -68,7 +73,7 @@ private async Task<PersistentRuleApplyResult> ApplyRuleAsync(
var result = CreateSuccessResult(rule, process);
var success = true;

if (!rule.ApplyAffinityOnStart && !rule.ApplyPriorityOnStart)
if (!rule.ApplyAffinityOnStart && !rule.ApplyPriorityOnStart && !rule.ApplyMemoryPriorityOnStart)
{
return MarkRuleConfigurationFailure(
result,
Expand Down Expand Up @@ -130,6 +135,35 @@ private async Task<PersistentRuleApplyResult> ApplyRuleAsync(
}
}

if (rule.ApplyMemoryPriorityOnStart && !result.IsProcessExited)
{
if (!rule.MemoryPriority.HasValue)
{
success = false;
result = MarkRuleConfigurationFailure(
result,
rule,
MissingMemoryPriorityErrorCode,
"This saved rule has no memory priority value to apply.");
}
else
{
cancellationToken.ThrowIfCancellationRequested();
var memoryPriorityResult = await this.memoryPriorityService
.SetMemoryPriorityAsync(process, rule.MemoryPriority.Value)
.ConfigureAwait(false);
if (memoryPriorityResult.Success)
{
result = result with { MemoryPriorityApplied = true };
}
else
{
success = false;
result = this.MergeMemoryPriorityFailure(result, memoryPriorityResult);
}
}
}

return result with
{
Success = success,
Expand All @@ -138,6 +172,32 @@ private async Task<PersistentRuleApplyResult> ApplyRuleAsync(
};
}

private PersistentRuleApplyResult MergeMemoryPriorityFailure(
PersistentRuleApplyResult result,
ProcessOperationResult memoryPriorityResult)
{
this.logger.LogWarning(
"Persistent rule memory priority apply failed for rule {RuleId} on process {ProcessName} (PID: {ProcessId}): {Message}",
result.RuleId,
result.ProcessName,
result.ProcessId,
memoryPriorityResult.TechnicalMessage);

return result with
{
ErrorCode = string.IsNullOrWhiteSpace(memoryPriorityResult.ErrorCode)
? MemoryPriorityApplyFailedErrorCode
: memoryPriorityResult.ErrorCode,
UserMessage = string.IsNullOrWhiteSpace(memoryPriorityResult.UserMessage)
? "ThreadPilot could not apply the saved memory priority rule."
: memoryPriorityResult.UserMessage,
TechnicalMessage = memoryPriorityResult.TechnicalMessage,
IsAccessDenied = result.IsAccessDenied || memoryPriorityResult.IsAccessDenied,
IsAntiCheatLikely = result.IsAntiCheatLikely || memoryPriorityResult.IsAntiCheatLikely,
IsProcessExited = result.IsProcessExited || memoryPriorityResult.IsProcessExited,
};
}

private Task<AffinityApplyResult> ApplyAffinityAsync(PersistentProcessRule rule, ProcessModel process)
{
if (rule.CpuSelection != null)
Expand Down
Loading
Loading