File Coverage

blib/lib/IPC/Concurrency/DBI/Application.pm
Criterion Covered Total %
statement 66 66 100.0
branch 18 26 69.2
condition 2 3 66.6
subroutine 14 14 100.0
pod 8 8 100.0
total 108 117 92.3


line stmt bran cond sub pod time code
1             package IPC::Concurrency::DBI::Application;
2              
3 20     20   31692 use warnings;
  20         41  
  20         765  
4 20     20   106 use strict;
  20         39  
  20         583  
5              
6 20     20   9572 use Data::Validate::Type;
  20         134443  
  20         1071  
7 20     20   11772 use Data::Dumper;
  20         3583735  
  20         1473  
8 20     20   142 use Carp;
  20         40  
  20         1176  
9              
10 20     20   13021 use IPC::Concurrency::DBI::Application::Instance;
  20         54  
  20         1172568  
11              
12              
13             =head1 NAME
14              
15             IPC::Concurrency::DBI::Application - Application identifier that represents the resource that will be limited.
16              
17              
18             =head1 VERSION
19              
20             Version 1.1.3
21              
22             =cut
23              
24             our $VERSION = '1.1.3';
25              
26              
27             =head1 SYNOPSIS
28              
29             This module allows controlling how many instances of a given program are allowed
30             to run in parallel. It does not manage forking or starting those instances.
31              
32             See the documentation of IPC::Concurrency::DBI for more information.
33              
34             # Configure the concurrency object.
35             use IPC::Concurrency::DBI;
36             my $concurrency_manager = IPC::Concurrency::DBI->new(
37             'database_handle' => $dbh,
38             'verbose' => 1,
39             );
40              
41             # Retrieve the application.
42             my $application = $concurrency_manager->get_application(
43             name => 'cron_script.pl',
44             );
45              
46             # Count how many instances are currently running.
47             my $instances_count = $application->get_instances_count();
48              
49             # NOT IMPLEMENTED YET: Get a list of what instances are currently running.
50             # my $instances = $application->get_instances_list()
51              
52             # Start a new instance of the application. If this returns undef, we've
53             # reached the limit.
54             my $instance = $concurrent_program->start_instance();
55             unless ( defined( $instance ) )
56             {
57             print "Too many instances of $0 are already running.\n";
58             exit;
59             }
60              
61             # [...] Do some work.
62              
63             # Now that the application is about to exit, flag the instance as completed.
64             # (note: this is implicit when $instance is destroyed).
65             $instance->finish();
66              
67              
68             =head1 METHODS
69              
70             =head2 new()
71              
72             Create a new IPC::Concurrency::DBI::Application object. This function should
73             not be called directly and its API could change, instead use
74             IPC::Concurrency::DBI->get_application().
75              
76             # Retrieve the application by name.
77             my $application = IPC::Concurrency::DBI::Application->new(
78             database_handle => $dbh,
79             name => 'cron_script.pl',
80             );
81             die 'Application not found'
82             unless defined( $application );
83              
84             # Retrieve the application by ID.
85             my $application = IPC::Concurrency::DBI::Application->new(
86             database_handle => $dbh,
87             id => 12345,
88             );
89             die 'Application not found'
90             unless defined( $application );
91              
92             'database handle': mandatory, a DBI object.
93              
94             'name': the name of the application to retrieve.
95              
96             'id': the internal ID of the application to retrieve.
97              
98             =cut
99              
100             sub new
101             {
102 18     18 1 4961245 my ( $class, %args ) = @_;
103 18         58 my $database_handle = delete( $args{'database_handle'} );
104 18         59 my $name = delete( $args{'name'} );
105 18         43 my $application_id = delete( $args{'id'} );
106              
107             # Check parameters.
108 18 100       135 croak "Argument 'database_handle' is required to create a new IPC::Concurrency::DBI::Application object"
109             unless defined( $database_handle );
110 17 50       112 croak "Argument 'database_handle' is not a DBI object"
111             if !Data::Validate::Type::is_instance( $database_handle, class => 'DBI::db' );
112 17 50 66     561 croak 'Cannot pass both a name and an application ID, please use only one'
113             if defined( $name ) && defined( $application_id );
114              
115             # Determine what key to use to retrieve the row.
116 17         61 my ( $key, $value );
117 17 100       82 if ( defined( $name ) )
    100          
118             {
119 12         27 $key = 'name';
120 12         36 $value = $name;
121             }
122             elsif ( defined( $application_id ) )
123             {
124 4         11 $key = 'ipc_concurrency_application_id';
125 4         8 $value = $application_id;
126             }
127             else
128             {
129 1         15 croak 'You need to specify either a name or an ID to retrieve an application';
130             }
131              
132             # Retrieve the row from the database.
133 16         258 my $data = $database_handle->selectrow_hashref(
134             qq|
135             SELECT *
136             FROM ipc_concurrency_applications
137             WHERE $key = ?
138             |,
139             {},
140             $value,
141             );
142 16 50       9823 croak 'Cannot execute SQL: ' . $database_handle->errstr()
143             if defined( $database_handle->errstr() );
144 16 100       122 croak 'Application not found'
145             unless defined( $data );
146              
147             # Create the object.
148 13         77 my $self = bless(
149             {
150             database_handle => $database_handle,
151             data => $data,
152             },
153             $class,
154             );
155              
156 13         97 return $self;
157             }
158              
159              
160             =head2 start_instance()
161              
162             Start a new instance of the current application.
163              
164             my $instance = $application->start_instance();
165             unless ( defined( $instance ) )
166             {
167             print "Too many instances of $0 are already running.\n";
168             exit;
169             }
170              
171             =cut
172              
173             sub start_instance
174             {
175 5     5 1 2259 my ( $self ) = @_;
176 5         21 my $database_handle = $self->get_database_handle();
177 5         21 my $maximum_instances = $self->get_maximum_instances();
178              
179 5         126 my $rows_affected = $database_handle->do(
180             q|
181             UPDATE ipc_concurrency_applications
182             SET current_instances = current_instances + 1, modified = ?
183             WHERE ipc_concurrency_application_id = ?
184             AND current_instances < maximum_instances
185             |,
186             {},
187             time(),
188             $self->get_id(),
189             );
190 5 50       123353 croak 'Cannot execute SQL: ' . $database_handle->errstr()
191             if defined( $database_handle->errstr() );
192              
193             # If no row was affected, we've reached the maximum number of instances or
194             # the application ID has vanished. Either way, we can't start the instance.
195 5 100       29 return unless $rows_affected == 1;
196              
197 4         53 return IPC::Concurrency::DBI::Application::Instance->new(
198             application => $self,
199             );
200             }
201              
202              
203             =head1 GETTERS / SETTERS
204              
205             =head2 get_instances_count()
206              
207             Retrieve the number of instances that currently running.
208              
209             my $instances_count = $application->get_instances_count();
210              
211             =cut
212              
213             sub get_instances_count
214             {
215 5     5 1 1147 my ( $self ) = @_;
216 5         21 my $database_handle = $self->get_database_handle();
217 5         20 my $maximum_instances = $self->get_maximum_instances();
218              
219 5         26 my $data = $database_handle->selectrow_hashref(
220             q|
221             SELECT current_instances
222             FROM ipc_concurrency_applications
223             WHERE ipc_concurrency_application_id = ?
224             |,
225             {},
226             $self->get_id(),
227             );
228 5 50       1716 croak 'Cannot execute SQL: ' . $database_handle->errstr()
229             if defined( $database_handle->errstr() );
230 5 50       17 croak 'Application not found'
231             unless defined( $data );
232              
233 5         48 return $data->{'current_instances'};
234             }
235              
236              
237             =head2 get_maximum_instances()
238              
239             Retrieve the maximum number of instances of the current application that are
240             allowed to run in parallel.
241              
242             my $maximum_instances = $application->get_maximum_instances();
243              
244             =cut
245              
246             sub get_maximum_instances
247             {
248 13     13 1 1467 my ( $self ) = @_;
249              
250 13         70 return $self->{'data'}->{'maximum_instances'};
251             }
252              
253              
254             =head2 set_maximum_instances()
255              
256             Change the maximum number of instances of the current application that are
257             allowed to run in parallel.
258              
259             $application->set_maximum_instances( 10 );
260              
261             =cut
262              
263             sub set_maximum_instances
264             {
265 1     1 1 31 my ( $self, $maximum_instances ) = @_;
266              
267             # Check parameters.
268 1 50       6 croak 'The maximum number of instances needs to be a strictly positive integer'
269             if !Data::Validate::Type::is_number( $maximum_instances, strictly_positive => 1 );
270              
271             # Update the application information.
272 1         28 my $database_handle = $self->get_database_handle();
273 1         36 my $rows_affected = $database_handle->do(
274             q|
275             UPDATE ipc_concurrency_applications
276             SET maximum_instances = ?
277             WHERE ipc_concurrency_application_id = ?
278             |,
279             {},
280             $maximum_instances,
281             $self->get_id(),
282             );
283 1 50       4324705 croak 'Cannot execute SQL: ' . $database_handle->errstr()
284             if defined( $database_handle->errstr() );
285              
286 1         9 $self->{'data'}->{'maximum_instances'} = $maximum_instances;
287              
288 1         63 return 1;
289             }
290              
291              
292             =head2 get_name()
293              
294             Returns the name of the current application.
295              
296             my $name = $application->get_name();
297              
298             =cut
299              
300             sub get_name
301             {
302 1     1 1 517 my ( $self ) = @_;
303              
304 1         9 return $self->{'data'}->{'name'};
305             }
306              
307              
308             =head2 get_id()
309              
310             Returns the internal ID of the current application.
311              
312             my $application_id = $self->get_id();
313              
314             =cut
315              
316             sub get_id
317             {
318 18     18 1 495 my ( $self ) = @_;
319              
320 18         193 return $self->{'data'}->{'ipc_concurrency_application_id'};
321             }
322              
323              
324             =head1 INTERNAL METHODS
325              
326             =head2 get_database_handle()
327              
328             Returns the database handle used for this object.
329              
330             my $database_handle = $concurrency_manager->get_database_handle();
331              
332             =cut
333              
334             sub get_database_handle
335             {
336 18     18 1 376 my ( $self ) = @_;
337              
338 18         80 return $self->{'database_handle'};
339             }
340              
341              
342             =head1 BUGS
343              
344             Please report any bugs or feature requests through the web interface at
345             L.
346             I will be notified, and then you'll automatically be notified of progress on
347             your bug as I make changes.
348              
349              
350             =head1 SUPPORT
351              
352             You can find documentation for this module with the perldoc command.
353              
354             perldoc IPC::Concurrency::DBI
355              
356              
357             You can also look for information at:
358              
359             =over 4
360              
361             =item * GitHub's request tracker
362              
363             L
364              
365             =item * AnnoCPAN: Annotated CPAN documentation
366              
367             L
368              
369             =item * CPAN Ratings
370              
371             L
372              
373             =item * MetaCPAN
374              
375             L
376              
377             =back
378              
379              
380             =head1 AUTHOR
381              
382             L,
383             C<< >>.
384              
385              
386             =head1 COPYRIGHT & LICENSE
387              
388             Copyright 2011-2014 Guillaume Aubert.
389              
390             This program is free software: you can redistribute it and/or modify it under
391             the terms of the GNU General Public License version 3 as published by the Free
392             Software Foundation.
393              
394             This program is distributed in the hope that it will be useful, but WITHOUT ANY
395             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
396             PARTICULAR PURPOSE. See the GNU General Public License for more details.
397              
398             You should have received a copy of the GNU General Public License along with
399             this program. If not, see http://www.gnu.org/licenses/
400              
401             =cut
402              
403             1;