System designTroubleshooting

V4L2 device busy after restart: the old node still holds the camera

Device or resource busy after a restart means the old camera process still holds the V4L2 node. Find the holder with lsof, stop it, keep rosbag record running.

LumaSpec Vision9 min read Share on LinkedIn

On this page 9 sections
  1. 01What the log actually says
  2. 02The rule: one streaming owner per node
  3. 03Why rosbag record takes the blame
  4. 04Likely causes, in order of probability
  5. 05Worked example: the 2048 × 1544 GREY node that relaunches busy
  6. 06The restart sequence that works with the recorder on
  7. 07Keep it from happening again
  8. 08When the holder list is empty and it is still busy
  9. 09Questions

Stop the recorder, and the relaunch works. Start rosbag record -a again, and the next restart of your camera node fails with Failed to set format: Device or resource busy. That correlation sends most teams into a loop of stopping the recorder before every relaunch, and it is the wrong fix for a problem that is easy to name: a process still holds the open device file, and a V4L2 node accepts one streaming owner at a time. This guide decodes the error sequence line by line, shows the commands that name the holding process, and gives a restart order you can run with the recorder left on. The checks are the same for a raw CSI sensor, a USB capture card, or a board-level camera built into your own housing.

What the log actually says

Read the lines in order: only the first is a real failure.

  • Failed to set format: Device or resource busy — the node's format ioctl, VIDIOC_S_FMT, was refused because another opener already holds the streaming state.
  • New Pixel Format: GREY, New Width: 2048, New Height: 1544 — your own code echoing the format it requested. Nothing was rejected on its merits.
  • Failed to get stream parameters: Inappropriate ioctl for device and Frame rate control not supported — the driver declining a frame-rate query, which most raw sensor paths do not expose. This is a notice, not a failure.
  • VIDIOC_REQBUFS: Bad file descriptor and the remaining lines — the capture loop kept calling ioctls after setup had already failed, so each later call lands on a descriptor it can no longer use.

One genuine refusal, then fallout. The sibling case where two nodes open one camera shows the same refusal from the second opener's side; here the two openers are your old node and your new one. Treat the busy error as the whole diagnosis: the settings are fine, the ownership is not.

The rule: one streaming owner per node

A V4L2 device node tolerates several open file handles, and plain queries can run concurrently. Exclusivity applies where state changes: setting a format, requesting buffers, starting streaming. While any process holds the node configured for streaming, VIDIOC_S_FMT from a second process returns EBUSY, errno 16. That answer is generic Unix rather than camera-specific: the same errno 16 device-busy pattern on a GPIO line resolves the same way, by finding the claimant that got there first.

The consequence for a restart is worth stating plainly. Your relaunch does not race against rosbag; it races against the previous instance of your own node. The open call often succeeds, and the launch dies at the first exclusive ioctl, which is why the log shows a format failure rather than an open failure.

Why rosbag record takes the blame

rosbag record -a subscribes to every topic in the ROS graph, /image_raw included, and writes the arriving messages into a bag file, so it never touches the device. What it changes is timing. With a live subscriber attached, the node's shutdown — stop streaming, release buffers, close the capture — can lag your Ctrl+C by seconds, longer when the process is mid-callback. Start the next roslaunch inside that window and the new instance loses the race: old process alive, node held, EBUSY.

Drop the recorder first and the shutdown window shrinks, so the relaunch wins. That is exactly why the workaround works and why the recorder looks guilty. The durable fix is to make the old process provably dead before relaunching; the recorder never has to stop.

Likely causes, in order of probability

Work down this table; the first three cover nearly every occurrence.

# Cause Test Fix
1 The previous camera node has not exited yet sudo lsof /dev/video0 names a PID from the old launch Terminate that PID, then relaunch
2 The launch file started twice; one instance already streams ps -ef filtered on the node name shows two copies Kill the older copy and gate the launch
3 A second consumer holds the node: a viewer, a test script, a second capture object lsof shows a command you did not expect Close it; give one process sole ownership
4 A crashed child left the file handle open, or a platform camera service holds the sensor lsof names a process you believed was stopped Terminate it; power-cycle the camera computer if it survives
5 You are gating the wrong node: several /dev/video* entries and the camera moved v4l2-ctl --list-devices, then v4l2-ctl -d <node> --all to confirm the format Identify the node first; the device-node identification guide walks the steps

Worked example: the 2048 × 1544 GREY node that relaunches busy

The inputs, and where each comes from:

  • Setup: a ROS node reads a raw CSI camera through V4L2 in OpenCV and publishes /image_raw; rosbag record -a is running; the launch restarts with the recorder up. This is the failing case.
  • Log facts: the requested format is GREY at 2048 × 1544, and the first refusal lands on VIDIOC_S_FMT.
  • Node path: written here as /dev/video0; confirm yours with v4l2-ctl --list-devices.

First, clear the format of suspicion. GREY is 8-bit mono, one byte per pixel:

