'lock levels' are deprecated, now a noop (#20539)

* 'lock levels' are deprecated, now a noop

* fixes tests
This commit is contained in:
ringabout 2022-10-11 15:17:09 +08:00 • committed by GitHub
commit 5602183234
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
57 changed files with 121 additions and 372 deletions

View file

@ -1817,105 +1817,6 @@ restrictions / changes:
yet performed for ordinary slices outside of a `parallel` section.
Lock levels
===========
Lock levels are used to enforce a global locking order in order to detect
potential deadlocks during semantic analysis. A lock level is an constant
integer in the range 0..1_000. Lock level 0 means that no lock is acquired at
all.
If a section of code holds a lock of level `M`, it can also acquire any
lock of level `N < M`. Another lock of level `M` cannot be acquired. Locks
of the same level can only be acquired *at the same time* within a
single `locks` section:
```nim
var a, b: TLock[2]
var x: TLock[1]
# invalid locking order: TLock[1] cannot be acquired before TLock[2]:
{.locks: [x].}:
{.locks: [a].}:
...
# valid locking order: TLock[2] acquired before TLock[1]:
{.locks: [a].}:
{.locks: [x].}:
...
# invalid locking order: TLock[2] acquired before TLock[2]:
{.locks: [a].}:
{.locks: [b].}:
...
# valid locking order, locks of the same level acquired at the same time:
{.locks: [a, b].}:
...
```
Here is how a typical multilock statement can be implemented in Nim. Note how
the runtime check is required to ensure a global ordering for two locks `a`
and `b` of the same lock level:
```nim
template multilock(a, b: ptr TLock; body: untyped) =
if cast[ByteAddress](a) < cast[ByteAddress](b):
pthread_mutex_lock(a)
pthread_mutex_lock(b)
else:
pthread_mutex_lock(b)
pthread_mutex_lock(a)
{.locks: [a, b].}:
try:
body
finally:
pthread_mutex_unlock(a)
pthread_mutex_unlock(b)
```
Whole routines can also be annotated with a `locks` pragma that takes a lock
level. This then means that the routine may acquire locks of up to this level.
This is essential so that procs can be called within a `locks` section:
```nim
proc p() {.locks: 3.} = discard
var a: TLock[4]
{.locks: [a].}:
# p's locklevel (3) is strictly less than a's (4) so the call is allowed:
p()
```
As usual, `locks` is an inferred effect and there is a subtype
relation: `proc () {.locks: N.}` is a subtype of `proc () {.locks: M.}`
iff (M <= N).
The `locks` pragma can also take the special value `"unknown"`. This
is useful in the context of dynamic method dispatching. In the following
example, the compiler can infer a lock level of 0 for the `base` case.
However, one of the overloaded methods calls a procvar which is
potentially locking. Thus, the lock level of calling `g.testMethod`
cannot be inferred statically, leading to compiler warnings. By using
`{.locks: "unknown".}`, the base method can be marked explicitly as
having unknown lock level as well:
```nim
type SomeBase* = ref object of RootObj
type SomeDerived* = ref object of SomeBase
memberProc*: proc ()
method testMethod(g: SomeBase) {.base, locks: "unknown".} = discard
method testMethod(g: SomeDerived) =
if g.memberProc != nil:
g.memberProc()
```
This feature may be removed in the future due to its practical difficulties.
Strict definitions and `out` parameters
=======================================