You press Record a second time and the run dies: the camera list builds, each camera grabs one image, then the application reports that something still holds a reference to the camera and the worker process exits. This guide is for teams driving GigE Vision cameras from a GUI that spawns an acquisition process for each recording — a design the Python multiprocessing module makes easy and risky at once. You will find which process or thread holds control, release it in the order the SDK expects, and change one design decision so the error cannot come back.
What the error actually means
GigE Vision control is a single token. The camera grants control access — the Control Channel Privilege, exposed as the GevCCP feature — to exactly one application at a time, and refuses the second. Most SDKs word that refusal as a reference problem: something still holds the camera. The classic reproduction is a viewer left connected while you run an acquisition example from the command line; the example loses, because the viewer holds the token. For the same exclusivity seen from the tool side, read GigE camera open fails in a second tool: exclusive control; this page is about your own software being both applicants at once.
The wording matters for diagnosis. The camera did not fail and the lens did not fail: a reference in a process — yours or another tool's — still points at the camera, so the SDK refuses to hand the object to the new run. Every fix below destroys that reference or ends its process. Any GigE Vision camera behaves this way, whatever its maker or resolution.
A typical example of the camera this page is about is a 6 MP mono GigE area scan camera with a C-mount and a 1/1.8" sensor.
- Sensor format
- 1/1.8"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- 6-10 MP
Cause 1: a thread of your own application still holds the camera
The failing signature names this cause before any tool does. The second run builds its camera list, Init() succeeds, each camera returns one image, and only then the error fires. Init() does not succeed without control, so the previous run had released the camera. What it had not released was an object held by a thread that outlived the first worker: in resolved cases with exactly this signature, the answer was a GUI thread still holding a camera instance.
Test it before changing code. After a recording finishes and before the next one starts, print the live threads of the GUI process with threading.enumerate() and look for anything from the acquisition side — a preview loop, a callback, a logger that captured an image object. Any name that belongs to the camera stack is your holder.
The fix is an ownership rule: the process that calls Init() on a camera is the only code that ever touches it. Camera objects, image objects and the system instance never cross the process boundary; frames cross as arrays through a queue. When the worker exits, nothing in the parent can hold a stale reference, because the parent never had one. The in-process version of the same rule is in multiple GenTL cameras in one process: one acquisition object each.
Cause 2: the worker process was never reaped
Calling sys.exit() inside the child starts interpreter teardown; it does not make the process vanish. Until teardown completes, the child still exists in the process table with every camera reference it created, and a GUI that spawns the next worker immediately races it. The Python multiprocessing module's documentation gives the answer: join() blocks the caller until the child has exited, and only a joined child is finished for certain.
Test: after the first recording, list running processes. If a worker from the previous run is still alive — or dies a moment after you already started the next one — the race is real on your machine.
Fix: join the worker with a timeout longer than your longest video save, read its exitcode to confirm a clean exit, and only then re-enable the Record button. The same stop-before-restart discipline resolves the USB cousin of this symptom, described in USB camera timeout on reopen: stop acquisition before you close.
Cause 3: a viewer or second tool holds the camera
Development machines accumulate camera software: a viewer from an earlier debug session, a second instance of your own program, a vendor utility left running. Each takes control on connect. You can reproduce the error on demand by connecting a viewer and then running an acquisition example; the example reports the camera as held. Close every tool that can open a camera, then run one acquisition from exactly one program. If the error is gone, the holder was a tool, not your code — the same first move as in camera detected but no image: check if another app holds the camera.
Symptoms, tests and fixes
| Symptom | Most likely holder | Test | Fix |
|---|---|---|---|
| Error at open, before any image | Viewer or second tool | Close all camera tools, rerun one program | One controller at a time |
| One image per camera, then the error | Surviving thread in your GUI | threading.enumerate() after a run | Camera objects never leave the worker |
| Works after a wait, fails on immediate retry | Unreaped child process | Process table between runs | join() and read exitcode before restart |
| Only one camera of a pair fails | A tool, or a duplicate address on that device | Enumerate devices, read each control state | Remove the holder or fix the address |
Worked example: two cameras, one worker process, the second recording run
Inputs, all from the failing pattern this page addresses: a GUI; a worker process created with multiprocessing for each recording; two GigE cameras. Per run, the worker builds a camera list, calls Init() on both cameras, streams with GetNextImage() and image.Release(), then calls DeInit() on both, Clear(), deletes the camera objects and calls system.ReleaseInstance() before exiting. The failing run grabs and releases one image per camera, then dies.
The rule reads like a conservation law: everything the SDK hands out must come back, and every process that touched a camera must be gone, before the next Init().
Run the ledger on the failing pattern. Cameras: 2 Init() calls and 2 DeInit() calls — balanced. Images before the crash: 2 GetNextImage() calls and 2 Release() calls — balanced. System: 1 instance and 1 ReleaseInstance() — balanced. Workers: 1 started per recording, and nothing in the pattern joins it — count 1.
The SDK ledger balances at zero, so the call order is not the fault. The surviving counts sit at process and thread level: 1 unjoined child, plus any number of GUI-thread references above zero. One stale reference anywhere fails the run; zero is the only passing count. That is why the confirmed fix for this signature was a thread, and why the checklist below starts with process lifetime, not with SDK calls.
The teardown order that releases control
Do the destruction in reverse order of creation, images first, system last:
- Stop acquisition on each camera.
- Release every outstanding image: one Release() for each GetNextImage() that has not yet returned its buffer.
- Call DeInit() on each camera you Init()ed.
- Clear() the camera list once no live camera objects remain.
- Release the system instance, once, after every camera is gone.
- Exit the worker; the GUI joins it and reads the exitcode.
- Start the next run only after steps 1 through 6 are complete in the parent's own bookkeeping.
The memory-management traps that bite in C++ around this same sequence are covered in camera SDK access violation on close: fix teardown order. If your runs are single frames rather than videos, each repeated start still needs its stop — single frame acquisition still busy: why repeated starts need a stop shows the loop case.
When the cleanup answer is wrong
Two findings redirect you. First, power-cycle the camera: if the error survives a full power cycle of camera and host, the holder lives in a process on that machine — find it in the process table — or the device address is duplicated on the network; neither is a teardown bug. Second, look at the link: a connection that drops packets starves the camera of heartbeats, the camera reopens control on its own mid-run, and your next SDK call reads as a lost reference. Reseat or replace the cable before auditing code.
The LS-CBL-0199 is a 3 m GigE camera cable, high-flex for drag-chain use, with screw locks.
For an error that appears on the very first run after boot, skip this page's causes and read release a camera after a crash on Linux: GigE restart fix for the network-level recovery path.
Prevention: one acquisition worker for the whole session
The permanent fix is architectural. Start the worker once, when the application launches; Init() both cameras once; let the worker idle between recordings and pass frames or previews to the GUI through a queue. The GUI owns buttons and plots, never camera objects. Teardown happens once, at shutdown, in the order above. Applications built this way never race their own teardown, and the same long-lived worker is what keeps a GUI responsive during capture — the in-process version is GUI freezes during image capture: run fetch off the main thread.
Planning a two-camera station from scratch? Start from GigE area scan cameras or the camera finder, and the configurator below walks the stack step by step:
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
Checklist before the next run
- Close every viewer and utility that can open a camera.
- After each recording: worker joined, exitcode read, process gone from the table.
- threading.enumerate() in the parent shows no acquisition-side names.
- Every GetNextImage() of the last run matched by one Release().
- DeInit() count equals Init() count; Clear() and ReleaseInstance() each ran once.
- Power cycle used as a diagnostic only; a repeat failure means the holder is local.
More integration-failure guides are collected under the systems topic. If a holder survives every check above, send the teardown code and a process listing through contact and we will trace it with you.
Questions
Can two programs share one GigE camera?
Not for control. GigE Vision grants control access to one application at a time; a second program that tries to change settings or start acquisition gets the busy error. Some cameras can stream the same image data to extra listeners over multicast, so a monitoring tool can watch frames, but it still cannot control exposure, trigger or teardown. Design around one controller: a single acquisition service that owns the cameras, with everything else asking it for frames.
Does power cycling the camera clear the error?
Yes, and it is the fastest way to tell where the holder lives. Pull power for a few seconds: control resets to open and the camera is controllable again. If the error returns on the very next run, the holder is inside your own application — a thread or an unreaped child process — and no amount of camera resets will keep it away. Treat the power cycle as a diagnostic, not as the production fix.
How long must I wait between runs if the previous holder crashed?
Until the camera stops receiving heartbeats for longer than its heartbeat timeout. Every GigE Vision controller sends periodic heartbeat packets; when they stop, the camera reclaims control and opens access again. The timeout is exposed as the GevHeartbeatTimeout feature on conformant cameras and is typically a few seconds, which is why waiting briefly between runs often fixes the timing case. A clean teardown releases control immediately, with no wait at all.
Is it better to keep the cameras open between recordings?
Yes. Recreating the whole stack — system, camera list, Init — for every recording multiplies the chances of a stale reference. Start one worker process when the application launches, Init both cameras once, and let it idle between recordings. The GUI sends start and stop commands over a queue, the worker streams saved frames back, and teardown happens once, at shutdown. You trade a little memory for an entire error class that disappears.
One camera of a pair keeps failing — same cause?
Look at that camera's holder first. With two devices, the failing one is usually the one a viewer, an old debug session or a duplicate address touches. Read its control state before you blame the code: a GenTL enumeration tool shows which application holds control of each device. If both cameras fail together, the cause is process lifetime in your application, not either device.

