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 connectionView on GitHub (pinned to 3ecb446f75)
Solutions
- Close or kill the previously running Unity environment process still bound to the port (check with lsof -i :5005 or netstat).
- 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.
- Call env.close() on every environment before script exit, including in except/finally blocks.
- 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
- Always call env.close() in a finally block or context manager.
- Use a unique worker_id per concurrent training process.
- Kill orphaned Unity processes before re-running scripts (lsof -i :5005).
- Check the port with a bind test before launching the environment.
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
- The Unity environment took too long to respond. Make sure th
- Compressed observation and its mapping had different number
- Invalid Compressed Channel Mapping: the mapping {mappings} d
- Invalid Compressed Channel Mapping: the mapping has index la
- Observation at index={obs_index} for agent with id={agent_in
AI-assisted analysis of Unity-Technologies/ml-agents@3ecb446f75 (2026-09-02).
Data as JSON: /api/errors/07c5856a11338fe1.
Report an issue: GitHub.