Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions highway_env/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import sys

from gymnasium.envs.registration import register
from gymnasium.envs.registration import register, registry


__version__ = "1.10.2"
Expand All @@ -20,7 +20,15 @@


def _register_highway_envs():
"""Import the envs module so that envs register themselves."""
"""Import the envs module so that envs register themselves.

This function is idempotent: calling it multiple times (e.g. when
gymnasium resolves a ``"highway_env:env-id"`` spec in a subprocess)
will not raise duplicate-registration errors.
"""
# Skip if environments are already registered (idempotent)
if "highway-v0" in registry:
return

from highway_env.envs.common.abstract import MultiAgentWrapper

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ Repository = "https://github.com/eleurent/highway-env"
Documentation = "https://highway-env.farama.org/"
"Bug Report" = "https://github.com/eleurent/highway-env/issues"

[project.entry-points."gymnasium.envs.__root__"]
highway-env = "highway_env:_register_highway_envs"

[tool.setuptools]
include-package-data = true

Expand Down
59 changes: 59 additions & 0 deletions tests/envs/test_multiprocessing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Test that highway-env environments work with multiprocessing (forkserver/spawn).

When using SubprocVecEnv from stable-baselines3 or similar vectorized
environment wrappers, child processes are started via ``forkserver`` or
``spawn`` and do **not** inherit the parent's ``import highway_env``.

Gymnasium's ``module:env_name`` syntax (e.g. ``"highway_env:highway-v0"``)
triggers an import of the module in the subprocess, which registers the
environments on demand.

See: https://github.com/Farama-Foundation/HighwayEnv/issues/648
"""

import multiprocessing as mp

import gymnasium as gym
import pytest


def _make_env_in_subprocess(env_id: str, result_queue: mp.Queue) -> None:
"""Create and step an environment inside a subprocess (no prior import of highway_env)."""
try:
env = gym.make(env_id)
obs, _info = env.reset()
_obs, _reward, _terminated, _truncated, _info = env.step(
env.action_space.sample()
)
env.close()
result_queue.put(("ok", str(type(obs))))
except Exception as exc:
result_queue.put(("error", f"{type(exc).__name__}: {exc}"))


@pytest.mark.parametrize(
"env_id",
[
"highway_env:highway-v0",
"highway_env:highway-fast-v0",
"highway_env:merge-v0",
"highway_env:roundabout-v0",
"highway_env:intersection-v0",
"highway_env:parking-v0",
],
)
@pytest.mark.parametrize("start_method", ["forkserver", "spawn"])
def test_env_in_subprocess(env_id: str, start_method: str) -> None:
"""Environments should be creatable in forkserver/spawn subprocesses via module:name syntax."""
if start_method not in mp.get_all_start_methods():
pytest.skip(f"{start_method} not available on this platform")

ctx = mp.get_context(start_method)
q: mp.Queue = ctx.Queue()
p = ctx.Process(target=_make_env_in_subprocess, args=(env_id, q))
p.start()
p.join(timeout=30)

assert not q.empty(), "Subprocess produced no result (likely crashed)"
status, detail = q.get()
assert status == "ok", f"Subprocess failed: {detail}"
Loading