Skip to content

v0.29.4

Choose a tag to compare

@github-actions github-actions released this 28 Sep 09:26
· 54 commits to main since this release
a2ae848

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. MaxAttempts and the BackoffMs ladder 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 after Dispose.
    • 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; Close and Dispose still wake it. If the other Open
    connected, the loop has nothing left to do. If it failed, the loop waits one step before trying.

  • Open() right after a loss reopens. Until the loop's first attempt (1 s by default), Open() returned normally without
    doing anything and IsConnected stayed false. That contradicted ICam.Open and the 0.29.2 note "Open() after a loss restores
    live". If that Open fails, the loop keeps retrying; the exception does not mean the loop gave up.

  • When Open and Close overlap on different threads, the one called last wins. Before, whichever got the internal gate
    last decided, and a later caller can get it first. An Open queued before a Close could reopen after it, and the Close then
    removed only that session. The wrapper was then neither closed nor reconnecting — "Camera is reconnecting." forever. In the
    opposite order, a later Open was undone.

  • An Open that fails on a closed wrapper leaves it closed (first open, or open after Close, with RetryInitialOpen off).
    Before, a StartContinuous in between was silently recorded and started live on the next successful Open. An Open that
    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 failed Open there is not retried in the background — call Open() again (or enable RetryInitialOpen).

  • 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.

  • Dispose waits at most ShutdownWaitMs in 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.

  • StartContinuous while closed (before the first Open, after Close) 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 Close won no longer says "retrying in
    the background", and an open that finishes failing after Close is logged at Info, not Warning.

Imaging — VirtualCam

  • Every stop waits for the tick that is running. A second StopContinuous, a Close, a Dispose or a GrabOne used 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 a GrabFrameAsync that subscribes
    during a stop gets its own frame. The wait no longer depends on how the runtime implements Timer.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 AcquisitionStop after 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
    AcquisitionStop failures are still warnings.

Contract notes (documentation, no API change)

  • ICam.IsGrabbing must not throw, even after Dispose (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.MaxAttempts and BackoffMs count real failures only. ShutdownWaitMs bounds each Close and Dispose call
    separately, so Close followed by Dispose can 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 == false it throws if that open fails (it returned silently); the loop keeps retrying.
  • Open/Close racing on different threads: the one called last wins.
  • A reconnect attempt waits for a manual Open in progress instead of giving up on it. A manual Open that hangs keeps the loop
    waiting; Close/Dispose still wake it.
  • Hosts with MaxAttempts > 0 see "giving up" only after that many real failures.
  • Log text: the Warning initial open failed — retrying in the background. is now open 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.
  • Dispose returns after ShutdownWaitMs even 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, set ShutdownWaitMs above the worst-case open time (discovery plus open).
  • VirtualCam.GrabOne, StopContinuous and Close can 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-incremental reports 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
    Open after 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.
  • main is an ancestor of this branch (the release-pr-guard job verifies it)
  • the tag will be created on the merge commit, main merged back into dev afterwards, and dev bumped to the next -dev version