Skip to content

Commit ebfa81b

Browse files
authored
Fix issue with readerwritelock usage in basilisp (#722) (#731)
Hi, could you please review patch to fix what it seems to be a potential issue with how readwrite locks are used in basilisp. It addresses #722. This was discovered while testing of the upcoming nrepl-server described in #723. The nrepl-server tests which are using threads were failing due to trying to release an rlock which has been already released, and is fixed as described in #722 by using `with self._lock.gen_rlock()` instead of a single instance of `with self._rlock()`. The error was ``` File "/home/runner/work/basilisp/basilisp/.tox/py39/lib/python3.9/site-packages/basilisp/lang/runtime.py", line 706, in find return v File "/home/runner/work/basilisp/basilisp/.tox/py39/lib/python3.9/site-packages/readerwriterlock/rwlock.py", line 49, in __exit__ self.release() File "/home/runner/work/basilisp/basilisp/.tox/py39/lib/python3.9/site-packages/readerwriterlock/rwlock.py", line 344, in release if not self.v_locked: raise RELEASE_ERR_CLS(RELEASE_ERR_MSG) RuntimeError: release unlocked lock ``` Thanks Co-authored-by: ikappaki <[email protected]>
1 parent 220065d commit ebfa81b

File tree

4 files changed

+40
-48
lines changed

4 files changed

+40
-48
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
* Fix `(is (= exp act))` should only evaluate its args once on failure (#712).
1818
* Fix issue with `with` failing with a traceback error when an exception is thrown (#714).
1919
* Fix issue with `sort-*` family of funtions returning an error on an empty seq (#716).
20-
* Fix issue with `intern` failing when used (#725).
20+
* Fix issue with `intern` failing when used (#725).
2121
* Fix issue with `ns` not being available after `in-ns` on the REPL (#718).
2222
* Fixed issue with import modules aliasing using ns eval (#719).
2323
* Fix issue with `ns-resolve` throwing an error on macros (#720).
24+
* Fix issue with py module `readerwritelock` locks handling (#722).
2425

2526
## [v0.1.0a2]
2627
### Added

src/basilisp/lang/atom.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
class Atom(RefBase[T], Generic[T]):
13-
__slots__ = ("_meta", "_state", "_rlock", "_wlock", "_watches", "_validator")
13+
__slots__ = ("_meta", "_state", "_lock", "_watches", "_validator")
1414

1515
def __init__(
1616
self,
@@ -20,17 +20,15 @@ def __init__(
2020
) -> None:
2121
self._meta: Optional[IPersistentMap] = meta
2222
self._state = state
23-
lock = RWLockFair()
24-
self._rlock = lock.gen_rlock()
25-
self._wlock = lock.gen_wlock()
23+
self._lock = RWLockFair()
2624
self._watches = PersistentMap.empty()
2725
self._validator = validator
2826

2927
if validator is not None:
3028
self._validate(state)
3129

3230
def _compare_and_set(self, old: T, new: T) -> bool:
33-
with self._wlock:
31+
with self._lock.gen_wlock():
3432
if self._state != old:
3533
return False
3634
self._state = new
@@ -48,7 +46,7 @@ def compare_and_set(self, old: T, new: T) -> bool:
4846

4947
def deref(self) -> T:
5048
"""Return the state stored within the Atom."""
51-
with self._rlock:
49+
with self._lock.gen_rlock():
5250
return self._state
5351

5452
def reset(self, v: T) -> T:

src/basilisp/lang/reference.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Any, Callable, Optional, TypeVar
22

3-
from readerwriterlock.rwlock import Lockable
3+
from readerwriterlock.rwlock import RWLockable
44

55
from basilisp.lang import keyword as kw
66
from basilisp.lang import map as lmap
@@ -33,24 +33,23 @@ class ReferenceBase(IReference):
3333
`basilisp.lang.runtime.Namespace` objects are the only objects which are
3434
`IReference` objects without also being `IRef` objects.
3535
36-
Implementers must have the `_rlock`, `_wlock`, and `_meta` properties defined."""
36+
Implementers must have the `_lock` and `_meta` properties defined."""
3737

38-
_rlock: Lockable
39-
_wlock: Lockable
38+
_lock: RWLockable
4039
_meta: Optional[IPersistentMap]
4140

4241
@property
4342
def meta(self) -> Optional[IPersistentMap]:
44-
with self._rlock:
43+
with self._lock.gen_rlock():
4544
return self._meta
4645

4746
def alter_meta(self, f: AlterMeta, *args) -> Optional[IPersistentMap]:
48-
with self._wlock:
47+
with self._lock.gen_wlock():
4948
self._meta = f(self._meta, *args)
5049
return self._meta
5150

5251
def reset_meta(self, meta: Optional[IPersistentMap]) -> Optional[IPersistentMap]:
53-
with self._wlock:
52+
with self._lock.gen_wlock():
5453
self._meta = meta
5554
return meta
5655

@@ -72,7 +71,7 @@ class RefBase(IRef[T], ReferenceBase):
7271
_watches: IPersistentMap
7372

7473
def add_watch(self, k: RefWatchKey, wf: RefWatcher) -> "RefBase[T]":
75-
with self._wlock:
74+
with self._lock.gen_wlock():
7675
self._watches = self._watches.assoc(k, wf)
7776
return self
7877

@@ -81,7 +80,7 @@ def _notify_watches(self, old: Any, new: Any):
8180
wf(k, self, old, new)
8281

8382
def remove_watch(self, k: RefWatchKey) -> "RefBase[T]":
84-
with self._wlock:
83+
with self._lock.gen_wlock():
8584
self._watches = self._watches.dissoc(k)
8685
return self
8786

src/basilisp/lang/runtime.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,7 @@ class Var(RefBase):
228228
"_is_bound",
229229
"_tl",
230230
"_meta",
231-
"_rlock",
232-
"_wlock",
231+
"_lock",
233232
"_watches",
234233
"_validator",
235234
)
@@ -248,9 +247,7 @@ def __init__(
248247
self._is_bound = False
249248
self._tl = None
250249
self._meta = meta
251-
lock = RWLockFair()
252-
self._rlock = lock.gen_rlock()
253-
self._wlock = lock.gen_wlock()
250+
self._lock = RWLockFair()
254251
self._watches = lmap.PersistentMap.empty()
255252
self._validator = None
256253

@@ -281,7 +278,7 @@ def dynamic(self) -> bool:
281278
return self._dynamic
282279

283280
def set_dynamic(self, dynamic: bool) -> None:
284-
with self._wlock:
281+
with self._lock.gen_wlock():
285282
if dynamic == self._dynamic:
286283
return
287284

@@ -309,18 +306,18 @@ def _set_root(self, newval) -> None:
309306

310307
@property
311308
def root(self):
312-
with self._rlock:
309+
with self._lock.gen_rlock():
313310
return self._root
314311

315312
def bind_root(self, val) -> None:
316313
"""Atomically update the root binding of this Var to val."""
317-
with self._wlock:
314+
with self._lock.gen_wlock():
318315
self._set_root(val)
319316

320317
def alter_root(self, f, *args) -> None:
321318
"""Atomically alter the root binding of this Var to the result of calling
322319
f with the existing root value and any additional arguments."""
323-
with self._wlock:
320+
with self._lock.gen_wlock():
324321
self._set_root(f(self._root, *args))
325322

326323
def push_bindings(self, val):
@@ -347,7 +344,7 @@ def value(self):
347344
348345
For non-dynamic Vars, this will just be the root. For dynamic Vars, this will
349346
be any thread-local binding if one is defined. Otherwise, the root value."""
350-
with self._rlock:
347+
with self._lock.gen_rlock():
351348
if self._dynamic:
352349
assert self._tl is not None
353350
if len(self._tl.bindings) > 0:
@@ -359,7 +356,7 @@ def set_value(self, v) -> None:
359356
360357
If the Var is not dynamic, this is equivalent to binding the root value. If the
361358
Var is dynamic, this will set the thread-local bindings for the Var."""
362-
with self._wlock:
359+
with self._lock.gen_wlock():
363360
if self._dynamic:
364361
assert self._tl is not None
365362
self._validate(v)
@@ -551,8 +548,7 @@ class Namespace(ReferenceBase):
551548
"_name",
552549
"_module",
553550
"_meta",
554-
"_rlock",
555-
"_wlock",
551+
"_lock",
556552
"_interns",
557553
"_refers",
558554
"_aliases",
@@ -567,9 +563,7 @@ def __init__(
567563
self._module = Maybe(module).or_else(lambda: _new_module(name.as_python_sym()))
568564

569565
self._meta: Optional[IPersistentMap] = None
570-
lock = RWLockFair()
571-
self._rlock = lock.gen_rlock()
572-
self._wlock = lock.gen_wlock()
566+
self._lock = RWLockFair()
573567

574568
self._aliases: NamespaceMap = lmap.PersistentMap.empty()
575569
self._imports: ModuleMap = lmap.map(
@@ -605,36 +599,36 @@ def module(self, m: BasilispModule):
605599
def aliases(self) -> NamespaceMap:
606600
"""A mapping between a symbolic alias and another Namespace. The
607601
fully qualified name of a namespace is also an alias for itself."""
608-
with self._rlock:
602+
with self._lock.gen_rlock():
609603
return self._aliases
610604

611605
@property
612606
def imports(self) -> ModuleMap:
613607
"""A mapping of names to Python modules imported into the current
614608
namespace."""
615-
with self._rlock:
609+
with self._lock.gen_rlock():
616610
return self._imports
617611

618612
@property
619613
def import_aliases(self) -> AliasMap:
620614
"""A mapping of a symbolic alias and a Python module name."""
621-
with self._rlock:
615+
with self._lock.gen_rlock():
622616
return self._import_aliases
623617

624618
@property
625619
def interns(self) -> VarMap:
626620
"""A mapping between a symbolic name and a Var. The Var may point to
627621
code, data, or nothing, if it is unbound. Vars in `interns` are
628622
interned in _this_ namespace."""
629-
with self._rlock:
623+
with self._lock.gen_rlock():
630624
return self._interns
631625

632626
@property
633627
def refers(self) -> VarMap:
634628
"""A mapping between a symbolic name and a Var. Vars in refers are
635629
interned in another namespace and are only referred to without an
636630
alias in this namespace."""
637-
with self._rlock:
631+
with self._lock.gen_rlock():
638632
return self._refers
639633

640634
def __repr__(self):
@@ -665,41 +659,41 @@ def require(self, ns_name: str, *aliases: sym.Symbol) -> BasilispModule:
665659

666660
def add_alias(self, namespace: "Namespace", *aliases: sym.Symbol) -> None:
667661
"""Add Symbol aliases for the given Namespace."""
668-
with self._wlock:
662+
with self._lock.gen_wlock():
669663
new_m = self._aliases
670664
for alias in aliases:
671665
new_m = new_m.assoc(alias, namespace)
672666
self._aliases = new_m
673667

674668
def get_alias(self, alias: sym.Symbol) -> "Optional[Namespace]":
675669
"""Get the Namespace aliased by Symbol or None if it does not exist."""
676-
with self._rlock:
670+
with self._lock.gen_rlock():
677671
return self._aliases.val_at(alias, None)
678672

679673
def remove_alias(self, alias: sym.Symbol) -> None:
680674
"""Remove the Namespace aliased by Symbol. Return None."""
681-
with self._wlock:
675+
with self._lock.gen_wlock():
682676
self._aliases = self._aliases.dissoc(alias)
683677

684678
def intern(self, sym: sym.Symbol, var: Var, force: bool = False) -> Var:
685679
"""Intern the Var given in this namespace mapped by the given Symbol.
686680
If the Symbol already maps to a Var, this method _will not overwrite_
687681
the existing Var mapping unless the force keyword argument is given
688682
and is True."""
689-
with self._wlock:
683+
with self._lock.gen_wlock():
690684
old_var = self._interns.val_at(sym, None)
691685
if old_var is None or force:
692686
self._interns = self._interns.assoc(sym, var)
693687
return self._interns.val_at(sym)
694688

695689
def unmap(self, sym: sym.Symbol) -> None:
696-
with self._wlock:
690+
with self._lock.gen_wlock():
697691
self._interns = self._interns.dissoc(sym)
698692

699693
def find(self, sym: sym.Symbol) -> Optional[Var]:
700694
"""Find Vars mapped by the given Symbol input or None if no Vars are
701695
mapped by that Symbol."""
702-
with self._rlock:
696+
with self._lock.gen_rlock():
703697
v = self._interns.val_at(sym, None)
704698
if v is None:
705699
return self._refers.val_at(sym, None)
@@ -708,7 +702,7 @@ def find(self, sym: sym.Symbol) -> Optional[Var]:
708702
def add_import(self, sym: sym.Symbol, module: Module, *aliases: sym.Symbol) -> None:
709703
"""Add the Symbol as an imported Symbol in this Namespace. If aliases are given,
710704
the aliases will be applied to the"""
711-
with self._wlock:
705+
with self._lock.gen_wlock():
712706
self._imports = self._imports.assoc(sym, module)
713707
if aliases:
714708
m = self._import_aliases
@@ -722,7 +716,7 @@ def get_import(self, sym: sym.Symbol) -> Optional[BasilispModule]:
722716
723717
First try to resolve a module directly with the given name. If no module
724718
can be resolved, attempt to resolve the module using import aliases."""
725-
with self._rlock:
719+
with self._lock.gen_rlock():
726720
mod = self._imports.val_at(sym, None)
727721
if mod is None:
728722
alias = self._import_aliases.get(sym, None)
@@ -734,17 +728,17 @@ def get_import(self, sym: sym.Symbol) -> Optional[BasilispModule]:
734728
def add_refer(self, sym: sym.Symbol, var: Var) -> None:
735729
"""Refer var in this namespace under the name sym."""
736730
if not var.is_private:
737-
with self._wlock:
731+
with self._lock.gen_wlock():
738732
self._refers = self._refers.assoc(sym, var)
739733

740734
def get_refer(self, sym: sym.Symbol) -> Optional[Var]:
741735
"""Get the Var referred by Symbol or None if it does not exist."""
742-
with self._rlock:
736+
with self._lock.gen_rlock():
743737
return self._refers.val_at(sym, None)
744738

745739
def refer_all(self, other_ns: "Namespace") -> None:
746740
"""Refer all the Vars in the other namespace."""
747-
with self._wlock:
741+
with self._lock.gen_wlock():
748742
final_refers = self._refers
749743
for s, var in other_ns.interns.items():
750744
if not var.is_private:

0 commit comments

Comments
 (0)