I've been testing the C# editions of EMA (LSEG.Ema.Core v3.1.0 NuGet package), and am seeing significant memory leaks that would seem to prevent it from being used in production in its current state. These leaks seem to be due to the unusually extensive use of GCHandles throughout the code.
One such memory leak results from LSEG.Eta.ValueAdd.Reactor.Watchlist, and results in a substantial object graph never becoming eligible for garbage collection. The leak occurs as a result of the following:
Watchlist instantiates WlStream objects in InitWlStreamPool(), and allocates a GCHandle for each.
These GCHandles will only be freed if the Watchlist's finalizer runs (which calls FreeWlStream()), and the finalizer will only run if the Watchlist becomes unreachable.
Unfortunately this can never happen because the WlStream instances hold a reference to the Watchlist (via the m_Watchlist field), which in-turn means that the Watchlist is always considered reachable (GCHandle -> WlStream -> Watchlist), and so never becomes eligible for finalization.
(Note that this is not the only cause of Watchlist never being finalized, e.g. another exists via a GCHandle allocated for a WlRequest instance, and there may be more).
It seems likely given the extensive use of GCHandles that other such leaks exist elsewhere. What does the use of GCHandles in this way attempt to achieve, and does it result in demonstrable performance benefits?
The use global object pools (EmaGlobalObjectPool, EtaObjectGlobalPool) stored in static fields (hence ineligible for garbage collection for the life of the application) also seems to result in significant amounts of memory being used that will never be released, even after all connections are closed. Perhaps a means for these to be freed (either automatically or manually) would be beneficial as well?
I've been testing the C# editions of EMA (LSEG.Ema.Core v3.1.0 NuGet package), and am seeing significant memory leaks that would seem to prevent it from being used in production in its current state. These leaks seem to be due to the unusually extensive use of
GCHandle
s throughout the code.One such memory leak results from
LSEG.Eta.ValueAdd.Reactor.Watchlist
, and results in a substantial object graph never becoming eligible for garbage collection. The leak occurs as a result of the following:Watchlist
instantiatesWlStream
objects inInitWlStreamPool()
, and allocates aGCHandle
for each. TheseGCHandle
s will only be freed if theWatchlist
's finalizer runs (which callsFreeWlStream()
), and the finalizer will only run if theWatchlist
becomes unreachable. Unfortunately this can never happen because theWlStream
instances hold a reference to theWatchlist
(via them_Watchlist
field), which in-turn means that theWatchlist
is always considered reachable (GCHandle -> WlStream -> Watchlist
), and so never becomes eligible for finalization.(Note that this is not the only cause of
Watchlist
never being finalized, e.g. another exists via aGCHandle
allocated for aWlRequest
instance, and there may be more).It seems likely given the extensive use of
GCHandle
s that other such leaks exist elsewhere. What does the use ofGCHandle
s in this way attempt to achieve, and does it result in demonstrable performance benefits?The use global object pools (
EmaGlobalObjectPool
,EtaObjectGlobalPool
) stored in static fields (hence ineligible for garbage collection for the life of the application) also seems to result in significant amounts of memory being used that will never be released, even after all connections are closed. Perhaps a means for these to be freed (either automatically or manually) would be beneficial as well?