bytes per frame = width × height × bytes per pixel = 2048 × 1544 × 1 B = 3,163,392 B ≈ 3.02 MB

At 3.02 MB per frame the geometry is ordinary, and the same driver accepted it on the previous run. Nothing about 2048 × 1544 GREY can be the refusal, which leaves ownership.

Find the holder:

sudo lsof /dev/video0

A clean failure shows exactly one holder, and its PID belongs to the previous launch. The shape of the output:

COMMAND   PID  USER  FD  TYPE DEVICE SIZE/OFF NODE NAME
python3  7412  ros   9u  CHR  81,0     0t0   /dev/video0

The PID and command name will be yours; what matters is the count — one holder that is not your new launch. On a machine without lsof, fuser -v /dev/video0 names the same holder.

The decision, with its threshold: zero other holders before you launch, exactly one after. Terminate the named PID (kill 7412, escalating to kill -9 after a few seconds), relaunch, and the log applies GREY at 2048 × 1544 and reaches streaming with no busy line. If lsof prints nothing and the format still fails, skip to the last section: the holder is in another namespace or the driver is wedged.

The restart sequence that works with the recorder on

  1. Interrupt the old launch once and wait five to ten seconds; the node needs that long to stop streaming and close the capture.
  2. Run sudo lsof /dev/video0. Empty is what you want before a relaunch.
  3. If a PID from the old launch remains, terminate it: kill <PID>, then kill -9 <PID> if it survives a few seconds.
  4. Start the camera launch. Leave rosbag record -a alone throughout.
  5. Confirm the new ownership: the holder list shows one process, the new node, and the log reaches streaming with the applied 2048 × 1544 GREY format.

Keep it from happening again

  • Handle the shutdown path in the node. On the interrupt signal, stop streaming and close the capture before exiting; in OpenCV terms, release the capture object. A node that only dies when the kernel forces it is the one that lingers.
  • Change settings without restarting. Exposure, format and trigger settings belong in runtime parameters. Most restarts exist only to apply a new setting, and a parameter update removes them.
  • Gate the launch. A first line that runs lsof on the node and aborts when a holder appears turns a mysterious EBUSY into a one-line message.
  • Pin the device path when more than one capture node exists, so a replug does not move the camera to a different /dev/videoN. The device path conflict fix covers the stable-path method.
  • Prefer fewer, longer sessions. The restart-between-captures guide and the open-close handle leak guide both land on the same design: one session with explicit start and stop.
  • Watch the trigger wait. A node parked on a hardware trigger can sit inside a blocking capture call while you believe it is stopping; give the wait loop a timeout and check the interrupt flag there. The keep-open pattern between triggered grabs shows the session design that fits.

When the holder list is empty and it is still busy

Three situations survive the lsof check.

The node runs inside a container. The holder lives in the container's namespace, invisible to a host-side lsof; run the check inside the container. While you are there, confirm the V4L2 and media nodes are passed through, not just one video device — the Docker node pass-through guide lists what a capture stack needs.

The driver is wedged after a crash. No holder anywhere, yet the format still fails: stop the platform's capture stack or power-cycle the camera computer. A clean driver comes back with an empty holder list and accepts the format on the first try.

The capture pattern opens per shot. If the design opens and closes the node for every frame, the busy window is the capture itself, and the sequencing has to be exact. The repeated-start busy guide and the busy-buffers stop guide cover that pairing.

If the camera side of the setup is the part you are reconsidering rather than debugging, the area scan cameras category filters by interface and shutter, the camera finder narrows by sensor and mount, and the systems guides collect the rest of these node-level fixes.

Questions

Does rosbag record open the camera device?

No. rosbag record subscribes to ROS topics such as /image_raw and writes the serialized messages into a bag file. It never opens the device node and never issues a V4L2 ioctl, so it cannot make the device busy. Once the previous node process is gone, the recorder can keep running across any number of camera restarts.

Can two processes open one V4L2 node at the same time?

Often yes at the open stage: drivers allow several handles for queries. Exclusivity is enforced on state-changing calls, meaning setting a format, requesting buffers and starting streaming. If another handle still owns the streaming state, VIDIOC_S_FMT returns EBUSY before a single frame moves, which is why a failing relaunch dies at the format step rather than at open.

Why does the rest of the log fill with Bad file descriptor errors?

The busy error at the format set is the only real failure in that sequence. A capture loop that continues after a failed setup calls ioctls on a descriptor it has already closed or on state that was never applied, so every later call reports EBADF or ENOTTY. Clear the one busy refusal and the cascade disappears with it.

Does the one-owner rule apply to GigE Vision and USB3 Vision cameras?

The rule is universal; the mechanism differs. GigE Vision and USB3 Vision cameras are not opened through a /dev/videoX node, since frames travel over the network or a dedicated USB transport, so a second process produces a library-level in-use error instead of an EBUSY ioctl failure. On those cameras, check who holds the device through the SDK's own tools rather than lsof.