Free request-scoped GeneXus resources eagerly to drain the finalizer queue #1278
Open
claudiamurialdo wants to merge 7 commits into
Open
Free request-scoped GeneXus resources eagerly to drain the finalizer queue #1278claudiamurialdo wants to merge 7 commits into
claudiamurialdo wants to merge 7 commits into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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).
runs the existing handle/temp-file cleanup, and suppresses the
finalizer (kept as a safety net).
bodies in try/finally and dispose the context at end of request;
errors are logged via GXLogging.Warn.
when the connection was already moved to Closed (sync + async).
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