File Coverage

blib/lib/Future/Mutex.pm
Criterion Covered Total %
statement 31 31 100.0
branch 7 8 87.5
condition 2 2 100.0
subroutine 7 7 100.0
pod 3 3 100.0
total 50 51 98.0


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2016-2020 -- leonerd@leonerd.org.uk
5              
6             package Future::Mutex 0.52;
7              
8 1     1   713 use v5.14;
  1         4  
9 1     1   6 use warnings;
  1         2  
  1         96  
10              
11 1     1   8 use Future;
  1         2  
  1         483  
12              
13             =head1 NAME
14              
15             C - mutual exclusion lock around code that returns Ls
16              
17             =head1 SYNOPSIS
18              
19             =for highlighter language=perl
20              
21             use Future::Mutex;
22              
23             my $mutex = Future::Mutex->new;
24              
25             sub do_atomically
26             {
27             return $mutex->enter( sub {
28             ...
29             return $f;
30             });
31             }
32              
33             =head1 DESCRIPTION
34              
35             Most L-using code expects to run with some level of concurrency, using
36             future instances to represent still-pending operations that will complete at
37             some later time. There are occasions however, when this concurrency needs to
38             be restricted - some operations that, once started, must not be interrupted
39             until they are complete. Subsequent requests to perform the same operation
40             while one is still outstanding must therefore be queued to wait until the
41             first is finished. These situations call for a mutual-exclusion lock, or
42             "mutex".
43              
44             A C instance provides one basic operation, which will execute a
45             given block of code which returns a future, and itself returns a future to
46             represent that. The mutex can be in one of two states; either unlocked or
47             locked. While it is unlocked, requests to execute code are handled
48             immediately. Once a block of code is invoked, the mutex is now considered to
49             be locked, causing any subsequent requests to invoke code to be queued behind
50             the first one, until it completes. Once the initial code indicates completion
51             (by its returned future providing a result or failing), the next queued code
52             is invoked.
53              
54             An instance may also be a counting mutex if initialised with a count greater
55             than one. In this case, it can keep multiple blocks outstanding up to that
56             limit, with subsequent requests queued as before. This allows it to act as a
57             concurrency-bounding limit around some operation that can run concurrently,
58             but an application wishes to apply overall limits to stop it growing too much,
59             such as communications with external services or executing other programs.
60              
61             =cut
62              
63             =head1 CONSTRUCTOR
64              
65             =cut
66              
67             =head2 new
68              
69             $mutex = Future::Mutex->new( count => $n );
70              
71             Returns a new C instance. It is initially unlocked.
72              
73             Takes the following named arguments:
74              
75             =over 8
76              
77             =item count => INT
78              
79             Optional number to limit outstanding concurrency. Will default to 1 if not
80             supplied.
81              
82             =back
83              
84             =cut
85              
86             sub new
87             {
88 10     10 1 205885 my $class = shift;
89 10         25 my %params = @_;
90              
91             return bless {
92 10   100     95 avail => $params{count} // 1,
93             waitf => undef,
94             queue => [],
95             }, $class;
96             }
97              
98             =head1 METHODS
99              
100             =cut
101              
102             =head2 enter
103              
104             $f = $mutex->enter( \&code );
105              
106             Returns a new C that represents the eventual result of calling the
107             code. If the mutex is currently unlocked, the code will be invoked
108             immediately. If it is currently locked, the code will be queued waiting for
109             the next time it becomes unlocked.
110              
111             The code is invoked with no arguments, and is expected to return a C.
112             The eventual result of that future determines the result of the future that
113             C returned.
114              
115             =cut
116              
117             sub enter
118             {
119 17     17 1 95 my $self = shift;
120 17         29 my ( $code ) = @_;
121              
122 17         25 my $down_f;
123 17 100       50 if( $self->{avail} ) {
124 11         16 $self->{avail}--;
125 11         56 $down_f = Future->done;
126             }
127             else {
128 6 50       13 die "ARGH Need to clone an existing future\n" unless defined $self->{waitf};
129 6         8 push @{ $self->{queue} }, $down_f = $self->{waitf}->new;
  6         32  
130             }
131              
132             my $up = sub {
133 17 100   17   27 if( my $next_f = shift @{ $self->{queue} } ) {
  17         57  
134 6         38 $next_f->done;
135             }
136             else {
137 11         19 $self->{avail}++;
138 11         112 undef $self->{waitf};
139             }
140 17         70 };
141              
142 17         57 my $retf = $down_f->then( $code )->on_ready( $up );
143 17 100       51 $self->{waitf} or $self->{waitf} = $retf;
144 17         125 return $retf;
145             }
146              
147             =head2 available
148              
149             $avail = $mutex->available;
150              
151             Returns true if the mutex is currently unlocked, or false if it is locked.
152              
153             =cut
154              
155             sub available
156             {
157 12     12 1 1228 my $self = shift;
158 12         73 return $self->{avail};
159             }
160              
161             =head1 AUTHOR
162              
163             Paul Evans
164              
165             =cut
166              
167             0x55AA;