feat!: add cooperative execution-time limits across engine, VM, and binding (#539)

- Introduce ExecutionTimer/ExecutionTimerConfig to allow limiting evaluating time.
- To amortize time checking costs, checking interval can be configured via the notion of work units
- A global fallback time limit can be set to universally limit all evaluation in addition to engine level limit setting.
- Implement limnits in interpreter and RVM. In RVM, also handle suspend/resume so that time during pause is not counted.
- Add engine-level APIs to set/clear per-engine timer configuration and apply global fallback defaults.
- Surface execution-time limits through FFI and C# bindings
- Add C# tests and example usage to validate engine overrides, global fallback behavior, and compiled policy enforcement.
- Expand docs for execution-time limit
- Add interpreter YAML cases and VM unit tests for time-limit behavior and deterministic time sources.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2026-01-28 05:58:03 +05:30
committed by GitHub
parent e68e852ee3
commit 394625d4bc
32 changed files with 2259 additions and 183 deletions

View File

@@ -2,6 +2,8 @@
// Licensed under the MIT License.
using System;
using System.Runtime.InteropServices;
using System.Text;
using Regorus.Internal;
@@ -24,6 +26,17 @@ namespace Regorus
_handle = RegorusEngineHandle.Create();
}
public static void SetFallbackExecutionTimerConfig(ExecutionTimerConfig config)
{
var nativeConfig = config.ToNative();
CheckAndDropResult(Regorus.Internal.API.regorus_set_fallback_execution_timer_config(nativeConfig));
}
public static void ClearFallbackExecutionTimerConfig()
{
CheckAndDropResult(Regorus.Internal.API.regorus_clear_fallback_execution_timer_config());
}
public void Dispose()
{
Dispose(disposing: true);
@@ -87,6 +100,32 @@ namespace Regorus
}
});
}
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));
}
});
}
public void ClearExecutionTimerConfig()
{
ThrowIfDisposed();
UseHandle(enginePtr =>
{
unsafe
{
CheckAndDropResult(Regorus.Internal.API.regorus_engine_clear_execution_timer_config((Regorus.Internal.RegorusEngine*)enginePtr));
}
});
}
public string? AddPolicy(string path, string rego)
{
ThrowIfDisposed();
@@ -355,7 +394,21 @@ namespace Regorus
});
}
string? CheckAndDropResult(Regorus.Internal.RegorusResult result)
private static string? StringFromUtf8(IntPtr ptr)
{
#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
{