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   30499 use warnings;
  20         34  
  20         799  
4 20     20   98 use strict;
  20         28  
  20         508  
5              
6 20     20   5900 use Data::Validate::Type;
  20         98263  
  20         1093  
7 20     20   7552 use Data::Dumper;
  20         67582  
  20         1567  
8 20     20   132 use Carp;
  20         31  
  20         2006  
9              
10 20     20   10274 use IPC::Concurrency::DBI::Application::Instance;
  20         51  
  20         17423  
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.2.0
21              
22             =cut
23              
24             our $VERSION = '1.2.0';
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 112692 my ( $class, %args ) = @_;
103 18         46 my $database_handle = delete( $args{'database_handle'} );
104 18         36 my $name = delete( $args{'name'} );
105 18         36 my $application_id = delete( $args{'id'} );
106              
107             # Check parameters.
108 18 100       89 croak "Argument 'database_handle' is required to create a new IPC::Concurrency::DBI::Application object"
109             unless defined( $database_handle );
110 17 50       79 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     469 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         29 my ( $key, $value );
117 17 100       63 if ( defined( $name ) )
    100          
118             {
119 12         23 $key = 'name';
120 12         18 $value = $name;
121             }
122             elsif ( defined( $application_id ) )
123             {
124 4         7 $key = 'ipc_concurrency_application_id';
125 4         7 $value = $application_id;
126             }
127             else
128             {
129 1         20 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         248 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       8226 croak 'Cannot execute SQL: ' . $database_handle->errstr()
143             if defined( $database_handle->errstr() );
144 16 100       103 croak 'Application not found'
145             unless defined( $data );
146              
147             # Create the object.
148 13         53 my $self = bless(
149             {
150             database_handle => $database_handle,
151             data => $data,
152             },
153             $class,
154             );
155              
156 13         64 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 1950 my ( $self ) = @_;
176 5         18 my $database_handle = $self->get_database_handle();
177 5         16 my $maximum_instances = $self->get_maximum_instances();
178              
179 5         27 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       67736 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       31 return unless $rows_affected == 1;
196              
197 4         49 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 2263 my ( $self ) = @_;
216 5         19 my $database_handle = $self->get_database_handle();
217 5         13 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       1358 croak 'Cannot execute SQL: ' . $database_handle->errstr()
229             if defined( $database_handle->errstr() );
230 5 50       15 croak 'Application not found'
231             unless defined( $data );
232              
233 5         37 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 864 my ( $self ) = @_;
249              
250 13         50 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 20 my ( $self, $maximum_instances ) = @_;
266              
267             # Check parameters.
268 1 50       7 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         20 my $database_handle = $self->get_database_handle();
273 1         18 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       54665 croak 'Cannot execute SQL: ' . $database_handle->errstr()
284             if defined( $database_handle->errstr() );
285              
286 1         4 $self->{'data'}->{'maximum_instances'} = $maximum_instances;
287              
288 1         12 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 300 my ( $self ) = @_;
303              
304 1         8 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 329 my ( $self ) = @_;
319              
320 18         156 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 344 my ( $self ) = @_;
337              
338 18         52 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-2017 Guillaume Aubert.
389              
390             This code is free software; you can redistribute it and/or modify it under the
391             same terms as Perl 5 itself.
392              
393             This program is distributed in the hope that it will be useful, but WITHOUT ANY
394             WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
395             PARTICULAR PURPOSE. See the LICENSE file for more details.
396              
397             =cut
398              
399             1;