Files
regorus/bindings/csharp/Regorus/MemoryLimits.cs
Anand Krishnamoorthi 96360fa9d8 fix(bindings): add SafeHandleWrapper + memory growth checks; bump 0.9.1 (#571)
- Introduce SafeHandleWrapper with gating, short drain wait, and deferred release on last in-flight exit.
- Wire Engine/Program/Rvm/CompiledPolicy to wrapper (centralized handle use, interop helper).
- Add C# memory growth tests (using/finalizer paths) and extend xtask C# runner options.
- Add pooled marshalling utilities, ResultHelpers, and API cleanups; update versions/changelog.

Fixes #570. Closes #554

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
2026-02-09 12:22:05 -06:00

129 lines
4.5 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Regorus.Internal;
#nullable enable
namespace Regorus
{
/// <summary>
/// Helpers for configuring and inspecting Regorus memory limits via the native allocator bridge.
/// </summary>
public static class MemoryLimits
{
/// <summary>
/// Configure the process-wide global memory limit in bytes. Pass <c>null</c> to remove the limit.
/// </summary>
/// <param name="bytes">Maximum number of bytes the allocator may reserve before signalling an error.</param>
public static void SetGlobalMemoryLimit(ulong? bytes)
{
var result = API.regorus_set_global_memory_limit(bytes ?? 0, bytes.HasValue);
EnsureSuccess(result, nameof(SetGlobalMemoryLimit));
}
/// <summary>
/// Returns the currently configured global memory limit, if any.
/// </summary>
public static ulong? GetGlobalMemoryLimit()
{
var result = API.regorus_get_global_memory_limit();
return ExtractOptionalU64(result, "Failed to get global memory limit");
}
/// <summary>
/// Forces the allocator to flush this thread's pending counters into the global aggregates.
/// </summary>
public static void FlushThreadMemoryCounters()
{
var result = API.regorus_flush_thread_memory_counters();
EnsureSuccess(result, nameof(FlushThreadMemoryCounters));
}
/// <summary>
/// Immediately checks the global memory limit and throws if the allocator reports exhaustion.
/// </summary>
public static void CheckGlobalMemoryLimit()
{
var result = API.regorus_check_global_memory_limit();
EnsureSuccess(result, nameof(CheckGlobalMemoryLimit));
}
/// <summary>
/// Override the per-thread automatic flush threshold in bytes. Pass <c>null</c> to restore the default.
/// </summary>
public static void SetThreadFlushThresholdOverride(ulong? bytes)
{
var result = API.regorus_set_thread_flush_threshold_override(bytes ?? 0, bytes.HasValue);
EnsureSuccess(result, nameof(SetThreadFlushThresholdOverride));
}
/// <summary>
/// Returns the per-thread flush threshold, if automatic flushing is enabled.
/// </summary>
public static ulong? GetThreadMemoryFlushThreshold()
{
var result = API.regorus_get_thread_memory_flush_threshold();
return ExtractOptionalU64(result, "Failed to get thread memory flush threshold");
}
private static unsafe ulong? ExtractOptionalU64(RegorusResult result, string errorContext)
{
try
{
if (result.status != RegorusStatus.Ok)
{
var message = Utf8Marshaller.FromUtf8(result.error_message) ?? $"{errorContext}: native call failed";
throw result.status.CreateException(message);
}
if (!result.bool_value)
{
return null;
}
if (result.data_type != RegorusDataType.Integer)
{
throw new InvalidOperationException(
$"{errorContext}: native call returned {result.data_type} ({(int)result.data_type}) with bool_value={result.bool_value}"
);
}
try
{
return checked((ulong)result.int_value);
}
catch (OverflowException ex)
{
throw new OverflowException($"{errorContext}: native value was out of range ({result.int_value})", ex);
}
}
finally
{
API.regorus_result_drop(result);
}
}
private static void EnsureSuccess(RegorusResult result, string operation)
{
try
{
if (result.status != RegorusStatus.Ok)
{
string? message;
unsafe
{
message = Utf8Marshaller.FromUtf8(result.error_message);
}
throw result.status.CreateException(message);
}
}
finally
{
API.regorus_result_drop(result);
}
}
}
}