Summary
In Remote Desktop Manager 2026.2.18.0 (Windows), every embedded RDP session that is closed leaves its AxMsRdpClient11 ActiveX control (wrapped in Devolutions.RemoteDesktopManager.Frames.Embedded.RDPClient11) alive in the process. The control is kept reachable only by a COM RefCount GC handle pointing at its AxMsRdpClient11NotSafeForScriptingEventMulticaster, i.e. the event sink registered with the native mstscax COM object is never released. Each leaked control keeps one OS thread, its native mstscax buffers and its kernel handles alive, so the process grows without bound over a working day until RDM is restarted.
After ~4.5 days of uptime and ~97 embedded sessions opened, the process reached 4.5 GB private bytes, 928 threads and 23,200 handles with only 20 sessions actually open. The machine became sluggish and the laptop fan ran continuously.
Environment
Item.......................................... Value
Remote Desktop Manager..... 2026.2.18.0 (Windows, 64-bit)
.NET runtime in process......... coreclr 10.0.1226.42308
OS........................................... Windows 11 Pro, build 10.0.26200
mstscax.dll.............................. 10.0.26100.8875
Hardware................................ HP EliteBook 840 G10, Intel i7-1370P, 64 GB RAM
Session type........................... RDP (Microsoft Remote Desktop), embedded (tabbed) mode, credentials inherited from parent folder
Steps to reproduce
Start RDM and note the process thread count and private bytes (Task Manager, Details tab, or Get-Process RemoteDesktopManager).
Open an embedded RDP session to any Windows host, wait until the desktop is displayed.
Close the tab (or disconnect the session).
Repeat steps 2 and 3 twenty or thirty times.
Observe that thread count, handle count and private bytes never return to the baseline. Each open/close cycle leaves behind roughly one extra thread, several hundred handles and tens of MB of native memory.
Optional confirmation with dotnet-dump (Microsoft tool, dotnet tool install -g dotnet-dump):
dotnet-dump collect -p <RDM pid> --type Full
dotnet-dump analyze <dump>
> dumpheap -stat -type AxMSTSCLib.AxMsRdpClient11NotSafeForScripting
> dumpheap -stat -type Devolutions.RemoteDesktopManager.Frames.Embedded.RDPClient11
> gcroot <address of any RDPClient11 whose _parent field is null>
Evidence from a full process dump
Live state at the time of the dump: 18 RDP sessions actually connected (18 established TCP connections to port 3389 owned by the process).
Managed heap object counts:
Type............................................................................................................................ Instances..... Expected for 18 live sessions
AxMSTSCLib.AxMsRdpClient11NotSafeForScriptingEventMulticaster.................... 117............... 18
Devolutions.RemoteDesktopManager.Frames.Embedded.RdpDvcListener............ 117............... 18
Devolutions.RemoteDesktopManager.Frames.Embedded.RDPClient11................. 171............... 18
Devolutions.RemoteDesktopManager.Frames.Embedded.FreEmbeddedRDP11... 158............... 18
...AvaloniaUI.ViewModels.EmbeddedConnectingPanelViewModel........................... 720.............. 18
GC handle statistics: 2,002 RefCount handles, 99,290 weak short handles.
dumpobj on the oldest RDPClient11 instance shows it is detached from the UI (_parent = null, _reflectParent = null, _newParent = null, _ocxState = null) yet still has a live _events list and _axState.
gcroot on two different detached RDPClient11 instances gives the same single root:
HandleTable:
-> 0162cb005d60 AxMSTSCLib.AxMsRdpClient11NotSafeForScriptingEventMulticaster
-> 0162b7926d88 Devolutions.RemoteDesktopManager.Frames.Embedded.RDPClient11
Found 1 unique roots.
Nothing in managed code references the control any more. The only thing keeping it alive is the RefCount GC handle created by COM interop for the event multicaster, which means the native IMsTscAx object still holds the connection point sink. The sink is presumably never unadvised (or the AxHost is not disposed) when the tab closes.
Correlated native symptoms
Thread start addresses in the live process (mapped with
NtQueryInformationThread(ThreadQuerySetWin32StartAddress)), with 19 sessions open:
Start module................................... Threads.... Notes
mstscax.dll (main worker entry)...... 649........... 34.2 per live session. Scales exactly with live sessions, so these are released correctly.
TextInputMethodFormatter.dll.......... 117........... Matches the 117 event multicasters exactly. One per control ever created, never released.
coreclr.dll............................................ 34
Intel graphics driver / ANGLE............ 43
Process growth observed while the user opened sessions 17 to 20: each new session added about 27 to 30 threads, 550 to 600 handles and 100 to 130 MB working set. Closing sessions did not give any of that back over the previous days.
Managed heap was only ~0.55 GB of the 4.5 GB private bytes, so most of the leaked memory is native (mstscax bitmap caches, GPU surfaces) owned by the orphaned controls.
Impact
Process grows by roughly 30 to 40 MB, 1 thread and several hundred handles per closed RDP session.
Kernel non-paged pool on the machine reached 2 GB with the process at 23k handles.
Heavy users (dozens of RDP sessions per day) end up with a multi-GB RDM process and a sluggish machine within a few days, fixed only by restarting RDM.
Workarounds found
Restarting RDM releases everything.
Running RDP sessions in an external process should contain the leak to the child process.
Notes
The full dump is not attached because it contains session credentials. The issue is easy to reproduce with the steps above; a heap comparison before and after 20 open/close cycles is enough to see it.