Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document missing Lock::Async methods #2842

Merged
merged 1 commit into from
Jun 9, 2019
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 110 additions & 32 deletions doc/Type/Lock/Async.pod6
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exclusion mechanism, C<Lock::Async> works with the high-level concurrency
features of Perl 6. The C<lock> method returns a C<Promise>, which will
be kept when the lock is available. This C<Promise> can be used with
non-blocking C<await>. This means that a thread from the thread pool need
not be consumed while waiting for the C<Async::Lock> to be available,
not be consumed while waiting for the C<Lock::Async> to be available,
and the code trying to obtain the lock will be resumed once it is available.

The result is that it's quite possible to have many thousands of outstanding
Expand All @@ -37,6 +37,51 @@ and L<Supply|/type/Supply>.

=head1 Methods

=head2 method lock

Defined as:

method lock(Lock::Async:D: --> Promise:D)

Returns a L<Promise|/type/Promise> that will be kept when the lock is
available. In the case that the lock is already available, an already
kept C<Promise> will be returned. Use C<await> to wait for the lock to
be available in a non-blocking manner.

my $l = Lock::Async.new;
await $l.lock;

Prefer to use L<protect|/type/Lock/Async#method_protect> instead of
explicit calls to C<lock> and C<unlock>.

=head2 method unlock

Defined as:

method unlock(Lock::Async:D: --> Nil)

Releases the lock. If there are any outstanding C<lock> C<Promise>s,
the one at the head of the queue will then be kept, and potentially
code scheduled on the thread pool (so the cost of calling C<unlock>
is limited to the work needed to schedule another piece of code that
wants to obtain the lock, but not to execute that code).

my $l = Lock::Async.new;
await $l.lock;
$l.unlock;

Prefer to use L<protect|/type/Lock/Async#method_protect> instead of
explicit calls to C<lock> and C<unlock>. However, if wishing to use
the methods separately, it is wise to use a C<LEAVE> block to ensure
that C<unlock> is reliably called. Failing to C<unlock> will mean that
nobody can ever C<lock> this particular C<Lock::Async> instance again.

my $l = Lock::Async.new;
{
await $l.lock;
LEAVE $l.unlock;
}

=head2 method protect

Defined as:
Expand Down Expand Up @@ -79,50 +124,83 @@ thus they don't actually protect the code we want to protect.
}
}

=head2 method lock
=head2 method protect-or-queue-on-recursion

Defined as:

method lock(Lock::Async:D: --> Promise:D)
method protect-or-queue-on-recursion(Lock::Async:D: &code)

When calling L<protect|/type/Lock/Async#method_protect> on a C<Lock::Async>
instance that is already locked, the method is forced to block until the lock
gets unlocked. C<protect-or-queue-on-recursion> avoids this issue by either
behaving the same as L<protect|/type/Lock/Async#method_protect> if the lock is
unlocked or the lock was locked by something outside the caller chain,
returning C<Nil>, or queueing the call to C<&code> and returning a C<Promise>
if the lock had already been locked at another point in the caller chain.

my Lock::Async $lock .= new;
my Int $count = 0;

# The lock is unlocked, so the code runs instantly.
$lock.protect-or-queue-on-recursion({
$count++
});

# Here, we have caller recursion. The outer call only returns a Promise
# because the inner one does. If we try to await the inner call's Promise
# from the outer call, the two calls will block forever since the inner
# caller's Promise return value is just the outer's with a then block.
$lock.protect-or-queue-on-recursion({
$lock.protect-or-queue-on-recursion({
$count++
}).then({
$count++
})
});

# Here, the lock is locked, but not by anything else on the caller chain.
# This behaves just like calling protect would in this scenario.
for 0..^2 {
$lock.protect-or-queue-on-recursion({
$count++;
});
}

Returns a L<Promise|/type/Promise> that will be kept when the lock is
available. In the case that the lock is already available, an already
kept C<Promise> will be returned. Use C<await> to wait for the lock to
be available in a non-blocking manner.
say $count; # OUTPUT: 5

my $l = Lock::Async.new;
await $l.lock;
=head2 method with-lock-hidden-from-recursion-check

Prefer to use L<protect|/type/Lock/Async#method_protect> instead of
explicit calls to C<lock> and C<unlock>.
Defined as:

=head2 method unlock
method with-lock-hidden-from-recursion-check(&code)

Defined as:
Temporarily resets the Lock::Async recursion list so that it no longer includes
the lock this method is called on and runs the given C<&code> immediately if
the call to the method occurred in a caller chain where
L<protect-or-queue-on-recursion|/type/Lock/Async/#method_protect-or-queue-on-recursion>
has already been called and the lock has been placed on the recursion list.

method unlock(Lock::Async:D: --> Nil)
my Lock::Async $lock .= new;
my Int $count = 0;

Releases the lock. If there are any outstanding C<lock> C<Promise>s,
the one at the head of the queue will then be kept, and potentially
code scheduled on the thread pool (so the cost of calling C<unlock>
is limited to the work needed to schedule another piece of code that
wants to obtain the lock, but not to execute that code).
$lock.protect-or-queue-on-recursion({
my Int $count = 0;

my $l = Lock::Async.new;
await $l.lock;
$l.unlock;
# Runs instantly.
$lock.with-lock-hidden-from-recursion-check({
$count++;
});

Prefer to use L<protect|/type/Lock/Async#method_protect> instead of
explicit calls to C<lock> and C<unlock>. However, if wishing to use
the methods separately, it is wise to use a C<LEAVE> block to ensure
that C<unlock> is reliably called. Failing to C<unlock> will mean that
nobody can ever C<lock> this particular C<Lock::Async> instance again.
# Runs after the outer caller's protect-or-queue-on-recursion call has
# finished running.
$lock.protect-or-queue-on-recursion({
$count++;
}).then({
say $count; # OUTPUT: 2
});

my $l = Lock::Async.new;
{
await $l.lock;
LEAVE $l.unlock;
}
say $count; # OUTPUT: 1
});

=end pod

Expand Down