Application hang: ImageViewerWpfControl.Disconnect() never returns while the camera source is still connecting
Product: MIP SDK 26.1.19408.1 · Severity: application hang, unrecoverable
Summary
Calling ImageViewerWpfControl.Disconnect() on a camera viewer that has not yet reported a successful connect intermittently hangs the WPF UI thread permanently.
In every dump we captured, the UI thread is blocked in a WaitForSingleObject call inside CoreToolkits.dll, reached from our own Disconnect() call. At the same time, a CoreToolkits worker thread is alive and waiting in WaitForMultipleObjectsEx. That wait never completes and the application never recovers.
The stall is not limited to the one camera. A renderer callback thread is simultaneously blocked in System.Threading.Monitor.Enter, so all rendering stops while the UI thread is stuck.
Confirmed identical in 6 of 6 full-memory dumps.
Environment
| Item | Value |
|---|---|
| MIP SDK | 26.1.19408.1 (VideoOS.Platform, VideoOS.Platform.SDK.UI, VideoOS.UI.Common.WPF.VideoPlayer) |
| Module where the thread is blocked | CoreToolkits.dll |
| Application | .NET Framework 4.8, WPF, x64 |
| OS | Windows 10 IoT Enterprise LTSC 2021, 10.0.19044, 24 logical cores |
| Topology | 1 recording server, TCP 7563, 24 concurrent camera sources |
Symptom and impact
-
The UI thread blocks forever inside
Disconnect(); the process must be killed. -
Reproduces only on newer, faster hardware. The identical build and test suite never reproduce it on our older machine.
-
Approximately 5-10 hangs across 210 automated test runs (roughly 2-5% of runs). Six of these were captured as full-memory dumps in one day (2026-09-10, 12:50-18:42). Process uptime at the hang was 41 s and 68 s in two of the dumps, so this happens during normal startup/teardown cycling, not after long running.
Reproduction
-
Create N
ImageViewerWpfControlinstances (we use 24) for cameras on one recording server and callInitialize()thenConnect(). -
While at least one viewer has not yet raised
ConnectResponseReceivedwithConnectionGranted == true, tear the view down and callDisconnect(). -
Intermittently
Disconnect()never returns.
Anything that makes connects slow or unreliable (a loaded or throttled recording server) widens the window. Faster client hardware appears to widen it as well.
What we observe
1. The UI thread never returns from Disconnect().
The managed stack ends in a P/Invoke into native code; the native stack below it is CoreToolkits.dll calling WaitForSingleObject, which has not returned. See appendix A and B.
2. A CoreToolkits worker thread is alive and waiting.
Its native stack is CoreToolkits.dll calling WaitForMultipleObjectsEx. The thread has not exited. See appendix C.
3. From our own application state, the affected viewer had never completed its connect.
In every one of these cases our code had created the viewer and called Connect(), but had not yet received ConnectResponseReceived with ConnectionGranted == true for it. We have never observed the hang on a viewer that had reported a successful connect.
Taken together, the wait inside CoreToolkits.dll does not complete and does not appear to time out. The hangs had already persisted for some time when the dumps were taken, and the application never recovered in any of them.
What we are asking for
In rough priority order:
-
Bound the wait that
Disconnect()performs. A wait with a timeout, plus a logged diagnostic if it expires, would turn a permanent application hang into a recoverable and attributable error. This alone resolves the incident for us. -
Do not let one stuck source block all rendering. While the UI thread is stuck, a renderer callback thread is blocked on a monitor, so the entire video pipeline stalls rather than just the one camera. See appendix D.
-
Provide a supported way to abort a connect that is still in progress, so a source can be torn down deterministically while connecting.
-
Document the supported teardown sequence for a viewer that has never completed its connect (see the questions below).
Workaround in place, and questions
We currently track ConnectResponseReceived and skip Disconnect() and Close() for any viewer that has not reported ConnectionGranted == true, calling only Dispose(). This avoids the hang in our testing, but we are not confident it is correct usage.
We would appreciate confirmation on three points:
-
Is releasing an
ImageViewerWpfControlwithDispose()alone, withoutDisconnect()andClose(), safe and supported? -
What is leaked when we do this? We assume the source registration and its worker thread persist for the lifetime of the process. Is that correct?
-
Is there a supported way to release a source that has never completed its connect, so that we do not have to skip the normal teardown path?
Attachments
Full-memory dumps (6) are available on request via secure upload. They contain internal hostnames and possibly session tokens, so we have not attached them here.
Appendix: stacks from the dumps
All extracts are from Argos.Ui.Wpf_MiniDumpWithFullMemory_2026-09-10_12-50-57.dmp and are reproduced exactly as the debugger printed them (WinDbg/CDB with the SOS extension). We have no private symbols for CoreToolkits.dll, so its frames appear as the debugger resolved them against the module’s export table: an exported name plus a raw offset. They should map directly to real frames with your own PDBs.
A. UI thread (thread 0) — managed stack
0000006efb3fd698 00007ffea3cb0ec3 [InlinedCallFrame: 0000006efb3fd698]
0000006efb3fd670 00007ffea3cb0ec3 DomainBoundILStubClass.IL_STUB_PInvoke(IntPtr, std.set<int,std::less<int>,std::allocator<int> >*)
0000006efb3fd720 00007ffea3cf3115 VideoOS.Toolkit.RendererToolkit.DisconnectSources(System.Collections.Generic.ISet`1<Int32>)
0000006efb3fdd70 00007ffea3cf2e88 VideoOS.UI.Common.WPF.VideoPlayer.Toolkit.ToolkitWrapper.ExecuteSourceOperation(VideoOS.UI.Common.WPF.VideoPlayer.Toolkit.DisconnectSourceOperation)
0000006efb3fddf0 00007ffea3cf2d41 VideoOS.UI.Common.WPF.VideoPlayer.Toolkit.VideoImage.VideoImageManager.VideoImageDisconnect(VideoOS.UI.Common.WPF.VideoPlayer.Toolkit.VideoImage.VideoImage)
0000006efb3fde70 00007ffea3cf2b9b VideoOS.UI.Common.WPF.VideoPlayer.Toolkit.VideoImage.VideoImage.Disconnect()
0000006efb3fdeb0 00007ffea3ca1e2c VideoOS.UI.Common.WPF.VideoPlayer.Toolkit.ToolkitVideoSource.Disconnect()
0000006efb3fdf70 00007ffea3cf283d VideoOS.Platform.SDK.UI.PlatformImageViewerWpfControl.DisconnectInternal()
0000006efb3fdfc0 00007ffea3cf24d2 Argos.Ui.Wpf.BackendProxyManager.MipCameraViewer.MipCamerasViewerService.DestroyOnUiThread(VideoOS.Platform.Client.ImageViewerWpfControl)
0000006efb3fe000 00007ffea3cf1cd4 Argos.Ui.Wpf.BackendProxyManager.CameraDisposableControl.Dispose()
... our own frames, then System.Windows.Threading.DispatcherOperation.InvokeImpl() and the WPF dispatcher ...
The bottom frames are our own code: MipCamerasViewerService.DestroyOnUiThread is our teardown method, called on the WPF dispatcher thread.
B. UI thread (thread 0) — native stack
ntdll!NtWaitForSingleObject+0x14
KERNELBASE!WaitForSingleObjectEx+0x8e
CoreToolkits!DeleteInstance+0x262476
CoreToolkits!DeleteInstance+0x3b7d82
CoreToolkits!DeleteInstance+0x3b76f3
CoreToolkits!DeleteInstance+0x26046
CoreToolkits!DeleteInstance+0xa06a5
CoreToolkits!DeleteInstance+0x9280d
C. The CoreToolkits worker thread that is still running (tid 0x2c28)
ntdll!NtWaitForMultipleObjects+0x14
KERNELBASE!WaitForMultipleObjectsEx+0xf0
CoreToolkits!DeleteInstance+0x267729
CoreToolkits!DeleteInstance+0x26b2f0
CoreToolkits!DeleteInstance+0x3b9697
CoreToolkits!DeleteInstance+0x2629a6
ucrtbase!thread_start<unsigned int (__cdecl*)(void *),1>+0x42
kernel32!BaseThreadInitThunk+0x14
ntdll!RtlUserThreadStart+0x21
This thread is alive in the dump and has not exited. Twelve threads in the process are in a WaitForMultipleObjects wait at the time of the hang, which is consistent with several camera sources being active concurrently.
D. Rendering is blocked behind a monitor (thread 59, tid 0x2e84)
0000006e8ab3f1f8 00007fff0d2ee104 [HelperMethodFrame: 0000006e8ab3f1f8] System.Threading.Monitor.Enter(System.Object)
0000006e8ab3f2f0 00007ffea3ccadbc VideoOS.UI.Common.WPF.VideoPlayer.Toolkit.ToolkitWrapper.HandleRenderedData(System.Collections.Generic.Dictionary`2<Int32,IRenderingInformation>)
0000006e8ab3f3f0 00007ffea3cc94aa <Module>.VideoOS.Toolkit.CmRenderingHandler.HandleRenderedData(...)
A renderer callback arriving from native code cannot proceed while the UI thread is blocked, so video rendering stops process-wide for the duration of the hang, which is indefinite.
E. Consistency across dumps
| Dump (2026-09-10) | UI thread blocked in WaitForSingleObject under Disconnect() |
CoreToolkits worker thread still waiting |
|---|---|---|
| 12-50-57 | yes | yes |
| 12-52-41 | yes | yes |
| 13-14-24 | yes | yes |
| 15-25-46 | yes | yes |
| 16-54-07 | yes | yes |
| 18-42-37 | yes | yes |