Skip to content

Commit e5d3d78

Browse files
Fix build lockfile (#466)
Move from `lockfile` library to `fasteners`
1 parent 83759c2 commit e5d3d78

File tree

5 files changed

+80
-27
lines changed

5 files changed

+80
-27
lines changed

mujoco_py/builder.py

+11-12
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
import distutils
2+
import glob
23
import os
34
import shutil
5+
import subprocess
46
import sys
57
from distutils.core import Extension
68
from distutils.dist import Distribution
79
from distutils.sysconfig import customize_compiler
10+
from importlib.machinery import ExtensionFileLoader
811
from os.path import abspath, dirname, exists, join, getmtime
912
from random import choice
1013
from shutil import move
1114
from string import ascii_lowercase
12-
from importlib.machinery import ExtensionFileLoader
13-
import glob
1415

16+
import fasteners
1517
import numpy as np
16-
from cffi import FFI
1718
from Cython.Build import cythonize
1819
from Cython.Distutils.old_build_ext import old_build_ext as build_ext
19-
from mujoco_py.version import get_version
20-
from lockfile import LockFile
21-
import subprocess
20+
from cffi import FFI
2221

2322
from mujoco_py.utils import discover_mujoco, MISSING_KEY_MESSAGE
23+
from mujoco_py.version import get_version
2424

2525

2626
def get_nvidia_lib_dir():
@@ -86,7 +86,7 @@ def load_cython_ext(mujoco_path):
8686

8787
lockpath = os.path.join(os.path.dirname(cext_so_path), 'mujocopy-buildlock')
8888

89-
with LockFile(lockpath):
89+
with fasteners.InterProcessLock(lockpath):
9090
mod = None
9191
force_rebuild = os.environ.get('MUJOCO_PY_FORCE_REBUILD')
9292
if force_rebuild:
@@ -104,6 +104,7 @@ def load_cython_ext(mujoco_path):
104104
if mod is None:
105105
cext_so_path = builder.build()
106106
mod = load_dynamic_ext('cymj', cext_so_path)
107+
107108
return mod
108109

109110

@@ -119,7 +120,7 @@ def _ensure_set_env_var(var_name, lib_path):
119120

120121

121122
def load_dynamic_ext(name, path):
122-
''' Load compiled shared object and return as python module. '''
123+
""" Load compiled shared object and return as python module. """
123124
loader = ExtensionFileLoader(name, path)
124125
return loader.load_module()
125126

@@ -144,7 +145,7 @@ def build_extensions(self):
144145

145146

146147
def fix_shared_library(so_file, name, library_path):
147-
''' Used to fixup shared libraries on Linux '''
148+
""" Used to fixup shared libraries on Linux """
148149
subprocess.check_call(['patchelf', '--remove-rpath', so_file])
149150
ldd_output = subprocess.check_output(['ldd', so_file]).decode('utf-8')
150151

@@ -154,7 +155,7 @@ def fix_shared_library(so_file, name, library_path):
154155

155156

156157
def manually_link_libraries(mujoco_path, raw_cext_dll_path):
157-
''' Used to fix mujoco library linking on Mac '''
158+
""" Used to fix mujoco library linking on Mac """
158159
root, ext = os.path.splitext(raw_cext_dll_path)
159160
final_cext_dll_path = root + '_final' + ext
160161

@@ -490,8 +491,6 @@ def build_callback_fn(function_string, userdata_names=[]):
490491
return module.lib.__fun
491492

492493

493-
494-
495494
def find_key():
496495
''' Try to find the key file, if missing, print out a big message '''
497496
if exists(key_path):

mujoco_py/tests/test_cymj.py

+65-11
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,25 @@
1-
import numpy as np
2-
import pytest
1+
import glob
2+
import os
3+
import shutil
34
import sys
5+
import time
46
import unittest
5-
67
from io import BytesIO, StringIO
78
from multiprocessing import get_context
89
from numbers import Number
9-
from numpy.testing import assert_array_equal, assert_array_almost_equal
10+
11+
import numpy as np
12+
import pytest
1013
# from threading import Thread, Event
1114
from PIL import Image
15+
from numpy.testing import assert_array_equal, assert_array_almost_equal
1216

13-
from mujoco_py import (MjSim, load_model_from_xml,
14-
load_model_from_path, MjSimState,
15-
ignore_mujoco_warnings,
16-
load_model_from_mjb)
17+
from mujoco_py import (
18+
MjSim, load_model_from_xml,
19+
load_model_from_path, MjSimState,
20+
ignore_mujoco_warnings,
21+
load_model_from_mjb
22+
)
1723
from mujoco_py import const, cymj, functions
1824
from mujoco_py.tests.utils import compare_imgs
1925

@@ -42,6 +48,26 @@
4248
"""
4349

4450

51+
def remove_mujoco_build():
52+
# Removes previously compiled mujoco_py files.
53+
print("Removing previously compiled mujoco_py files.")
54+
path = os.path.join(os.path.dirname(__file__), "generated")
55+
for fname in glob.glob(f"{path}/*.so"):
56+
os.remove(fname)
57+
for dirname in glob.glob(f"{path}/_pyxbld*"):
58+
shutil.rmtree(dirname, ignore_errors=True)
59+
shutil.rmtree(f"{path}/__pycache__", ignore_errors=True)
60+
61+
62+
def remove_mujoco_build_and_lock():
63+
# Removes previously compiled mujoco_py files.
64+
remove_mujoco_build()
65+
path = os.path.join(os.path.dirname(__file__), "..", "generated")
66+
fname = f"{path}/mujocopy-buildlock.lock"
67+
if os.path.exists(fname):
68+
os.remove(fname)
69+
70+
4571
def test_nested():
4672
model = load_model_from_xml(BASIC_MODEL_XML)
4773
model.vis.global_.fovy
@@ -150,6 +176,7 @@ def udd_callback(sim):
150176
with pytest.raises(AssertionError):
151177
sim.step()
152178

179+
153180
def test_data_attribute_getters():
154181
model = load_model_from_xml(BASIC_MODEL_XML)
155182
sim = MjSim(model)
@@ -657,14 +684,15 @@ def test_high_res():
657684

658685
@pytest.mark.skipif(sys.platform.startswith("win"), reason="This test fails on windows.")
659686
def test_multiprocess():
660-
'''
687+
"""
661688
Tests for importing mujoco_py from multiple processes.
662-
'''
689+
"""
690+
remove_mujoco_build_and_lock()
663691
ctx = get_context('spawn')
664692
processes = []
665693
times = 3
666694
queue = ctx.Queue()
667-
for idx in range(3):
695+
for idx in range(5):
668696
processes.append(ctx.Process(target=import_process, args=(queue, )))
669697
for p in processes:
670698
p.start()
@@ -674,6 +702,32 @@ def test_multiprocess():
674702
assert queue.get(), "One of processes failed."
675703

676704

705+
@pytest.mark.skipif(sys.platform.startswith("win"), reason="This test fails on windows.")
706+
def test_multiprocess_with_killing():
707+
"""
708+
Kills a process in a middle of compilation and verifies that
709+
other processes can resume compilation.
710+
"""
711+
remove_mujoco_build_and_lock()
712+
ctx = get_context('spawn')
713+
processes = []
714+
times = 3
715+
queue = ctx.Queue()
716+
for idx in range(times):
717+
processes.append(ctx.Process(target=import_process, args=(queue, )))
718+
processes[0].start()
719+
# We wait 20s so the compilation already
720+
# has started. Then we kill the process.
721+
time.sleep(20)
722+
for p in processes[1:]:
723+
p.start()
724+
processes[0].terminate()
725+
for p in processes[1:]:
726+
p.join()
727+
for _ in range(times - 1):
728+
assert queue.get(), "One of processes failed."
729+
730+
677731
def import_process(queue):
678732
try:
679733
from mujoco_py import builder

mujoco_py/version.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
__all__ = ['__version__', 'get_version']
22

3-
version_info = (2, 0, 2, 6)
3+
version_info = (2, 0, 2, 7)
44
# format:
55
# ('mujoco_major', 'mujoco_minor', 'mujoco_py_major', 'mujoco_py_minor')
66

77

88
def get_version():
9-
"Returns the version as a human-format string."
9+
""" Returns the version as a human-format string. """
1010
return '%d.%d.%d.%d' % version_info
1111

1212

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ requires = [
66
"Cython>=0.27.2",
77
"imageio>=2.1.2",
88
"cffi>=1.10",
9-
"lockfile>=0.12.2"
9+
"fasteners~=0.15"
1010
]

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ numpy>=1.11
44
Cython>=0.27.2
55
imageio>=2.1.2
66
cffi>=1.10
7-
lockfile>=0.12.2
7+
fasteners~=0.15

0 commit comments

Comments
 (0)