Unity-Technologies/ml-agents · error · UnityWorkerInUseException

Couldn't start socket communication because worker number {}

Error message

Couldn't start socket communication because worker number {} is still in use. You may need to manually close a previously opened environment or use a different worker number.

What it means

UnityWorkerInUseException is raised by RpcCommunicator.check_port when a socket bind to (localhost, worker_id port) fails with OSError. It means the port assigned to this worker_id (base port 5005 + worker_id) is already occupied, typically by a previous Unity environment that hasn't fully released the port (TIME_WAIT) or is still running.

Source

Thrown at ml-agents-envs/mlagents_envs/rpc_communicator.py:89

            self.server.add_insecure_port("[::]:" + str(self.port))
            self.server.start()
            self.is_open = True
        except Exception:
            raise UnityWorkerInUseException(self.worker_id)

    def check_port(self, port):
        """
        Attempts to bind to the requested communicator port, checking if it is already in use.
        """
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        if platform == "linux" or platform == "linux2":
            # On linux, the port remains unusable for TIME_WAIT=60 seconds after closing
            # SO_REUSEADDR frees the port right after closing the environment
            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        try:
            s.bind(("localhost", port))
        except OSError:
            raise UnityWorkerInUseException(self.worker_id)
        finally:
            s.close()

    def poll_for_timeout(self, poll_callback: Optional[PollCallback] = None) -> None:
        """
        Polls the GRPC parent connection for data, to be used before calling recv.  This prevents
        us from hanging indefinitely in the case where the environment process has died or was not
        launched.

        Additionally, a callback can be passed to periodically check the state of the environment.
        This is used to detect the case when the environment dies without cleaning up the connection,
        so that we can stop sooner and raise a more appropriate error.
        """
        deadline = time.monotonic() + self.timeout_wait
        callback_timeout_wait = self.timeout_wait // 10
        while time.monotonic() < deadline:
            if self.unity_to_external.parent_conn.poll(callback_timeout_wait):
                # Got an acknowledgment from the connection

View on GitHub (pinned to 3ecb446f75)

Solutions

  1. Close or kill the previously running Unity environment process still bound to the port (check with lsof -i :5005 or netstat).
  2. Wait ~60 seconds for TIME_WAIT to release, or pass a different worker_id (e.g. UnityEnvironment(worker_id=1)) which uses port 5006, etc.
  3. Call env.close() on every environment before script exit, including in except/finally blocks.
  4. Restart the Unity editor if it was relaunched with the previous training run still attached.

Example fix

// before
env = UnityEnvironment(file_name=env_path, worker_id=0)
...
env = UnityEnvironment(file_name=env_path, worker_id=0)  # UnityWorkerInUseException
// after
env = UnityEnvironment(file_name=env_path, worker_id=0)
try:
    ...
finally:
    env.close()
# or for parallel runs:
env2 = UnityEnvironment(file_name=env_path, worker_id=1)
Defensive patterns

Strategy: retry

Validate before calling

import socket

def port_free(worker_id: int) -> bool:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    try:
        s.bind(("localhost", 5005 + worker_id))
        return True
    except OSError:
        return False
    finally:
        s.close()

# before creating: pick a free worker_id
# wid = next(w for w in range(10) if port_free(w))

Type guard

import socket

def is_port_available(port: int) -> bool:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        try:
            s.bind(("localhost", port))
            return True
        except OSError:
            return False

Try / catch

from mlagents_envs.exception import UnityWorkerInUseException

for worker_id in range(4):
    try:
        env = UnityEnvironment(file_name=env_path, worker_id=worker_id)
        break
    except UnityWorkerInUseException:
        continue
else:
    raise RuntimeError("No free worker ports available")

Prevention

When it happens

Trigger: Calling UnityEnvironment / create_server with a worker_id whose port (5005+worker_id) is bound by a still-running or recently-closed Unity editor/executable; binding twice without closing the first environment; the OS holding the port in TIME_WAIT for ~60s after closing (mitigated by SO_REUSEADDR).

Common situations: Running multiple training runs in parallel with the same worker_id; a crashed Unity process still holding port 5005; re-running a script right after killing the previous one; Jupyter notebooks where old envs were never closed.

Related errors


AI-assisted analysis of Unity-Technologies/ml-agents@3ecb446f75 (2026-09-02). Data as JSON: /api/errors/07c5856a11338fe1. Report an issue: GitHub.