Skip to content

Free request-scoped GeneXus resources eagerly to drain the finalizer queue #1278

Open
claudiamurialdo wants to merge 7 commits into
release-1.34from
fix/memory-leak-disposal
Open

Free request-scoped GeneXus resources eagerly to drain the finalizer queue #1278
claudiamurialdo wants to merge 7 commits into
release-1.34from
fix/memory-leak-disposal

Conversation

@claudiamurialdo
Copy link
Copy Markdown
Collaborator

@claudiamurialdo claudiamurialdo commented May 7, 2026

Heap dumps from a long-running w3wp.exe showed tens of thousands of
objects pending finalization, dominated by Oracle native handles and
GxContext instances retaining the AJAX response payload
(GxContext -> HttpAjaxContext -> JArray -> JObject -> Hashtable).

  • HttpAjaxContext.Cleanup() resets the response collections.
  • GxContext implements IDisposable; Dispose() invokes Cleanup(),
    runs the existing handle/temp-file cleanup, and suppresses the
    finalizer (kept as a safety net).
  • GXHttpHandler.ProcessRequest / ProcessRequestAsync wrap their
    bodies in try/finally and dispose the context at end of request;
    errors are logged via GXLogging.Warn.
  • GxDataReader.Dispose() forwards to Close() instead of being a no-op.
  • GxConnection.Close always clears the prepared-command cache, even
    when the connection was already moved to Closed (sync + async).
  • GetCommand disposes prior IDbDataParameter instances when reusing
    a cached command with a different parameter shape.

System.Data.OracleClient is deprecated (https://learn.microsoft.com/en-us/dotnet/api/system.data.oracleclient);
migrating to ODP.NET Managed eliminates native OCI handles entirely

claudiamurialdo and others added 7 commits May 7, 2026 15:04
After getJSONResponse() serializes the response, the underlying
JArray/JObject collections (AttValues, HiddenValues, PropValues, Grids,
Messages, WebComponents, ComponentObjects, StylesheetsToLoad,
LoadCommands, commands, cmpContents) stay rooted on the
HttpAjaxContext and only the GC + finalizer can free them when the
owning GxContext is collected. Heap analysis showed thousands of
JObject + Hashtable+bucket[] retained via this chain. Cleanup() resets
all collections to empty instances so the previous payload becomes
garbage as soon as the request finishes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… eagerly

GxContext only had a finalizer, so the HttpAjaxContext + JObject tree
it owns waited for the GC + finalizer thread to be released. Heap
analysis showed dozens of GxContext stuck on the finalizer queue with
the AJAX response payload retained behind them.

Implement IDisposable. Dispose() calls HttpAjaxContext.Cleanup(), runs
the same handle / temp-file cleanup the finalizer did, and suppresses
finalization so the instance does not pay the gen2-finalize-queue cost
when callers dispose deterministically. The finalizer is kept as a
safety net for paths that do not call Dispose; both paths gate on a
NonSerialized _disposed flag to remain idempotent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dispose() was an empty no-op, so any 'using (var rdr = ...)' block left
the underlying IDataReader (and the Oracle native handles it owns) alive
until the next GC + finalizer pass. Forward Dispose() to Close() and
swallow exceptions through the standard dispose contract.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
connectionCache.Clear() was only invoked inside the 'connection is open'
branch, so when the underlying ADO.NET pool had already moved the
connection to Closed state (or an exception closed it) the cached
IDbCommand instances were never disposed. The same hole existed on the
NETCORE async path. Move the Clear() call out so it always runs and
guard it against a null cache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the parameter count of an incoming call did not match the cached
IDbCommand, GetCommand cleared the collection and re-added cloned
parameters - but the previously bound parameter objects were dropped on
the floor. Providers that own native handles (ODP.NET, etc.) need their
parameter objects disposed to release those handles eagerly instead of
relying on the finalizer thread. The 'is IDisposable' guard keeps the
deprecated System.Data.OracleClient.OracleParameter (which is not
IDisposable) compiling and behaving as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GXHttpHandler.ProcessRequest and ProcessRequestAsync are the IHttpHandler
entry points for every GeneXus web page / AJAX call. IsReusable is
false, so each instance owns its GxContext for exactly one request.
Wrap the existing body in try/finally and dispose the context on the
finally branch, so the HttpAjaxContext payload (JObject tree, hashtables)
and per-request handles are released eagerly instead of waiting for the
GC + finalizer thread.

The cast '(context as IDisposable)?.Dispose()' avoids changing the
public IGxContext interface; only the concrete GxContext class needs to
implement IDisposable for the cast to bind. The cleanup is idempotent
via the _disposed flag introduced earlier, so any inner code that
already disposed the context is safe.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A failure inside the finally-clause Dispose was being silently lost,
making any future regression in HttpAjaxContext.Cleanup or
GxContext.Dispose invisible. Forward the exception to GXLogging.Warn so
operators can spot it in the standard log4net output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@claudiamurialdo claudiamurialdo had a problem deploying to external-storage-tests May 7, 2026 19:26 — with GitHub Actions Failure
@claudiamurialdo claudiamurialdo had a problem deploying to kafka-integration-tests May 7, 2026 19:26 — with GitHub Actions Failure
@claudiamurialdo claudiamurialdo changed the title Fix/memory leak disposal Free request-scoped GeneXus resources eagerly to drain the finalizer queue May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant