Conversation
Decision 0033: after X.Y.Z, main carries X.Y.(Z+1)-DEV. TagOnMerge skips a -DEV version. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… every exit The quickbeam rig saw capture hang at full frame, 12.5 ms (v0.2.4). Fixes: - DCAMWAIT_START is built with its size (it was sent as 0), and a negative timeout (DCAM's INFINITE) is refused before any library call. - Every frame wait is bounded by capture_timeout_ms: 2 x (exposure + readout) + 1 s, with the readout read from the camera (TIMING_READOUTTIME); getdata scales it by N. - capture stops and releases any earlier capture first, arms the wait before dcamcap_start, and stops, releases and closes on every exit. A timeout or failed wait logs, sets last_error and throws a clear error. - getlastframe and getdata no longer destructure dcamwait_close's single DCAMERR (a MethodError on every failure path). getdata reads an ended cycle at once, never passes DCAM a null wait handle, and cleans up on every exit (in LIVE mode it now ends the live view). - test/dcam4_pure.jl covers what runs without the DCAM library. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…olls against a deadline, sequence's poller has one Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… a newer capture, getdata leaves a live view running Also: getdata reads a READY buffer that never ran as LOSTFRAME, and capture and getlastframe set last_error on a failed frame copy. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
kalidke
added a commit
that referenced
this pull request
Sep 30, 2026
Merged
Member
Author
@dcamcall replaces @CCall at all 30 DCAM call sites. With tracing on (DCAM4.dcam_trace!(path) or ENV MC_DCAM4_TRACE) it writes a flushed BEGIN and END line per call with arguments, elapsed time, return value and cumulative GC time, so a hang inside a ccall leaves its last BEGIN on disk. Off, it costs one Ref{Bool} check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DCAM4
capturehang, reported from the quickbeam rig on v0.2.4. At full frame (2304x2304) and standard readout,capturehangs indefinitely at 12.5 ms and at 100 ms. At 100 ms it hangs as the FIRST capture in a fresh kernel,spinning (131 CPU-s), and interrupts do not land. Full frame works at 232 ms, and at 5 ms right after an ROI switch;
256x256 works. The DCAM4 code is identical at v0.2.4, v0.2.5 and main. For 0.2.6, with #73 and #74.
What was wrong
dcamwait_eventsentDCAMWAIT_STARTwithsize = 0. The sizing constructor existed but was never used.capturearmed the frame wait afterdcamcap_start, so a frame that was ready early could be missed.getlastframeandgetdatadiderr, hwait = dcamwait_close(hwait).dcamwait_closereturns one
DCAMERR, so that line threw a MethodError. It threw before any cleanup, andcapturestopped nothingand released nothing on failure.
getdata's end-of-sequence timeout ignored the readout time.sequence's status poller had no deadline.The most likely reading of the rig pattern:
interrupts cannot reach.
dcamwait_abortwatchdog) is with the rig session, to confirm it. If the DLL ignores the timeout even with thecorrect size, the next step is a watchdog that calls
dcamwait_abortfrom another thread.Where the struct layouts come from
dcamapi4.his not in this repository. The layouts were checked against the copy in the lab instrument archive:manuals/Hamamatsu/SDK/dcam-api/include/sdk4-v22126552/dcamapi4.h.manuals/is the gitignored symlink to theNAS archive; see CLAUDE.md.
DCAMWAIT_START(:815-822) is fourint32:size[in],eventhappened[out],eventmask[in] andtimeout[in]. That is 16 bytes.DCAMWAIT_OPEN(:806-813) isint32 size,int32 supportevent,HDCAMWAIT hwaitandHDCAM hdcam. That is24 bytes on 64-bit.
DCAMWAIT_TIMEOUT_INFINITEis0x80000000(:393), which is negative as anInt32, and this PR refuses it.What changes (DCAM4 files only)
DCAMWAIT_STARTcarries its size, and a negative timeout is refused before any library call.capture_timeout_ms= 2 x (exposure + readout) + 1 s.DCAM_IDPROP_TIMING_READOUTTIME, read aftersetroi!bylive,sequenceandcapture, and cached forgetlastframe. A camera that does not report it gets 1 s, with one warning.capture:releases a buffer that another task's
getlastframewait may be blocked on; that crashes the process.dcamcap_start, and on every exit stops,releases and closes.
last_errorand throws a clear error.getdata:for the end-of-cycle event. An ended sequence cannot be missed, and an interrupt can land while it waits.
nothing, withlast_errorset from the copy'sDCAMERR(
dcambuf_getframe_err).nothing, withlast_errorset to
DCAMERR_LOSTFRAME.is_runningunchanged. Releasing the buffer under a threaded live reader crashes the process.sequence: its poller gives up at the same deadline and stops a capture stuck running, sois_runningcannot stay true forever.
stop_and_release!increments acapture_generation, and everystart goes through it first, so a stale poller never stops or clears a newer live view or sequence.
is_runningfor its own sequence.stop_and_release!andabort:stop_and_release!handles every status (a capture in ERROR was neitherstopped nor released), and
abortis now that one call.getlastframe: a timeout or failed wait logs, setslast_errorand returnsnothing, and the wait handle isalways closed. A frame that cannot be copied sets
last_error, here and incapture.Compatibility (decisions 0033 and 0035)
Non-breaking. The paths that now throw a clear error, or return
nothing, threw a MethodError before. Onedeliberate behaviour change is listed under Changed:
capturewhile a live view or sequence runs now throws,where it used to fail at the buffer allocation and return
nothing. That path never produced a frame.MicroscopeAdapt (
control_functions.jl:250/319) and MicroscopeSeqSR (adapters.jl:91) callgetdataonly aftera finished sequence.
DCAM4Cameragains two fields (readout_s,capture_generation), with no keyword; the keyword constructor isunchanged.
Tests
test/dcam4_pure.jlcovers what runs without the DCAM library:DCAMWAIT_START(16);capture's refusal while running, before any library call;"dcamapi.dll", so a fake needs aswappable library, as the TCube and PI fakes have. Until then, the capture, poll and cleanup paths are exercised
only on a rig.
Rig checks owed (none run; no hardware)
captureat 100 ms, then 12.5 ms, 20 times each. Every call returnsa frame.
capturethrows the timeout errorwithin about 2 x (exposure + readout) + 1 s. Switch back, and the next
capturereturns a good frame with noreconnect.
live,capturethrows "Stop the live view or sequence first", andgetdatareturns a frame. Thelive view keeps running through both.
sequenceof 100 frames at 12.5 ms, thengetdata: it returns all 100 frames.2304 x 18.65 us (about 43 ms).
🤖 Generated with Claude Code