'lock levels' are deprecated, now a noop (#20539)
* 'lock levels' are deprecated, now a noop * fixes tests
This commit is contained in:
parent
7587371554
commit
5602183234
57 changed files with 121 additions and 372 deletions
|
|
@ -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
|
||||
=======================================
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue