Skip to content

Commit c557b5f

Browse files
committed
Document missing Lock::Async methods
This documents Lock::Async.protect-or-queue-on-recursion and Lock::Async.with-lock-hidden-from-recursion-check. Fixes #2785
1 parent b394e9d commit c557b5f

File tree

1 file changed

+110
-32
lines changed

1 file changed

+110
-32
lines changed

doc/Type/Lock/Async.pod6

Lines changed: 110 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ exclusion mechanism, C<Lock::Async> works with the high-level concurrency
1515
features of Perl 6. The C<lock> method returns a C<Promise>, which will
1616
be kept when the lock is available. This C<Promise> can be used with
1717
non-blocking C<await>. This means that a thread from the thread pool need
18-
not be consumed while waiting for the C<Async::Lock> to be available,
18+
not be consumed while waiting for the C<Lock::Async> to be available,
1919
and the code trying to obtain the lock will be resumed once it is available.
2020
2121
The result is that it's quite possible to have many thousands of outstanding
@@ -37,6 +37,51 @@ and L<Supply|/type/Supply>.
3737
3838
=head1 Methods
3939
40+
=head2 method lock
41+
42+
Defined as:
43+
44+
method lock(Lock::Async:D: --> Promise:D)
45+
46+
Returns a L<Promise|/type/Promise> that will be kept when the lock is
47+
available. In the case that the lock is already available, an already
48+
kept C<Promise> will be returned. Use C<await> to wait for the lock to
49+
be available in a non-blocking manner.
50+
51+
my $l = Lock::Async.new;
52+
await $l.lock;
53+
54+
Prefer to use L<protect|/type/Lock/Async#method_protect> instead of
55+
explicit calls to C<lock> and C<unlock>.
56+
57+
=head2 method unlock
58+
59+
Defined as:
60+
61+
method unlock(Lock::Async:D: --> Nil)
62+
63+
Releases the lock. If there are any outstanding C<lock> C<Promise>s,
64+
the one at the head of the queue will then be kept, and potentially
65+
code scheduled on the thread pool (so the cost of calling C<unlock>
66+
is limited to the work needed to schedule another piece of code that
67+
wants to obtain the lock, but not to execute that code).
68+
69+
my $l = Lock::Async.new;
70+
await $l.lock;
71+
$l.unlock;
72+
73+
Prefer to use L<protect|/type/Lock/Async#method_protect> instead of
74+
explicit calls to C<lock> and C<unlock>. However, if wishing to use
75+
the methods separately, it is wise to use a C<LEAVE> block to ensure
76+
that C<unlock> is reliably called. Failing to C<unlock> will mean that
77+
nobody can ever C<lock> this particular C<Lock::Async> instance again.
78+
79+
my $l = Lock::Async.new;
80+
{
81+
await $l.lock;
82+
LEAVE $l.unlock;
83+
}
84+
4085
=head2 method protect
4186
4287
Defined as:
@@ -79,50 +124,83 @@ thus they don't actually protect the code we want to protect.
79124
}
80125
}
81126
82-
=head2 method lock
127+
=head2 method protect-or-queue-on-recursion
83128
84129
Defined as:
85130
86-
method lock(Lock::Async:D: --> Promise:D)
131+
method protect-or-queue-on-recursion(Lock::Async:D: &code)
132+
133+
When calling L<protect|/type/Lock/Async#method_protect> on a C<Lock::Async>
134+
instance that is already locked, the method is forced to block until the lock
135+
gets unlocked. C<protect-or-queue-on-recursion> avoids this issue by either
136+
behaving the same as L<protect|/type/Lock/Async#method_protect> if the lock is
137+
unlocked or the lock was locked by something outside the caller chain,
138+
returning C<Nil>, or queueing the call to C<&code> and returning a C<Promise>
139+
if the lock had already been locked at another point in the caller chain.
140+
141+
my Lock::Async $lock .= new;
142+
my Int $count = 0;
143+
144+
# The lock is unlocked, so the code runs instantly.
145+
$lock.protect-or-queue-on-recursion({
146+
$count++
147+
});
148+
149+
# Here, we have caller recursion. The outer call only returns a Promise
150+
# because the inner one does. If we try to await the inner call's Promise
151+
# from the outer call, the two calls will block forever since the inner
152+
# caller's Promise return value is just the outer's with a then block.
153+
$lock.protect-or-queue-on-recursion({
154+
$lock.protect-or-queue-on-recursion({
155+
$count++
156+
}).then({
157+
$count++
158+
})
159+
});
160+
161+
# Here, the lock is locked, but not by anything else on the caller chain.
162+
# This behaves just like calling protect would in this scenario.
163+
for 0..^2 {
164+
$lock.protect-or-queue-on-recursion({
165+
$count++;
166+
});
167+
}
87168
88-
Returns a L<Promise|/type/Promise> that will be kept when the lock is
89-
available. In the case that the lock is already available, an already
90-
kept C<Promise> will be returned. Use C<await> to wait for the lock to
91-
be available in a non-blocking manner.
169+
say $count; # OUTPUT: 5
92170
93-
my $l = Lock::Async.new;
94-
await $l.lock;
171+
=head2 method with-lock-hidden-from-recursion-check
95172
96-
Prefer to use L<protect|/type/Lock/Async#method_protect> instead of
97-
explicit calls to C<lock> and C<unlock>.
173+
Defined as:
98174
99-
=head2 method unlock
175+
method with-lock-hidden-from-recursion-check(&code)
100176
101-
Defined as:
177+
Temporarily resets the Lock::Async recursion list so that it no longer includes
178+
the lock this method is called on and runs the given C<&code> immediately if
179+
the call to the method occurred in a caller chain where
180+
L<protect-or-queue-on-recursion|/type/Lock/Async/#method_protect-or-queue-on-recursion>
181+
has already been called and the lock has been placed on the recursion list.
102182
103-
method unlock(Lock::Async:D: --> Nil)
183+
my Lock::Async $lock .= new;
184+
my Int $count = 0;
104185
105-
Releases the lock. If there are any outstanding C<lock> C<Promise>s,
106-
the one at the head of the queue will then be kept, and potentially
107-
code scheduled on the thread pool (so the cost of calling C<unlock>
108-
is limited to the work needed to schedule another piece of code that
109-
wants to obtain the lock, but not to execute that code).
186+
$lock.protect-or-queue-on-recursion({
187+
my Int $count = 0;
110188
111-
my $l = Lock::Async.new;
112-
await $l.lock;
113-
$l.unlock;
189+
# Runs instantly.
190+
$lock.with-lock-hidden-from-recursion-check({
191+
$count++;
192+
});
114193
115-
Prefer to use L<protect|/type/Lock/Async#method_protect> instead of
116-
explicit calls to C<lock> and C<unlock>. However, if wishing to use
117-
the methods separately, it is wise to use a C<LEAVE> block to ensure
118-
that C<unlock> is reliably called. Failing to C<unlock> will mean that
119-
nobody can ever C<lock> this particular C<Lock::Async> instance again.
194+
# Runs after the outer caller's protect-or-queue-on-recursion call has
195+
# finished running.
196+
$lock.protect-or-queue-on-recursion({
197+
$count++;
198+
}).then({
199+
say $count; # OUTPUT: 2
200+
});
120201
121-
my $l = Lock::Async.new;
122-
{
123-
await $l.lock;
124-
LEAVE $l.unlock;
125-
}
202+
say $count; # OUTPUT: 1
203+
});
126204
127205
=end pod
128206

0 commit comments

Comments
 (0)