@@ -15,7 +15,7 @@ exclusion mechanism, C<Lock::Async> works with the high-level concurrency
15
15
features of Perl 6. The C < lock > method returns a C < Promise > , which will
16
16
be kept when the lock is available. This C < Promise > can be used with
17
17
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,
19
19
and the code trying to obtain the lock will be resumed once it is available.
20
20
21
21
The result is that it's quite possible to have many thousands of outstanding
@@ -37,6 +37,51 @@ and L<Supply|/type/Supply>.
37
37
38
38
= head1 Methods
39
39
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
+
40
85
= head2 method protect
41
86
42
87
Defined as:
@@ -79,50 +124,83 @@ thus they don't actually protect the code we want to protect.
79
124
}
80
125
}
81
126
82
- = head2 method lock
127
+ = head2 method protect-or-queue-on-recursion
83
128
84
129
Defined as:
85
130
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
+ }
87
168
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
92
170
93
- my $l = Lock::Async.new;
94
- await $l.lock;
171
+ = head2 method with-lock-hidden-from-recursion-check
95
172
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:
98
174
99
- = head2 method unlock
175
+ method with-lock-hidden-from-recursion-check(&code)
100
176
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.
102
182
103
- method unlock(Lock::Async:D: --> Nil)
183
+ my Lock::Async $lock .= new;
184
+ my Int $count = 0;
104
185
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;
110
188
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
+ });
114
193
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
+ });
120
201
121
- my $l = Lock::Async.new;
122
- {
123
- await $l.lock;
124
- LEAVE $l.unlock;
125
- }
202
+ say $count; # OUTPUT: 1
203
+ });
126
204
127
205
= end pod
128
206
0 commit comments