commaai/openpilot · error · SerialException

could not exclusively lock port {self._port}: {e}

Error message

could not exclusively lock port {self._port}: {e}

What it means

When opened with exclusive=True, the port takes a non-blocking flock(LOCK_EX). If another process already holds the lock, OSError (EWOULDBLOCK) is converted to SerialException 'could not exclusively lock port'. This is a deliberate single-owner guard for UART devices shared between services.

Source

Thrown at openpilot/common/serial.py:81

    self._dtr = bool(value)
    if self._fd >= 0:
      self._set_line(TIOCM_DTR, _TIOCM_DTR, self._dtr)

  def open(self) -> None:
    if self._fd >= 0:
      return
    try:
      self._fd = os.open(self._port, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
    except OSError as e:
      self._fd = -1
      raise SerialException(e.errno, f"could not open port {self._port}: {e}") from e

    try:
      if self._exclusive:
        try:
          fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
        except OSError as e:
          raise SerialException(e.errno, f"could not exclusively lock port {self._port}: {e}") from e

      self._configure()

      # When not using hardware DSR/DTR handshaking, drive lines ourselves.
      if not self._dsrdtr:
        try:
          self._set_line(TIOCM_DTR, _TIOCM_DTR, self._dtr)
          if not self._rtscts:
            self._set_line(TIOCM_RTS, _TIOCM_RTS, True)
        except OSError as e:
          if e.errno not in (errno.EINVAL, errno.ENOTTY):
            raise

      self.reset_input_buffer()
    except BaseException:
      self._close_fd()
      raise

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Find and stop the other holder: lsof /dev/ttyUSB0 or fuser
  2. Start your service only after the previous instance fully exited (check supervisor logs)
  3. If sharing is intended, open with exclusive=False — but beware interleaved writes

Example fix

// before
ser = Serial(port="/dev/ttyUSB0", exclusive=True)

// after
ser = Serial(port="/dev/ttyUSB0", exclusive=False)
Defensive patterns

Strategy: validation

Validate before calling

import fcntl, os

def port_lock_free(port: str) -> bool:
    try:
        fd = os.open(port, os.O_RDWR | os.O_NONBLOCK)
    except OSError:
        return False
    try:
        fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
        return True
    except OSError:
        return False
    finally:
        os.close(fd)

Try / catch

try:
    ser.open()
except SerialException as e:
    if "exclusively lock" in str(e):
        report_port_in_use(ser._port)

Prevention

When it happens

Trigger: Opening a port with exclusive=True while another process (e.g. a modem daemon or a previous crashed-but-alive instance) has the device open.

Common situations: Two services configured for the same tty; a hung prior process still holding the fd; running a debug tool against a port the main daemon owns.

Related errors


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/19b36ab8f9c38ddd. Report an issue: GitHub.