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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.9.1](https://github.com/microsoft/regorus/compare/regorus-v0.9.0...regorus-v0.9.1) - 2026-02-06

### Fixed
- Release native C# handles reliably to avoid memory growth ([#571](https://github.com/microsoft/regorus/pull/571)).
- Centralize C# handle gating with a short dispose wait and deferred release to avoid leaks while blocking new calls ([#571](https://github.com/microsoft/regorus/pull/571)).

### Added
- Manual C# memory growth tests for both `using` and finalizer paths ([#571](https://github.com/microsoft/regorus/pull/571)).
- C# test runner options for filtered tests, console logging, and skipping sample apps ([#571](https://github.com/microsoft/regorus/pull/571)).

## [0.5.0](https://github.com/microsoft/regorus/compare/regorus-v0.4.0...regorus-v0.5.0) - 2025-07-08

### Added
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ members = [
[package]
name = "regorus"
description = "A fast, lightweight Rego (OPA policy language) interpreter"
version = "0.9.0"
version = "0.9.1"
edition = "2021"
license = "MIT AND Apache-2.0 AND BSD-3-Clause"
repository = "https://github.com/microsoft/regorus"
Expand Down
82 changes: 50 additions & 32 deletions bindings/csharp/Benchmarks/CompiledPolicyEvaluationBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Benchmarks
public class CompiledPolicyEvaluationBenchmark
{
private static readonly string TestDataPath = Path.Combine(
Directory.GetCurrentDirectory(),
Directory.GetCurrentDirectory(),
"..", "..", "..",
"benches", "evaluation", "test_data"
);
Expand All @@ -33,7 +33,7 @@ private static readonly (string PolicyFile, string[] InputFiles)[] PolicyInputFi
private static readonly string[] PolicyNames = new[]
{
"rbac_policy",
"api_access_policy",
"api_access_policy",
"data_sensitivity_policy",
"time_based_policy",
"data_processing_policy",
Expand All @@ -46,36 +46,36 @@ private static readonly (string PolicyFile, string[] InputFiles)[] PolicyInputFi
private static List<(string Policy, string[] Inputs)> LoadPoliciesWithInputs()
{
var result = new List<(string Policy, string[] Inputs)>();

foreach (var (policyFile, inputFiles) in PolicyInputFiles)
{
var policyPath = Path.Combine(TestDataPath, "policies", policyFile);
var policy = File.ReadAllText(policyPath);

var inputs = inputFiles.Select(inputFile =>
{
var inputPath = Path.Combine(TestDataPath, "inputs", inputFile);
return File.ReadAllText(inputPath);
}).ToArray();

result.Add((policy, inputs));
}

return result;
}

private static List<CompiledPolicy> PrepareSharedCompiledPolicies()
{
var policiesWithInputs = LoadPoliciesWithInputs();
var compiledPolicies = new List<CompiledPolicy>();

foreach (var (policy, _) in policiesWithInputs)
{
var modules = new[] { new PolicyModule { Id = "policy.rego", Content = policy } };
var modules = new[] { new PolicyModule("policy.rego", policy) };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
compiledPolicies.Add(compiled);
}

return compiledPolicies;
}

Expand All @@ -84,13 +84,13 @@ public static void RunCompiledPolicyEvaluationBenchmark()
var cpuCount = Environment.ProcessorCount;
var maxThreads = cpuCount * 2;
var threadCounts = new List<int> { 1, 2 };

// Add even numbers from 4 to maxThreads
for (int i = 4; i <= maxThreads; i += 2)
{
threadCounts.Add(i);
}

Console.WriteLine($"Running compiled policy benchmark with max_threads: {maxThreads}");
Console.WriteLine($"Testing with thread counts: {string.Join(", ", threadCounts)}");
Console.WriteLine();
Expand Down Expand Up @@ -120,19 +120,19 @@ public static void RunCompiledPolicyBenchmark(int threads, bool useSharedPolicie
const int durationSeconds = 3;
var policiesWithInputs = LoadPoliciesWithInputs();
List<CompiledPolicy>? compiledPolicies = null;

if (useSharedPolicies)
{
compiledPolicies = PrepareSharedCompiledPolicies();
}

Console.WriteLine($"Warming up with {threads} threads for {warmupSeconds} seconds...");

// Warmup phase
var (_, _, _, _) = RunBenchmarkPhase(threads, warmupSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: true);

Console.WriteLine($"Running benchmark with {threads} threads for {durationSeconds} seconds...");

// Actual benchmark phase
var (totalEvaluations, evaluationTime, policyCounters, allocatedBytes) = RunBenchmarkPhase(threads, durationSeconds, policiesWithInputs, compiledPolicies, useSharedPolicies, isWarmup: false);

Expand All @@ -155,7 +155,7 @@ public static void RunCompiledPolicyBenchmark(int threads, bool useSharedPolicie
{
foreach (var policy in compiledPolicies)
{
policy.Dispose();
DisposeCompiledPolicy(policy);
}
}

Expand All @@ -173,8 +173,8 @@ public static void RunCompiledPolicyBenchmark(int threads, bool useSharedPolicie
}

private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string, int> policyCounters, long allocatedBytes) RunBenchmarkPhase(
int threads,
int durationSeconds,
int threads,
int durationSeconds,
List<(string Policy, string[] Inputs)> policiesWithInputs,
List<CompiledPolicy>? compiledPolicies,
bool useSharedPolicies,
Expand Down Expand Up @@ -208,10 +208,10 @@ private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string
}

barrier.SignalAndWait();

int evaluationCount = 0;
var localEvaluationTime = TimeSpan.Zero;

while (!stopExecution)
{
// Use different policy for each iteration
Expand All @@ -226,23 +226,29 @@ private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string
{
// Measure only the evaluation call
var evalStopwatch = Stopwatch.StartNew();

if (useSharedPolicies)
{
var result = compiledPolicies![policyIdx].EvalWithInput(input);
}
else
{
// Compile policy in each iteration
var modules = new[] { new PolicyModule { Id = "policy.rego", Content = policy } };
// Compile policy in each iteration.
var modules = new[] { new PolicyModule("policy.rego", policy) };
var compiled = Compiler.CompilePolicyWithEntrypoint("{}", modules, "data.bench.allow");
var result = compiled.EvalWithInput(input);
compiled.Dispose();
try
{
var result = compiled.EvalWithInput(input);
}
finally
{
DisposeCompiledPolicy(compiled);
}
}

evalStopwatch.Stop();
localEvaluationTime += evalStopwatch.Elapsed;

// Track successful evaluations (only during actual benchmark, not warmup)
if (!isWarmup)
{
Expand All @@ -256,10 +262,10 @@ private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string
{
// Ignore evaluation errors for benchmarking purposes
}

evaluationCount++;
}

// Store the actual evaluation time for this thread
if (!isWarmup)
{
Expand All @@ -284,11 +290,23 @@ private static (int totalEvaluations, TimeSpan evaluationTime, Dictionary<string

var totalEvaluations = policyCounters.Values.Sum();
var totalEvaluationTime = evaluationTimes.Values.Aggregate(TimeSpan.Zero, (sum, time) => sum + time);

// Use pure evaluation time (consistent with Rust benchmark)
var evaluationTime = totalEvaluationTime == TimeSpan.Zero ? stopwatch.Elapsed : totalEvaluationTime;

return (totalEvaluations, evaluationTime, policyCounters, allocatedBytes);
}

private static void DisposeCompiledPolicy(CompiledPolicy policy)
{
try
{
policy.Dispose();
}
catch (TimeoutException ex)
{
Console.WriteLine($"Warning: {ex.Message}");
}
}
}
}
Loading
Loading