Repository navigation
v0.29.4
What changed and why
Fixes to ReconnectingCam, VirtualCam and GevCam, driven by a consumer that ported ReconnectingCam and audited its own
copy. We checked each of their findings against our code (code review, six areas) and fixed the ones that applied here. Every new
test fails on the code before its fix. No public type or member was added or removed.
Take this release if you use ReconnectingCam with MaxAttempts, call Open()/Close() from more than one thread, call
Open() yourself after a loss, or dispose cameras at shutdown with a tight time budget.
Imaging — ReconnectingCam
-
The reconnect loop counts only attempts that actually opened and failed.
MaxAttemptsand theBackoffMsladder used to
count two other things as failures:- An attempt stopped by
Close/Dispose. After a deliberate close the loop logged "giving up … Call Open() to retry", which
cannot be done afterDispose. - A wait while another caller (a manual
Open) held the camera. The loop could give up without ever trying, or move up the
ladder so the next real attempt came much later (5 s instead of 200 ms in the test).
The loop now waits for that other caller without a time limit;
CloseandDisposestill wake it. If the otherOpen
connected, the loop has nothing left to do. If it failed, the loop waits one step before trying. - An attempt stopped by
-
Open()right after a loss reopens. Until the loop's first attempt (1 s by default),Open()returned normally without
doing anything andIsConnectedstayed false. That contradictedICam.Openand the 0.29.2 note "Open() after a loss restores
live". If thatOpenfails, the loop keeps retrying; the exception does not mean the loop gave up. -
When
OpenandCloseoverlap on different threads, the one called last wins. Before, whichever got the internal gate
last decided, and a later caller can get it first. AnOpenqueued before aClosecould reopen after it, and theClosethen
removed only that session. The wrapper was then neither closed nor reconnecting — "Camera is reconnecting." forever. In the
opposite order, a laterOpenwas undone. -
An
Openthat fails on a closed wrapper leaves it closed (first open, or open afterClose, withRetryInitialOpenoff).
Before, aStartContinuousin between was silently recorded and started live on the next successfulOpen. AnOpenthat
fails after a loss, including after the loop gave up, keeps the live intent. Note that after a give-up no loop is running,
so a failedOpenthere is not retried in the background — callOpen()again (or enableRetryInitialOpen). -
A new loss after a manual reopen starts a new count. It no longer inherits the previous loss's failures and ladder step,
which produced "giving up" after a single attempt on the new loss. -
Disposewaits at mostShutdownWaitMsin total. It used to wait that long twice, once for the loop and once for the gate,
when both were stuck behind the same slow open. See "What can change for you" for the cost. -
StartContinuouswhile closed (before the firstOpen, afterClose) is still ignored, and now says so in one Info line. -
Logs no longer describe a deliberate close as a failure. A first open that fails after
Closewon no longer says "retrying in
the background", and an open that finishes failing afterCloseis logged at Info, not Warning.
Imaging — VirtualCam
- Every stop waits for the tick that is running. A second
StopContinuous, aClose, aDisposeor aGrabOneused to
return at once while only the first stop waited, and the old tick's frame followed — read as the next single grab's answer.
A tick that was still loading its image when the stop came is no longer raised at all, so aGrabFrameAsyncthat subscribes
during a stop gets its own frame. The wait no longer depends on how the runtime implementsTimer.Dispose(WaitHandle). - A tick stuck longer than the 1 s cap is waited for once, not once per call.
Imaging.Gev — GevCam
failed to execute AcquisitionStopafter control of the camera was lost is logged at Debug (the command is not even sent).
A consumer's field log showed one warning plus a stack per camera per outage, burying the "control lost" line. Other
AcquisitionStopfailures are still warnings.
Contract notes (documentation, no API change)
ICam.IsGrabbingmust not throw, even afterDispose(it is polled). Start/stop notifications raised from different threads
at nearly the same moment can reach subscribers in either order — the property is authoritative.CamReconnectOpt.MaxAttemptsandBackoffMscount real failures only.ShutdownWaitMsbounds eachCloseandDisposecall
separately, soClosefollowed byDisposecan take twice as long. Dispose several cameras in parallel if the shutdown budget
is tight.
What can change for you when you upgrade from 0.29.3
ReconnectingCam.Open()called right after a loss now blocks for a device open (it returned at once). With
RetryInitialOpen == falseit throws if that open fails (it returned silently); the loop keeps retrying.Open/Closeracing on different threads: the one called last wins.- A reconnect attempt waits for a manual
Openin progress instead of giving up on it. A manualOpenthat hangs keeps the loop
waiting;Close/Disposestill wake it. - Hosts with
MaxAttempts > 0see "giving up" only after that many real failures. - Log text: the Warning
initial open failed — retrying in the background.is nowopen failed — retrying in the background.
Update log filters that match the old text. New Info lines, all on the paths they describe:StartContinuous ignored — the camera is closed; call Open() first.open failed; not retrying because the camera was closed meanwhile.reconnect attempt abandoned: the camera was closed meanwhile.
Disposereturns afterShutdownWaitMseven if a reconnect open is still stuck, and that late session is closed in the
background. If the process exits right away, the device can stay controlled until its heartbeat times out. If an immediate
restart matters, setShutdownWaitMsabove the worst-case open time (discovery plus open).VirtualCam.GrabOne,StopContinuousandClosecan block up to 1 s behind a tick that is still loading or being handled.- GevCam logs one warning fewer per camera after a control loss.
Known limitation
A StartContinuous on one thread, arriving while a StopContinuous on another thread is still stopping the inner camera, can be
read as "the camera stopped on its own". The wrapper then rebuilds a healthy session, and live resumes after the first backoff
step. On GevCam, "still stopping" lasts up to about 2 s. This was found by code review and has not been reproduced yet. It widens
the start/stop window described in the 0.29.2 notes.
Version
0.29.4 — patch. No public type or member was added or removed. The behaviour changes are listed above. The four packages are
released in lockstep; CvInspect (core) and CvInspect.Wpf have no code change since 0.29.3.
Checks
-
dotnet build CvInspect.sln -c Release --no-incrementalreports 0 warnings - Tests: 72 (core) + 32 (Wpf) pass. The test harness was reworked: shared lists are locked, and order and "nothing more
happened" checks wait for explicit signals rather than sleeps. - New regressions (10-R15…R27, 9-V1…V3): each fails on the code before its fix; tests with several cases were checked one
case at a time. - Guards removed one at a time (17 in
ReconnectingCam), pre-change harness vs reworked harness: the reworked harness catches
a superset. Two guards no test caught are now pinned (10-R22). - Four review passes over this range. The third pass found a regression introduced by the second round of fixes (a failed
Openafter the loop gave up dropped the live intent). It is fixed and pinned (10-R26). The last pass found no defect in the
code, only missing tests and docs, which were added. - Hardware: none for these changes (fake camera only). A consumer's field test of 0.29.2 (two Crevis cameras, both links
pulled and restored) reconnected and restored live. -
mainis an ancestor of this branch (the release-pr-guard job verifies it) - the tag will be created on the merge commit,
mainmerged back intodevafterwards, anddevbumped to the next-devversion