diff --git a/src/api/dotnet/Context.cs b/src/api/dotnet/Context.cs
index cba223f149..715c81626c 100644
--- a/src/api/dotnet/Context.cs
+++ b/src/api/dotnet/Context.cs
@@ -5256,6 +5256,11 @@ namespace Microsoft.Z3
internal IntPtr nCtx { get { return m_ctx; } }
private Z3_ast_print_mode m_print_mode = Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT;
+ // Estimated native memory used per context, for GC memory pressure hints.
+ // The value is a conservative lower bound; actual usage may exceed this.
+ private const long NativeMemoryPressureEstimate = 8 * 1024 * 1024; // 8 MB
+ private bool m_memPressureAdded = false;
+
internal void NativeErrorHandler(IntPtr ctx, Z3_error_code errorCode)
{
// Do-nothing error handler. The wrappers in Z3.Native will throw exceptions upon errors.
@@ -5266,6 +5271,11 @@ namespace Microsoft.Z3
PrintMode = Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT;
m_n_err_handler = new Native.Z3_error_handler(NativeErrorHandler); // keep reference so it doesn't get collected.
Native.Z3_set_error_handler(m_ctx, m_n_err_handler);
+ if (!is_external)
+ {
+ GC.AddMemoryPressure(NativeMemoryPressureEstimate);
+ m_memPressureAdded = true;
+ }
}
internal void CheckContextMatch(Z3Object other)
@@ -5355,17 +5365,38 @@ namespace Microsoft.Z3
m_charSort = null;
if (m_ctx != IntPtr.Zero)
{
- IntPtr ctx = m_ctx;
+ // Suppress the finalizer before performing cleanup so that it cannot
+ // run concurrently or redundantly if cleanup raises an exception.
+ GC.SuppressFinalize(this);
+ IntPtr ctx;
+ // Keep a local reference to the error handler delegate to ensure it stays
+ // alive throughout Z3_del_context. Setting m_n_err_handler = null releases
+ // the field reference; without the local variable the GC could collect the
+ // delegate before the native destructor finishes using the handler.
+ Native.Z3_error_handler errHandler;
lock (this)
{
+ ctx = m_ctx;
+ errHandler = m_n_err_handler;
m_n_err_handler = null;
m_ctx = IntPtr.Zero;
}
- if (!is_external)
- Native.Z3_del_context(ctx);
+ // ctx is non-zero only for the thread that wins the lock and zeros m_ctx,
+ // preventing double-free when Dispose() is called concurrently.
+ if (ctx != IntPtr.Zero)
+ {
+ if (!is_external)
+ {
+ Native.Z3_del_context(ctx);
+ GC.KeepAlive(errHandler);
+ }
+ if (m_memPressureAdded)
+ {
+ GC.RemoveMemoryPressure(NativeMemoryPressureEstimate);
+ m_memPressureAdded = false;
+ }
+ }
}
-
- GC.SuppressFinalize(this);
}
diff --git a/src/api/dotnet/NativeContext.cs b/src/api/dotnet/NativeContext.cs
index afae66e468..0b0b71f73f 100644
--- a/src/api/dotnet/NativeContext.cs
+++ b/src/api/dotnet/NativeContext.cs
@@ -1334,6 +1334,11 @@ namespace Microsoft.Z3
internal Native.Z3_error_handler m_n_err_handler = null;
internal IntPtr nCtx { get { return m_ctx; } }
+ // Estimated native memory used per context, for GC memory pressure hints.
+ // The value is a conservative lower bound; actual usage may exceed this.
+ private const long NativeMemoryPressureEstimate = 8 * 1024 * 1024; // 8 MB
+ private bool m_memPressureAdded = false;
+
internal void NativeErrorHandler(IntPtr ctx, Z3_error_code errorCode)
{
// Do-nothing error handler. The wrappers in Z3.Native will throw exceptions upon errors.
@@ -1344,8 +1349,8 @@ namespace Microsoft.Z3
PrintMode = Z3_ast_print_mode.Z3_PRINT_SMTLIB2_COMPLIANT;
m_n_err_handler = new Native.Z3_error_handler(NativeErrorHandler); // keep reference so it doesn't get collected.
Native.Z3_set_error_handler(m_ctx, m_n_err_handler);
-
- GC.SuppressFinalize(this);
+ GC.AddMemoryPressure(NativeMemoryPressureEstimate);
+ m_memPressureAdded = true;
}
#endregion
@@ -1365,20 +1370,39 @@ namespace Microsoft.Z3
#region Dispose
+ ///
+ /// Finalizer.
+ ///
+ ~NativeContext()
+ {
+ Dispose();
+ }
+
///
/// Disposes of the context.
///
public void Dispose()
{
- if (m_ctx != IntPtr.Zero)
+ IntPtr ctx = System.Threading.Interlocked.Exchange(ref m_ctx, IntPtr.Zero);
+ if (ctx != IntPtr.Zero)
{
+ // Suppress the finalizer before performing cleanup so that it cannot
+ // run concurrently or redundantly if cleanup raises an exception.
+ GC.SuppressFinalize(this);
+ // Keep a local reference to the error handler delegate to ensure it stays
+ // alive throughout Z3_del_context. Setting m_n_err_handler = null releases
+ // the field reference; without the local variable the GC could collect the
+ // delegate before the native destructor finishes using the handler.
+ var errHandler = m_n_err_handler;
m_n_err_handler = null;
- IntPtr ctx = m_ctx;
- m_ctx = IntPtr.Zero;
Native.Z3_del_context(ctx);
+ GC.KeepAlive(errHandler);
+ if (m_memPressureAdded)
+ {
+ GC.RemoveMemoryPressure(NativeMemoryPressureEstimate);
+ m_memPressureAdded = false;
+ }
}
- else
- GC.ReRegisterForFinalize(this);
}
#endregion