mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
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>
This commit is contained in:
committed by
GitHub
parent
455d2aa588
commit
96360fa9d8
@@ -3,7 +3,6 @@
|
||||
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
@@ -18,20 +17,15 @@ namespace Regorus
|
||||
/// Each instance represents a unique native policy object.
|
||||
///
|
||||
/// Thread Safety: This class is thread-safe for all operations. Multiple threads
|
||||
/// can safely call EvalWithInput() concurrently, and Dispose() will safely wait
|
||||
/// for all active evaluations to complete before freeing resources. No external
|
||||
/// synchronization is required.
|
||||
/// can safely call EvalWithInput() concurrently. Dispose() blocks new calls, waits
|
||||
/// briefly, and defers the native release to the last in-flight caller if needed.
|
||||
/// No external synchronization is required.
|
||||
/// </summary>
|
||||
public unsafe sealed class CompiledPolicy : IDisposable
|
||||
public unsafe sealed class CompiledPolicy : SafeHandleWrapper
|
||||
{
|
||||
private RegorusCompiledPolicyHandle? _handle;
|
||||
private readonly ManualResetEventSlim _idleEvent = new(initialState: true);
|
||||
private int _isDisposed;
|
||||
private int _activeEvaluations;
|
||||
|
||||
internal CompiledPolicy(RegorusCompiledPolicyHandle handle)
|
||||
: base(handle, nameof(CompiledPolicy))
|
||||
{
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -45,36 +39,16 @@ namespace Regorus
|
||||
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
|
||||
public string? EvalWithInput(string inputJson)
|
||||
{
|
||||
// Increment active evaluations count
|
||||
var active = System.Threading.Interlocked.Increment(ref _activeEvaluations);
|
||||
if (active == 1)
|
||||
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
|
||||
{
|
||||
_idleEvent.Reset();
|
||||
}
|
||||
try
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
|
||||
return Internal.Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
|
||||
return UseHandle(policyPtr =>
|
||||
{
|
||||
return UseHandle(policyPtr =>
|
||||
unsafe
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
|
||||
}
|
||||
});
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_eval_with_input((Internal.RegorusCompiledPolicy*)policyPtr, (byte*)inputPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Decrement active evaluations count
|
||||
var remaining = System.Threading.Interlocked.Decrement(ref _activeEvaluations);
|
||||
if (remaining == 0)
|
||||
{
|
||||
_idleEvent.Set();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +60,6 @@ namespace Regorus
|
||||
/// <exception cref="ObjectDisposedException">Thrown when the policy has been disposed</exception>
|
||||
public PolicyInfo GetPolicyInfo()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var jsonResult = UseHandle(policyPtr =>
|
||||
{
|
||||
unsafe
|
||||
@@ -94,7 +67,7 @@ namespace Regorus
|
||||
return CheckAndDropResult(Internal.API.regorus_compiled_policy_get_policy_info((Internal.RegorusCompiledPolicy*)policyPtr));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (string.IsNullOrEmpty(jsonResult))
|
||||
{
|
||||
throw new Exception("Failed to get policy info: empty response");
|
||||
@@ -106,8 +79,8 @@ namespace Regorus
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
return JsonSerializer.Deserialize<PolicyInfo>(jsonResult!, options)
|
||||
|
||||
return JsonSerializer.Deserialize<PolicyInfo>(jsonResult!, options)
|
||||
?? throw new Exception("Failed to deserialize policy info");
|
||||
}
|
||||
catch (JsonException ex)
|
||||
@@ -116,106 +89,9 @@ namespace Regorus
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle != null)
|
||||
{
|
||||
_idleEvent.Wait();
|
||||
|
||||
handle.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
|
||||
_idleEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
|
||||
private string? CheckAndDropResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = Internal.Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
Internal.RegorusDataType.String => Internal.Utf8Marshaller.FromUtf8(result.output),
|
||||
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
Internal.RegorusDataType.Integer => result.int_value.ToString(),
|
||||
Internal.RegorusDataType.None => null,
|
||||
_ => Internal.Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private RegorusCompiledPolicyHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(CompiledPolicy));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
return UseHandle(func);
|
||||
}
|
||||
|
||||
private void UseHandle(Action<IntPtr> action)
|
||||
{
|
||||
UseHandle<object?>(handlePtr =>
|
||||
{
|
||||
action(handlePtr);
|
||||
return null;
|
||||
});
|
||||
return Internal.ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,17 +12,17 @@ namespace Regorus
|
||||
/// <summary>
|
||||
/// Represents a policy module with an ID and content.
|
||||
/// </summary>
|
||||
public struct PolicyModule
|
||||
public readonly struct PolicyModule
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the unique identifier for this policy module.
|
||||
/// Gets the unique identifier for this policy module.
|
||||
/// </summary>
|
||||
public string Id { get; set; }
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Rego policy content.
|
||||
/// Gets the Rego policy content.
|
||||
/// </summary>
|
||||
public string Content { get; set; }
|
||||
public string Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the PolicyModule struct.
|
||||
@@ -53,50 +53,40 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when compilation fails</exception>
|
||||
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IEnumerable<PolicyModule> modules, string entryPointRule)
|
||||
{
|
||||
var modulesArray = modules.ToArray();
|
||||
|
||||
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
|
||||
|
||||
try
|
||||
if (modules is null)
|
||||
{
|
||||
for (int i = 0; i < modulesArray.Length; i++)
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
return CompilePolicyWithEntrypoint(dataJson, modules.ToArray(), entryPointRule);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles a policy from data and modules with a specific entry point rule.
|
||||
/// </summary>
|
||||
public static CompiledPolicy CompilePolicyWithEntrypoint(string dataJson, IReadOnlyList<PolicyModule> modules, string entryPointRule)
|
||||
{
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
using var pinnedModules = Internal.ModuleMarshalling.PinPolicyModules(modules);
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
|
||||
{
|
||||
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
|
||||
pinnedStrings.Add(idPinned);
|
||||
pinnedStrings.Add(contentPinned);
|
||||
|
||||
nativeModules[i] = new Internal.RegorusPolicyModule
|
||||
unsafe
|
||||
{
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
Utf8Marshaller.WithUtf8(entryPointRule, entryPointPtr =>
|
||||
{
|
||||
unsafe
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
|
||||
{
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_with_entrypoint(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length, (byte*)entryPointPtr);
|
||||
var result = Internal.API.regorus_compile_policy_with_entrypoint(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)pinnedModules.Length, (byte*)entryPointPtr);
|
||||
|
||||
var policy = GetCompiledPolicyResult(result);
|
||||
return policy;
|
||||
}
|
||||
return GetCompiledPolicyResult(result);
|
||||
}
|
||||
}));
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
pinned.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -110,49 +100,39 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when compilation fails</exception>
|
||||
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IEnumerable<PolicyModule> modules)
|
||||
{
|
||||
var modulesArray = modules.ToArray();
|
||||
|
||||
var nativeModules = new Internal.RegorusPolicyModule[modulesArray.Length];
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2);
|
||||
|
||||
try
|
||||
if (modules is null)
|
||||
{
|
||||
for (int i = 0; i < modulesArray.Length; i++)
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
return CompilePolicyForTarget(dataJson, modules.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compiles a target-aware policy from data and modules.
|
||||
/// </summary>
|
||||
public static CompiledPolicy CompilePolicyForTarget(string dataJson, IReadOnlyList<PolicyModule> modules)
|
||||
{
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
using var pinnedModules = Internal.ModuleMarshalling.PinPolicyModules(modules);
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
|
||||
pinnedStrings.Add(idPinned);
|
||||
pinnedStrings.Add(contentPinned);
|
||||
|
||||
nativeModules[i] = new Internal.RegorusPolicyModule
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
|
||||
{
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
var result = Internal.API.regorus_compile_policy_for_target(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)pinnedModules.Length);
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
fixed (Internal.RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
{
|
||||
var result = Internal.API.regorus_compile_policy_for_target(
|
||||
(byte*)dataPtr, modulesPtr, (UIntPtr)modulesArray.Length);
|
||||
|
||||
var policy = GetCompiledPolicyResult(result);
|
||||
return policy;
|
||||
}
|
||||
return GetCompiledPolicyResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
pinned.Dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CompiledPolicy GetCompiledPolicyResult(Internal.RegorusResult result)
|
||||
|
||||
@@ -16,14 +16,11 @@ namespace Regorus
|
||||
/// Cloning is cheap and involves only incrementing reference counts for shared immutable objects like parsed policies,
|
||||
/// data etc. Mutable state is deep copied as needed.
|
||||
/// </summary>
|
||||
public unsafe sealed class Engine : IDisposable
|
||||
public unsafe sealed class Engine : SafeHandleWrapper
|
||||
{
|
||||
private RegorusEngineHandle? _handle;
|
||||
private int _isDisposed;
|
||||
|
||||
public Engine()
|
||||
: base(RegorusEngineHandle.Create(), nameof(Engine))
|
||||
{
|
||||
_handle = RegorusEngineHandle.Create();
|
||||
}
|
||||
|
||||
public static void SetFallbackExecutionTimerConfig(ExecutionTimerConfig config)
|
||||
@@ -37,42 +34,13 @@ namespace Regorus
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_clear_fallback_execution_timer_config());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
|
||||
// This object will be cleaned up by the Dispose method.
|
||||
// Therefore, call GC.SuppressFinalize to
|
||||
// take this object off the finalization queue
|
||||
// and prevent finalization code for this object
|
||||
// from executing a second time.
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// Dispose(bool disposing) executes in two distinct scenarios.
|
||||
// If disposing equals true, the method has been called directly
|
||||
// or indirectly by a user's code. Managed and unmanaged resources
|
||||
// can be disposed.
|
||||
// If disposing equals false, the method has been called by the
|
||||
// runtime from inside the finalizer and you should not reference
|
||||
// other objects. Only unmanaged resources can be disposed.
|
||||
void Dispose(bool disposing)
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Engine(RegorusEngineHandle handle)
|
||||
: base(handle, nameof(Engine))
|
||||
{
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
public Engine Clone()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
@@ -91,402 +59,198 @@ namespace Regorus
|
||||
|
||||
public void SetStrictBuiltinErrors(bool strict)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_strict_builtin_errors((Regorus.Internal.RegorusEngine*)enginePtr, strict));
|
||||
});
|
||||
}
|
||||
|
||||
public void SetExecutionTimerConfig(ExecutionTimerConfig config)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
var nativeConfig = config.ToNative();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
var localConfig = nativeConfig;
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr, &localConfig));
|
||||
}
|
||||
var localConfig = nativeConfig;
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr, &localConfig));
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearExecutionTimerConfig()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
public string? AddPolicy(string path, string rego)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
Utf8Marshaller.WithUtf8(rego, regoPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
UseHandle(enginePtr =>
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr, (byte*)regoPtr))
|
||||
)));
|
||||
}
|
||||
|
||||
public void SetRegoV0(bool enable)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_rego_v0((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
});
|
||||
}
|
||||
|
||||
public string? AddPolicyFromFile(string path)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
return UseHandle(enginePtr =>
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_policy_from_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr))
|
||||
);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void AddDataJson(string data)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(data, dataPtr =>
|
||||
{
|
||||
unsafe
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)dataPtr));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void AddDataFromJsonFile(string path)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
unsafe
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_add_data_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void SetInputJson(string input)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(input, inputPtr =>
|
||||
{
|
||||
unsafe
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_json((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)inputPtr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public void SetInputFromJsonFile(string path)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(path, pathPtr =>
|
||||
{
|
||||
unsafe
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_input_from_json_file((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)pathPtr));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public string? EvalQuery(string query)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(query, queryPtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
return UseHandle(enginePtr =>
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_query((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)queryPtr))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public string? EvalRule(string rule)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(rule, rulePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
return UseHandle(enginePtr =>
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_eval_rule((Regorus.Internal.RegorusEngine*)enginePtr, (byte*)rulePtr))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public void SetEnableCoverage(bool enable)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_enable_coverage((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
});
|
||||
}
|
||||
|
||||
public void ClearCoverageData()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_coverage_data((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetCoverageReport()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetCoverageReportPretty()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_coverage_report_pretty((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public void SetGatherPrints(bool enable)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
}
|
||||
CheckAndDropResult(Regorus.Internal.API.regorus_engine_set_gather_prints((Regorus.Internal.RegorusEngine*)enginePtr, enable));
|
||||
});
|
||||
}
|
||||
|
||||
public string? TakePrints()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_take_prints((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetAstAsJson()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_ast_as_json((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetPolicyPackageNames()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_package_names((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
public string? GetPolicyParameters()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(enginePtr =>
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
}
|
||||
return CheckAndDropResult(Regorus.Internal.API.regorus_engine_get_policy_parameters((Regorus.Internal.RegorusEngine*)enginePtr));
|
||||
});
|
||||
}
|
||||
|
||||
private static string? StringFromUtf8(IntPtr ptr)
|
||||
private static string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
|
||||
{
|
||||
|
||||
#if NETSTANDARD2_1
|
||||
return Marshal.PtrToStringUTF8(ptr);
|
||||
#else
|
||||
int len = 0;
|
||||
while (Marshal.ReadByte(ptr, len) != 0) { ++len; }
|
||||
byte[] buffer = new byte[len];
|
||||
Marshal.Copy(ptr, buffer, 0, buffer.Length);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Regorus.Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
Regorus.Internal.RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
Regorus.Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
Regorus.Internal.RegorusDataType.Integer => result.int_value.ToString(),
|
||||
Regorus.Internal.RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
Regorus.Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
}
|
||||
|
||||
internal RegorusEngineHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal void UseHandle(Action<IntPtr> action)
|
||||
{
|
||||
UseHandle<object?>(handlePtr =>
|
||||
{
|
||||
action(handlePtr);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Engine));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
return UseHandle(func);
|
||||
return ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,12 +89,14 @@ namespace Regorus
|
||||
);
|
||||
}
|
||||
|
||||
if (result.int_value < 0)
|
||||
try
|
||||
{
|
||||
throw new OverflowException($"{errorContext}: native value was negative ({result.int_value})");
|
||||
return checked((ulong)result.int_value);
|
||||
}
|
||||
catch (OverflowException ex)
|
||||
{
|
||||
throw new OverflowException($"{errorContext}: native value was out of range ({result.int_value})", ex);
|
||||
}
|
||||
|
||||
return (ulong)result.int_value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using Regorus;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Regorus.Internal
|
||||
{
|
||||
internal static unsafe class ModuleMarshalling
|
||||
{
|
||||
internal sealed class PinnedPolicyModules : IDisposable
|
||||
{
|
||||
private readonly List<Utf8Marshaller.PinnedUtf8> _pins;
|
||||
private bool _disposed;
|
||||
|
||||
internal PinnedPolicyModules(RegorusPolicyModule[] buffer, int length, List<Utf8Marshaller.PinnedUtf8> pins)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Length = length;
|
||||
_pins = pins;
|
||||
}
|
||||
|
||||
internal RegorusPolicyModule[] Buffer { get; }
|
||||
|
||||
internal int Length { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pin in _pins)
|
||||
{
|
||||
pin.Dispose();
|
||||
}
|
||||
|
||||
ArrayPool<RegorusPolicyModule>.Shared.Return(Buffer, clearArray: true);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class PinnedEntryPoints : IDisposable
|
||||
{
|
||||
private readonly List<Utf8Marshaller.PinnedUtf8> _pins;
|
||||
private bool _disposed;
|
||||
|
||||
internal PinnedEntryPoints(IntPtr[] buffer, int length, List<Utf8Marshaller.PinnedUtf8> pins)
|
||||
{
|
||||
Buffer = buffer;
|
||||
Length = length;
|
||||
_pins = pins;
|
||||
}
|
||||
|
||||
internal IntPtr[] Buffer { get; }
|
||||
|
||||
internal int Length { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pin in _pins)
|
||||
{
|
||||
pin.Dispose();
|
||||
}
|
||||
|
||||
ArrayPool<IntPtr>.Shared.Return(Buffer, clearArray: true);
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal static PinnedPolicyModules PinPolicyModules(IReadOnlyList<PolicyModule> modules)
|
||||
{
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
var count = modules.Count;
|
||||
var buffer = ArrayPool<RegorusPolicyModule>.Shared.Rent(count);
|
||||
var pins = new List<Utf8Marshaller.PinnedUtf8>(count * 2);
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var idPinned = Utf8Marshaller.Pin(modules[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modules[i].Content);
|
||||
pins.Add(idPinned);
|
||||
pins.Add(contentPinned);
|
||||
|
||||
buffer[i] = new RegorusPolicyModule
|
||||
{
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
}
|
||||
|
||||
return new PinnedPolicyModules(buffer, count, pins);
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach (var pin in pins)
|
||||
{
|
||||
pin.Dispose();
|
||||
}
|
||||
|
||||
ArrayPool<RegorusPolicyModule>.Shared.Return(buffer, clearArray: true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static PinnedEntryPoints PinEntryPoints(IReadOnlyList<string> entryPoints)
|
||||
{
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
var count = entryPoints.Count;
|
||||
var buffer = ArrayPool<IntPtr>.Shared.Rent(count);
|
||||
var pins = new List<Utf8Marshaller.PinnedUtf8>(count);
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var entryPinned = Utf8Marshaller.Pin(entryPoints[i]);
|
||||
pins.Add(entryPinned);
|
||||
buffer[i] = (IntPtr)entryPinned.Pointer;
|
||||
}
|
||||
|
||||
return new PinnedEntryPoints(buffer, count, pins);
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach (var pin in pins)
|
||||
{
|
||||
pin.Dispose();
|
||||
}
|
||||
|
||||
ArrayPool<IntPtr>.Shared.Return(buffer, clearArray: true);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,11 @@ namespace Regorus
|
||||
/// <summary>
|
||||
/// Represents a compiled RVM program.
|
||||
/// </summary>
|
||||
public unsafe sealed class Program : IDisposable
|
||||
public unsafe sealed class Program : SafeHandleWrapper
|
||||
{
|
||||
private RegorusProgramHandle? _handle;
|
||||
private int _isDisposed;
|
||||
|
||||
private Program(RegorusProgramHandle handle)
|
||||
: base(handle, nameof(Program))
|
||||
{
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,63 +33,57 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public static Program CompileFromModules(string dataJson, IEnumerable<PolicyModule> modules, IEnumerable<string> entryPoints)
|
||||
{
|
||||
var modulesArray = modules.ToArray();
|
||||
var entryPointsArray = entryPoints.ToArray();
|
||||
if (entryPointsArray.Length == 0)
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
return CompileFromModules(dataJson, modules.ToArray(), entryPoints.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from modules and entry points.
|
||||
/// </summary>
|
||||
public static Program CompileFromModules(string dataJson, IReadOnlyList<PolicyModule> modules, IReadOnlyList<string> entryPoints)
|
||||
{
|
||||
if (modules is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(modules));
|
||||
}
|
||||
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
if (entryPoints.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
|
||||
}
|
||||
|
||||
var nativeModules = new RegorusPolicyModule[modulesArray.Length];
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(modulesArray.Length * 2 + entryPointsArray.Length);
|
||||
var entryPointers = new IntPtr[entryPointsArray.Length];
|
||||
using var pinnedModules = ModuleMarshalling.PinPolicyModules(modules);
|
||||
using var pinnedEntryPoints = ModuleMarshalling.PinEntryPoints(entryPoints);
|
||||
|
||||
try
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
for (int i = 0; i < modulesArray.Length; i++)
|
||||
fixed (RegorusPolicyModule* modulesPtr = pinnedModules.Buffer)
|
||||
fixed (IntPtr* entryPtr = pinnedEntryPoints.Buffer)
|
||||
{
|
||||
var idPinned = Utf8Marshaller.Pin(modulesArray[i].Id);
|
||||
var contentPinned = Utf8Marshaller.Pin(modulesArray[i].Content);
|
||||
pinnedStrings.Add(idPinned);
|
||||
pinnedStrings.Add(contentPinned);
|
||||
var result = API.regorus_program_compile_from_modules(
|
||||
(byte*)dataPtr,
|
||||
modulesPtr,
|
||||
(UIntPtr)pinnedModules.Length,
|
||||
(byte**)entryPtr,
|
||||
(UIntPtr)pinnedEntryPoints.Length);
|
||||
|
||||
nativeModules[i] = new RegorusPolicyModule
|
||||
{
|
||||
id = idPinned.Pointer,
|
||||
content = contentPinned.Pointer
|
||||
};
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
|
||||
for (int i = 0; i < entryPointsArray.Length; i++)
|
||||
{
|
||||
var entryPinned = Utf8Marshaller.Pin(entryPointsArray[i]);
|
||||
pinnedStrings.Add(entryPinned);
|
||||
entryPointers[i] = (IntPtr)entryPinned.Pointer;
|
||||
}
|
||||
|
||||
return Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
fixed (RegorusPolicyModule* modulesPtr = nativeModules)
|
||||
fixed (IntPtr* entryPtr = entryPointers)
|
||||
{
|
||||
var result = API.regorus_program_compile_from_modules(
|
||||
(byte*)dataPtr,
|
||||
modulesPtr,
|
||||
(UIntPtr)modulesArray.Length,
|
||||
(byte**)entryPtr,
|
||||
(UIntPtr)entryPointsArray.Length);
|
||||
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
foreach (var pinned in pinnedStrings)
|
||||
{
|
||||
pinned.Dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -104,44 +95,48 @@ namespace Regorus
|
||||
{
|
||||
throw new ArgumentNullException(nameof(engine));
|
||||
}
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
var entryPointsArray = entryPoints.ToArray();
|
||||
if (entryPointsArray.Length == 0)
|
||||
return CompileFromEngine(engine, entryPoints.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compile an RVM program from an engine instance and entry points.
|
||||
/// </summary>
|
||||
public static Program CompileFromEngine(Engine engine, IReadOnlyList<string> entryPoints)
|
||||
{
|
||||
if (engine is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(engine));
|
||||
}
|
||||
|
||||
if (entryPoints is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(entryPoints));
|
||||
}
|
||||
|
||||
if (entryPoints.Count == 0)
|
||||
{
|
||||
throw new ArgumentException("At least one entry point is required.", nameof(entryPoints));
|
||||
}
|
||||
|
||||
var pinnedStrings = new List<Utf8Marshaller.PinnedUtf8>(entryPointsArray.Length);
|
||||
var entryPointers = new IntPtr[entryPointsArray.Length];
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < entryPointsArray.Length; i++)
|
||||
{
|
||||
var entryPinned = Utf8Marshaller.Pin(entryPointsArray[i]);
|
||||
pinnedStrings.Add(entryPinned);
|
||||
entryPointers[i] = (IntPtr)entryPinned.Pointer;
|
||||
}
|
||||
using var pinnedEntryPoints = ModuleMarshalling.PinEntryPoints(entryPoints);
|
||||
|
||||
return engine.UseHandleForInterop(enginePtr =>
|
||||
{
|
||||
fixed (IntPtr* entryPtr = entryPointers)
|
||||
{
|
||||
var result = API.regorus_engine_compile_program_with_entrypoints(
|
||||
(RegorusEngine*)enginePtr,
|
||||
(byte**)entryPtr,
|
||||
(UIntPtr)entryPointsArray.Length);
|
||||
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
return engine.UseHandleForInterop(enginePtr =>
|
||||
{
|
||||
foreach (var pinned in pinnedStrings)
|
||||
fixed (IntPtr* entryPtr = pinnedEntryPoints.Buffer)
|
||||
{
|
||||
pinned.Dispose();
|
||||
var result = API.regorus_engine_compile_program_with_entrypoints(
|
||||
(RegorusEngine*)enginePtr,
|
||||
(byte**)entryPtr,
|
||||
(UIntPtr)pinnedEntryPoints.Length);
|
||||
|
||||
return GetProgramResult(result);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -169,7 +164,6 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public byte[] SerializeBinary()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(programPtr =>
|
||||
{
|
||||
var result = API.regorus_program_serialize_binary((RegorusProgram*)programPtr);
|
||||
@@ -182,70 +176,12 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public string? GenerateListing()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(programPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_program_generate_listing((RegorusProgram*)programPtr));
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Program));
|
||||
}
|
||||
}
|
||||
|
||||
internal RegorusProgramHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Program));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Program));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Program GetProgramResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
@@ -272,27 +208,7 @@ namespace Regorus
|
||||
|
||||
private static string? CheckAndDropResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
RegorusDataType.Integer => result.int_value.ToString(),
|
||||
RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
return ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
|
||||
private static byte[] ExtractBuffer(RegorusResult result)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<LangVersion>10.0</LangVersion>
|
||||
|
||||
<!-- See https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-pack -->
|
||||
<VersionPrefix>0.9.0</VersionPrefix>
|
||||
<VersionPrefix>0.9.1</VersionPrefix>
|
||||
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageLicenseExpression>MIT AND Apache-2.0 AND BSD-3-Clause</PackageLicenseExpression>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Regorus.Internal
|
||||
{
|
||||
internal static unsafe class ResultHelpers
|
||||
{
|
||||
internal static string? GetStringResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
RegorusDataType.Integer => result.int_value.ToString(),
|
||||
RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool GetBoolResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == RegorusDataType.Boolean && result.bool_value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
internal static long GetIntResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == RegorusDataType.Integer ? result.int_value : 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,22 +7,35 @@ using Regorus.Internal;
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Execution mode for the RVM runtime.
|
||||
/// </summary>
|
||||
public enum ExecutionMode : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Run to completion without yielding.
|
||||
/// </summary>
|
||||
RunToCompletion = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Suspendable execution mode.
|
||||
/// </summary>
|
||||
Suspendable = 1,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for the Regorus RVM runtime.
|
||||
/// </summary>
|
||||
public unsafe sealed class Rvm : IDisposable
|
||||
public unsafe sealed class Rvm : SafeHandleWrapper
|
||||
{
|
||||
private RegorusRvmHandle? _handle;
|
||||
private int _isDisposed;
|
||||
|
||||
public Rvm()
|
||||
: base(RegorusRvmHandle.Create(), nameof(Rvm))
|
||||
{
|
||||
_handle = RegorusRvmHandle.Create();
|
||||
}
|
||||
|
||||
private Rvm(RegorusRvmHandle handle)
|
||||
: base(handle, nameof(Rvm))
|
||||
{
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,13 +60,12 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public void LoadProgram(Program program)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (program is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(program));
|
||||
}
|
||||
|
||||
program.UseHandle(programPtr =>
|
||||
program.UseHandleForInterop(programPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
@@ -69,7 +81,6 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public void SetDataJson(string dataJson)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(dataJson, dataPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
@@ -85,7 +96,6 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public void SetInputJson(string inputJson)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
Utf8Marshaller.WithUtf8(inputJson, inputPtr =>
|
||||
{
|
||||
UseHandle(vmPtr =>
|
||||
@@ -101,7 +111,6 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public void SetExecutionMode(byte mode)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
UseHandle(vmPtr =>
|
||||
{
|
||||
CheckAndDropResult(API.regorus_rvm_set_execution_mode((RegorusRvm*)vmPtr, mode));
|
||||
@@ -109,12 +118,19 @@ namespace Regorus
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the execution mode.
|
||||
/// </summary>
|
||||
public void SetExecutionMode(ExecutionMode mode)
|
||||
{
|
||||
SetExecutionMode((byte)mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute the program and return the JSON result.
|
||||
/// </summary>
|
||||
public string? Execute()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_execute((RegorusRvm*)vmPtr));
|
||||
@@ -126,7 +142,6 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public string? ExecuteEntryPoint(string entryPoint)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return Utf8Marshaller.WithUtf8(entryPoint, entryPtr =>
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
@@ -141,7 +156,6 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public string? ExecuteEntryPoint(ulong index)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_execute_entry_point_by_index((RegorusRvm*)vmPtr, (UIntPtr)index));
|
||||
@@ -153,7 +167,6 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public string? Resume(string? resumeValueJson)
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
if (resumeValueJson is null)
|
||||
{
|
||||
return UseHandle(vmPtr =>
|
||||
@@ -176,70 +189,12 @@ namespace Regorus
|
||||
/// </summary>
|
||||
public string? GetExecutionState()
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
return UseHandle(vmPtr =>
|
||||
{
|
||||
return CheckAndDropResult(API.regorus_rvm_get_execution_state((RegorusRvm*)vmPtr));
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _isDisposed, 1, 0) == 0)
|
||||
{
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_isDisposed != 0 || _handle is null || _handle.IsClosed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Rvm));
|
||||
}
|
||||
}
|
||||
|
||||
internal RegorusRvmHandle GetHandleForUse()
|
||||
{
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Rvm));
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
internal T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
var handle = GetHandleForUse();
|
||||
bool addedRef = false;
|
||||
try
|
||||
{
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(Rvm));
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (addedRef)
|
||||
{
|
||||
handle.DangerousRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Rvm GetRvmResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
@@ -266,27 +221,7 @@ namespace Regorus
|
||||
|
||||
private static string? CheckAndDropResult(RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
RegorusDataType.Integer => result.int_value.ToString(),
|
||||
RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
API.regorus_result_drop(result);
|
||||
}
|
||||
return ResultHelpers.GetStringResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
#nullable enable
|
||||
namespace Regorus
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for native handle wrappers that coordinates handle usage and disposal.
|
||||
///
|
||||
/// Behavior summary:
|
||||
/// - UseHandle: blocks Dispose while running; throws ObjectDisposedException if disposal has started or the handle is invalid.
|
||||
/// - Dispose: marks disposing and blocks new calls; waits briefly for in-flight calls to finish, then defers native release to the last exiting call if needed.
|
||||
/// - Handles are never exposed directly; derived classes can only work through UseHandle helpers.
|
||||
///
|
||||
/// Concurrency model:
|
||||
/// - _state tracks lifecycle transitions (Active -> DisposeRequested -> Released).
|
||||
/// - HandleGate tracks in-flight operations and enforces the "no new calls after Dispose" rule.
|
||||
/// - SafeHandle is pinned per call via DangerousAddRef to prevent use-after-free while native work runs.
|
||||
/// - If Dispose times out, the last in-flight caller performs the release to avoid leaks.
|
||||
/// </summary>
|
||||
public abstract class SafeHandleWrapper : IDisposable
|
||||
{
|
||||
private static readonly TimeSpan DefaultDisposeTimeout = TimeSpan.FromMilliseconds(50);
|
||||
private const int StateActive = 0;
|
||||
private const int StateDisposeRequested = 1;
|
||||
private const int StateReleased = 2;
|
||||
private readonly HandleGate _gate;
|
||||
private readonly string _ownerName;
|
||||
private int _state;
|
||||
private SafeHandle? _handle;
|
||||
|
||||
protected SafeHandleWrapper(SafeHandle handle, string ownerName)
|
||||
{
|
||||
// Cache ownership info and initialize the gate before any use to avoid racing disposal.
|
||||
_handle = handle ?? throw new ArgumentNullException(nameof(handle));
|
||||
_ownerName = ownerName ?? throw new ArgumentNullException(nameof(ownerName));
|
||||
_gate = new HandleGate(ownerName);
|
||||
// Default to a very short wait when in-flight calls exist; release is deferred to the last caller if needed.
|
||||
}
|
||||
|
||||
protected void UseHandle(Action<IntPtr> action)
|
||||
{
|
||||
// Reuse the generic path to keep add/ref/release in one place.
|
||||
UseHandle<object?>(ptr =>
|
||||
{
|
||||
action(ptr);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
protected T UseHandle<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
// Fast reject if dispose was requested.
|
||||
if (System.Threading.Volatile.Read(ref _state) != StateActive)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
|
||||
// Enter gate so Dispose waits for in-flight native calls.
|
||||
_gate.Enter();
|
||||
bool addedRef = false;
|
||||
SafeHandle? handle = null;
|
||||
try
|
||||
{
|
||||
// Race: Dispose could begin after Enter; GetHandleForUse validates the handle again.
|
||||
handle = GetHandleForUse();
|
||||
// DangerousAddRef pins the SafeHandle so Dispose cannot close it mid-call.
|
||||
handle.DangerousAddRef(ref addedRef);
|
||||
var pointer = handle.DangerousGetHandle();
|
||||
// Validate pointer after AddRef in case handle became invalid between checks.
|
||||
if (pointer == IntPtr.Zero)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
|
||||
return func(pointer);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Always release the DangerousAddRef to avoid leaking the native handle.
|
||||
if (addedRef)
|
||||
{
|
||||
handle?.DangerousRelease();
|
||||
}
|
||||
|
||||
// Leave gate so Dispose can proceed when the last caller exits.
|
||||
var idle = _gate.Exit();
|
||||
// Race: Dispose may have timed out while we were in-flight.
|
||||
// The last exiting caller performs the native release to avoid leaks.
|
||||
if (idle && System.Threading.Volatile.Read(ref _state) == StateDisposeRequested)
|
||||
{
|
||||
TryReleaseHandle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal T UseHandleForInterop<T>(Func<IntPtr, T> func)
|
||||
{
|
||||
// Explicit alias for interop-specific call sites.
|
||||
return UseHandle(func);
|
||||
}
|
||||
|
||||
internal void UseHandleForInterop(Action<IntPtr> action)
|
||||
{
|
||||
// Explicit alias for interop-specific call sites.
|
||||
UseHandle(action);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
// Fast check for dispose state so callers fail deterministically.
|
||||
if (System.Threading.Volatile.Read(ref _state) != StateActive)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
|
||||
// Validate the underlying SafeHandle is still usable; avoids races with release.
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
}
|
||||
|
||||
private SafeHandle GetHandleForUse()
|
||||
{
|
||||
// Centralized gate for derived classes to grab the handle safely.
|
||||
// This is a second line of defense in case disposal began after the initial state check.
|
||||
var handle = _handle;
|
||||
if (handle is null || handle.IsClosed || handle.IsInvalid)
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Only the first caller runs disposal; others become no-ops.
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _state, StateDisposeRequested, StateActive) == StateActive)
|
||||
{
|
||||
// Block new calls and wait briefly if there are in-flight operations.
|
||||
var completed = _gate.TryBeginDispose(DefaultDisposeTimeout, out var hadActive);
|
||||
if (completed)
|
||||
{
|
||||
// Either no active calls or they drained within the short timeout.
|
||||
TryReleaseHandle();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Defer release to the last in-flight caller to avoid leaks without blocking indefinitely.
|
||||
// Race: if the last in-flight caller already exited, there will be no Exit() to trigger release.
|
||||
// Re-check active state and release immediately in that case.
|
||||
if (!hadActive || _gate.IsIdle)
|
||||
{
|
||||
TryReleaseHandle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void TryReleaseHandle()
|
||||
{
|
||||
if (System.Threading.Interlocked.CompareExchange(ref _state, StateReleased, StateDisposeRequested) != StateDisposeRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Once released, no caller should be able to observe a valid handle.
|
||||
// SafeHandle.Dispose closes the native resource; null to prevent reuse after dispose.
|
||||
_handle?.Dispose();
|
||||
_handle = null;
|
||||
// Release the wait handle resources after disposal completes.
|
||||
_gate.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tracks in-flight operations and coordinates disposal.
|
||||
/// </summary>
|
||||
private sealed class HandleGate : IDisposable
|
||||
{
|
||||
private readonly string _ownerName;
|
||||
private readonly System.Threading.ManualResetEventSlim _idle = new(initialState: true);
|
||||
private int _active;
|
||||
private int _disposing;
|
||||
|
||||
internal HandleGate(string ownerName)
|
||||
{
|
||||
_ownerName = ownerName;
|
||||
}
|
||||
|
||||
internal void Enter()
|
||||
{
|
||||
// If disposal already started, reject new work immediately.
|
||||
if (System.Threading.Volatile.Read(ref _disposing) != 0)
|
||||
{
|
||||
ThrowDisposed();
|
||||
}
|
||||
|
||||
// Track active callers; first one resets idle event.
|
||||
var active = System.Threading.Interlocked.Increment(ref _active);
|
||||
if (active == 1)
|
||||
{
|
||||
_idle.Reset();
|
||||
}
|
||||
|
||||
// Re-check disposing to handle races where Dispose began after increment.
|
||||
if (System.Threading.Volatile.Read(ref _disposing) != 0)
|
||||
{
|
||||
Exit();
|
||||
ThrowDisposed();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool Exit()
|
||||
{
|
||||
// Last caller signals idle so Dispose can continue.
|
||||
if (System.Threading.Interlocked.Decrement(ref _active) == 0)
|
||||
{
|
||||
_idle.Set();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal bool IsIdle => System.Threading.Volatile.Read(ref _active) == 0;
|
||||
|
||||
internal bool TryBeginDispose(TimeSpan timeout, out bool hadActive)
|
||||
{
|
||||
// Set disposing flag once; subsequent calls treat as already disposing.
|
||||
if (System.Threading.Interlocked.Exchange(ref _disposing, 1) != 0)
|
||||
{
|
||||
hadActive = System.Threading.Volatile.Read(ref _active) != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
hadActive = System.Threading.Volatile.Read(ref _active) != 0;
|
||||
if (!hadActive)
|
||||
{
|
||||
// No in-flight callers; disposal can proceed without waiting.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Wait for active callers to drain; optional timeout avoids blocking forever.
|
||||
if (timeout == System.Threading.Timeout.InfiniteTimeSpan)
|
||||
{
|
||||
_idle.Wait();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Race note: callers may finish between the timeout decision and Wait call; Wait handles that safely.
|
||||
return _idle.Wait(timeout);
|
||||
}
|
||||
|
||||
private void ThrowDisposed()
|
||||
{
|
||||
throw new ObjectDisposedException(_ownerName);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_idle.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ namespace Regorus
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
@@ -76,7 +76,7 @@ namespace Regorus
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
@@ -124,7 +124,7 @@ namespace Regorus
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
@@ -172,7 +172,7 @@ namespace Regorus
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
if (!IsInvalid && !IsClosed)
|
||||
if (!IsInvalid)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
@@ -27,7 +29,7 @@ namespace Regorus
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -46,7 +48,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -61,7 +63,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_len();
|
||||
return GetIntResult(result);
|
||||
return ResultHelpers.GetIntResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +77,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_is_empty();
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +88,16 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static string ListResourceNames()
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_resource_schema_list_names()) ?? "[]";
|
||||
return ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_list_names()) ?? "[]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all registered resource schema names as managed strings.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> GetResourceNames()
|
||||
{
|
||||
var json = ListResourceNames();
|
||||
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -102,7 +113,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_resource_schema_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -113,7 +124,7 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static void ClearResources()
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_resource_schema_clear());
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_resource_schema_clear());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -130,7 +141,7 @@ namespace Regorus
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_register((byte*)namePtr, (byte*)schemaPtr));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -149,7 +160,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -164,7 +175,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_len();
|
||||
return GetIntResult(result);
|
||||
return ResultHelpers.GetIntResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +189,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_is_empty();
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +200,16 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static string ListEffectNames()
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_effect_schema_list_names()) ?? "[]";
|
||||
return ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_list_names()) ?? "[]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// List all registered effect schema names as managed strings.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> GetEffectNames()
|
||||
{
|
||||
var json = ListEffectNames();
|
||||
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -205,7 +225,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_effect_schema_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -216,68 +236,7 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static void ClearEffects()
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_effect_schema_clear());
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
Internal.RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
Internal.RegorusDataType.Integer => result.int_value.ToString(),
|
||||
Internal.RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetBoolResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static long GetIntResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_effect_schema_clear());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json;
|
||||
using Regorus.Internal;
|
||||
|
||||
#nullable enable
|
||||
@@ -26,7 +28,7 @@ namespace Regorus
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_register_target_from_json((byte*)targetPtr));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -44,7 +46,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_contains((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -56,7 +58,16 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static string ListNames()
|
||||
{
|
||||
return CheckAndDropResult(Internal.API.regorus_target_registry_list_names()) ?? "[]";
|
||||
return ResultHelpers.GetStringResult(Internal.API.regorus_target_registry_list_names()) ?? "[]";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of all registered target names as managed strings.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<string> GetNames()
|
||||
{
|
||||
var json = ListNames();
|
||||
return JsonSerializer.Deserialize<string[]>(json) ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -72,7 +83,7 @@ namespace Regorus
|
||||
unsafe
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_remove((byte*)namePtr);
|
||||
return GetBoolResult(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -83,7 +94,7 @@ namespace Regorus
|
||||
/// <exception cref="Exception">Thrown when the operation fails</exception>
|
||||
public static void Clear()
|
||||
{
|
||||
CheckAndDropResult(Internal.API.regorus_target_registry_clear());
|
||||
ResultHelpers.GetStringResult(Internal.API.regorus_target_registry_clear());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -96,10 +107,9 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_len();
|
||||
return GetIntResult(result);
|
||||
return ResultHelpers.GetIntResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the target registry is empty.
|
||||
/// </summary>
|
||||
@@ -110,68 +120,7 @@ namespace Regorus
|
||||
get
|
||||
{
|
||||
var result = Internal.API.regorus_target_registry_is_empty();
|
||||
return GetBoolResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static string? CheckAndDropResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type switch
|
||||
{
|
||||
Internal.RegorusDataType.String => Utf8Marshaller.FromUtf8(result.output),
|
||||
Internal.RegorusDataType.Boolean => result.bool_value.ToString().ToLowerInvariant(),
|
||||
Internal.RegorusDataType.Integer => result.int_value.ToString(),
|
||||
Internal.RegorusDataType.None => null,
|
||||
_ => Utf8Marshaller.FromUtf8(result.output)
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetBoolResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Boolean ? result.bool_value : false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
}
|
||||
}
|
||||
|
||||
private static long GetIntResult(Internal.RegorusResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (result.status != Internal.RegorusStatus.Ok)
|
||||
{
|
||||
var message = Utf8Marshaller.FromUtf8(result.error_message);
|
||||
throw result.status.CreateException(message);
|
||||
}
|
||||
|
||||
return result.data_type == Internal.RegorusDataType.Integer ? result.int_value : 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Internal.API.regorus_result_drop(result);
|
||||
return ResultHelpers.GetBoolResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ namespace Regorus.Internal
|
||||
/// </summary>
|
||||
internal static class Utf8Marshaller
|
||||
{
|
||||
// Mirrors BCL patterns (e.g., System.Text.Json encoding helpers) by stackalloc'ing
|
||||
// up to 512 bytes to cover common short strings while keeping the stack usage well
|
||||
// below typical per-frame limits; larger payloads fall back to pooled buffers.
|
||||
private const int StackAllocThreshold = 512;
|
||||
// Mirrors BCL patterns (e.g., System.Text.Json encoding helpers) by stackalloc'ing
|
||||
// up to 512 bytes to cover common short strings while keeping the stack usage well
|
||||
// below typical per-frame limits; larger payloads fall back to pooled buffers.
|
||||
private const int StackAllocThreshold = 512;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a pooled and pinned UTF-8 buffer suitable for scenarios where
|
||||
|
||||
Reference in New Issue
Block a user