File Coverage

lib/Mojo/IOLoop/ReadWriteProcess/Container.pm
Criterion Covered Total %
statement 89 91 97.8
branch 32 38 84.2
condition 13 18 72.2
subroutine 27 28 96.4
pod 7 8 87.5
total 168 183 91.8


line stmt bran cond sub pod time code
1             package Mojo::IOLoop::ReadWriteProcess::Container;
2              
3 13     13   10841 use Mojo::Base 'Mojo::EventEmitter';
  13         25  
  13         66  
4 13     13   2614 use Mojo::IOLoop::ReadWriteProcess::CGroup;
  13         27  
  13         460  
5 13     13   67 use Mojo::IOLoop::ReadWriteProcess;
  13         14  
  13         587  
6 13     13   5444 use Mojo::IOLoop::ReadWriteProcess::Namespace qw( CLONE_NEWPID CLONE_NEWNS );
  13         28  
  13         837  
7 13     13   104 use Mojo::IOLoop::ReadWriteProcess;
  13         26  
  13         426  
8 13     13   77 use Mojo::IOLoop::ReadWriteProcess::Session;
  13         15  
  13         439  
9 13     13   77 use Mojo::Collection 'c';
  13         24  
  13         504  
10 13     13   65 use Scalar::Util 'blessed';
  13         26  
  13         802  
11             our @EXPORT_OK = qw(container);
12 13     13   78 use Exporter 'import';
  13         25  
  13         312  
13              
14 13     13   65 use Carp 'croak';
  13         25  
  13         4318  
15             has 'name';
16             has 'group';
17              
18             # Roughly a container
19             has process => sub { Mojo::IOLoop::ReadWriteProcess->new };
20             has cgroups => sub {
21             c(Mojo::IOLoop::ReadWriteProcess::CGroup::v1->new(controller => 'pids'));
22             };
23             has namespace => sub { Mojo::IOLoop::ReadWriteProcess::Namespace->new };
24             has session => sub { Mojo::IOLoop::ReadWriteProcess::Session->singleton };
25             has pid_isolation => sub { 0 };
26             has unshare => undef;
27             has subreaper => 0;
28             has pre_migrate => 0;
29             has clean_cgroup => 0;
30              
31 13     13   102 use constant DEBUG => $ENV{MOJO_PROCESS_DEBUG};
  13         27  
  13         18518  
32              
33 76     76 0 32304 sub container { __PACKAGE__->new(@_) }
34              
35             sub new {
36 76     76 1 550 my $self = shift->SUPER::new(@_);
37 76 100 66     1579 $self->cgroups(c($self->cgroups))
38             unless blessed $self->cgroups && $self->cgroups->isa('Mojo::Collection');
39 76         3825 $self;
40             }
41              
42             sub migrate_process {
43 36     36 1 720 my $p = pop();
44 36     36   626 shift->cgroups->each(sub { shift->add_process($p) });
  36         4038  
45             }
46              
47             sub start {
48 76     76 1 793 my $self = shift;
49 76 100 66     291 croak
      100        
50             'You need either to pass a Mojo::IOLoop::ReadWriteProcess object or a callback'
51             unless (blessed $self->process
52             && $self->process->isa("Mojo::IOLoop::ReadWriteProcess"))
53             || ref $self->process eq 'CODE';
54              
55 65 100       1845 $self->process(Mojo::IOLoop::ReadWriteProcess->new($self->process))
56             unless blessed $self->process;
57              
58             $self->cgroups->map(
59             sub {
60 65 100 66 65   2196 return $_ if $_->name || $_->parent;
61 48 50       715 $_ = $_->name($self->group)->create if $self->group;
62 48 50       534 $_ = $_->child($self->name)->create if $self->name;
63 65 50 33     716 }) if defined $self->group || defined $self->name;
64              
65 65 100       1205 $self->process->subreaper(1) if $self->subreaper;
66              
67 65 100       1484 $self->unshare(CLONE_NEWPID | CLONE_NEWNS) if $self->pid_isolation;
68             $self->process->once(
69             start => sub {
70 30     30   3877 $self->migrate_process($self->process->pid);
71 65 100       457 }) unless $self->pre_migrate;
72              
73             $self->process->once(
74             stop => sub {
75             $self->cgroups->each(
76             sub {
77             $_[0]->processes->each(
78             sub {
79 55         10916 my $pid = shift;
80 55         982 my $p = Mojo::IOLoop::ReadWriteProcess->new(
81             process_id => $pid,
82             blocking_stop => 1
83             );
84 55         2016 $self->session->register($pid => $p);
85 55         2100 $p->stop();
86 55         2488 });
87 55 100       1165 $_[0]->remove() if $self->clean_cgroup;
88 55     55   4502 });
89 65         1481 });
90              
91 65     55   1639 $self->process->once(stop => sub { shift; $self->emit(stop => @_) });
  55         5512  
  55         280  
92 65     55   1286 $self->process->once(start => sub { shift; $self->emit(start => @_) });
  55         4618  
  55         739  
93              
94 65         1285 my $fn = $self->process->code();
95              
96 2     2   81 $self->process->code(sub { $self->migrate_process($$); $fn->(@_) })
  2         54  
97 65 100       763 if $self->pre_migrate;
98              
99             $self->process->code(
100             sub {
101 5 100   5   71 $self->migrate_process($$) if $self->pre_migrate;
102              
103 5 100 100     138 if ( $self->unshare & CLONE_NEWPID
    100          
104             && $self->namespace->unshare($self->unshare) == 0)
105             {
106              
107             # In such case, we have to spawn another process
108             my $init = Mojo::IOLoop::ReadWriteProcess->new(
109             set_pipes => 0,
110             internal_pipes => 1,
111             code => sub {
112 1 50       69 $_[0]->enable_subreaper if $self->subreaper;
113 1 50       46 $self->namespace->isolate() if $self->unshare & CLONE_NEWNS;
114 1         18 $fn->(@_);
115 1         83 });
116 1         55 $init->start()->wait_stop;
117              
118             #return $init->return_status if defined $init->return_status;
119 0         0 $init->_exit($init->exit_status);
120             }
121             elsif ($self->namespace->unshare($self->unshare) != 0) {
122 2         25 warn "Unshare failed";
123             }
124              
125 4         171 $fn->(@_);
126 65 100       1162 }) if defined $self->unshare;
127              
128 65         844 if (DEBUG) {
129             $self->process->diag("Starting container");
130             $self->process->diag("\tName: " . $self->name) if defined $self->name;
131             $self->process->diag("\tGroup: " . $self->group) if defined $self->group;
132 0     0   0 $self->cgroups->each(sub { $self->process->diag("CGroup: " . $_->_cgroup) }
133             );
134             }
135              
136 65         525 local ($@, $!);
137 65         197 eval { $self->process->start(); };
  65         239  
138 55 50       472 $self->emit(container_error => [$@, $!]) if $@;
139              
140 55         2267 $self;
141             }
142              
143 9     9 1 10827 sub stop { shift->emit('stop')->process->stop() }
144              
145 209     209 1 220078962 sub is_running { shift->process->is_running }
146              
147 45     45 1 3355 sub wait_stop { shift->process->wait_stop }
148              
149 10     10 1 1740 sub wait { shift->process->wait }
150              
151             =encoding utf-8
152              
153             =head1 NAME
154              
155             Mojo::IOLoop::ReadWriteProcess::Container - (kinda) Pure Perl containers.
156              
157             =head1 SYNOPSIS
158              
159             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
160             use Mojo::IOLoop::ReadWriteProcess qw(process);
161              
162             my $container = container(
163             pid_isolation => 1, # Best-effort, as depends on where you run it (you need CAP_SYS_ADMIN)
164             subreaper => 1,
165             group => "my_org",
166             name => "my_process",
167             process => process(
168             sub {
169             # Exec, fork ..
170             process(sub { warn "\o/"; sleep 42; })->start;
171             process(sub { warn "\o/"; sleep 42; })->start;
172             process(
173             sub {
174             process(
175             sub {
176             process(sub { warn "\o/"; sleep 42; })->start;
177             warn "\o/";
178             sleep 400;
179             warn "\o/";
180             })->start;
181             warn "Hey";
182             sleep 42;
183             warn "\o/";
184             })->start;
185             sleep 42;
186             }
187             )->separate_err(0));
188              
189             $container->start();
190             $container->is_running;
191             $container->stop;
192              
193             my @procs = $container->cgroups->first->processes;
194             $container->cgroups->first->pid->max(300);
195              
196             $container->process->on(stop => sub { print "Main container process stopped!" });
197              
198             =head1 DESCRIPTION
199              
200             L ties anonymous functions or a L object to differents
201             sets of L implementations.
202              
203             When the C attribute is set, it needs special permissions (CAP_SYS_ADMIN capabilities).
204             This module uses features that are only available on Linux, and requires cgroups and capability (CAP_SYS_ADMIN) for unshare syscalls to achieve pid isolation.
205              
206             =head1 EVENTS
207              
208             L inherits all events from L and can emit
209             the following new ones.
210              
211             =head2 start
212              
213             $container->on(start => sub {
214             my ($process) = @_;
215             ...
216             });
217              
218             Emitted when the container starts.
219              
220             =head2 stop
221              
222             $container->on(stop => sub {
223             my ($container) = @_;
224             ...
225             });
226              
227             Emitted when the container stops.
228              
229             =head2 process_error
230              
231             $container->on(container_error => sub {
232             my ($e) = @_;
233             my @errors = @{$e};
234             });
235              
236             Emitted when the container produce errors.
237              
238             =head1 METHODS
239              
240             L inherits all methods from L and implements
241             the following new ones.
242              
243             =head2 start
244              
245             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
246             use Mojo::IOLoop::ReadWriteProcess qw(process);
247              
248             my $c = container( name=>"test", process => sub { print "Hello!" });
249             $c->start();
250              
251             Starts the container, it's main process is a L,
252             contained in the C attribute. On stop it will terminate every process included in the L attribute.
253              
254             =head2 is_running
255              
256             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
257             use Mojo::IOLoop::ReadWriteProcess qw(process);
258              
259             my $c = container( name=>"test", process => sub { print "Hello!" });
260             $c->is_running();
261              
262             Returns 1 if the container is running.
263              
264             =head2 stop
265              
266             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
267             use Mojo::IOLoop::ReadWriteProcess qw(process);
268              
269             my $c = container( name=>"test", process => sub { print "Hello!" })->start;
270             $c->stop();
271              
272             Stops the container and kill all the processes belonging to the cgroup.
273             It also registers all the unknown processes to the current L.
274              
275             =head2 wait_stop
276              
277             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
278             use Mojo::IOLoop::ReadWriteProcess qw(process);
279              
280             my $c = container( name=>"test", process => sub { print "Hello!" })->start;
281             $c->wait_stop();
282              
283             Wait before stopping the container.
284              
285             =head2 wait
286              
287             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
288             use Mojo::IOLoop::ReadWriteProcess qw(process);
289              
290             my $c = container( name=>"test", process => sub { print "Hello!" })->start;
291             $c->wait();
292              
293             Wait the container to stop
294              
295             =head2 migrate_process
296              
297             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
298             use Mojo::IOLoop::ReadWriteProcess qw(process);
299              
300             my $c = container( name=>"test", process => sub { print "Hello!" })->start;
301             $c->migrate_process(42);
302              
303             Migrate the given process to the container cgroup.
304              
305             =head1 ATTRIBUTES
306              
307             L inherits all attributes from L and implements
308             the following new ones.
309              
310             =head2 name
311              
312             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
313             use Mojo::IOLoop::ReadWriteProcess qw(process);
314             use Mojo::IOLoop::ReadWriteProcess::CGroup qw(cgroupv1);
315             use Mojo::Collection 'c';
316              
317             my $container = container( name => "test", process => sub { print "Hello!" } );
318              
319             $container->session->on(register => sub { ... });
320             $container->start();
321              
322             Sets the container name. It creates in the indicated (or default) cgroups a sub-tree with the container name.
323              
324             This means that cgroups settings can be done also outside of the container object:
325              
326             use Mojo::IOLoop::ReadWriteProcess::CGroup qw(cgroupv1);
327             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
328              
329             my $container = container( name => "test", process => sub { print "Hello!" } );
330              
331             cgroupv1->from($continer->cgroups->first->_group)->pid->max(100);
332              
333             As cgroups are represented by path, you can set options directly from controllers objects that are pointing to the same cgroup slice.
334              
335             =head2 group
336              
337             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
338             use Mojo::IOLoop::ReadWriteProcess qw(process);
339             use Mojo::IOLoop::ReadWriteProcess::CGroup qw(cgroupv2);
340             use Mojo::Collection 'c';
341              
342             my $container = container( name => "bar", group => "foo", process => sub { print "Hello!" } );
343             my $container2 = container( name => "bar2", group => "foo", process => sub { print "Hello!" } );
344              
345             $container->start();
346             $container2->start();
347              
348             my $group_cgroup = cgroupv2->from($container2->cgroups->first->parent);
349              
350             $group_cgroup->pid->max(200);
351              
352             Sets the container group. If containers are sharing the same group they will inherit the same CGroup parent path,
353             in such way it is possible to create controllers pointing to it and set specific options for the whole group.
354              
355             =head2 pid_isolation
356              
357             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
358             use Mojo::IOLoop::ReadWriteProcess qw(process);
359             use Mojo::IOLoop::ReadWriteProcess::CGroup qw(cgroupv1);
360             use Mojo::Collection 'c';
361              
362             my $container = container( pid_isolation => 1, process => sub { print "Hello!" } );
363              
364             $container->session->on(register => sub { ... });
365             $container->start();
366              
367             If set, the process will see itself as PID 1. It needs CAP_SYS_ADMIN capabilities set on the executable (or run as root).
368              
369             =head2 pre_migrate
370              
371             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
372             use Mojo::IOLoop::ReadWriteProcess qw(process);
373              
374             my $container = container( pre_migrate => 1, process => sub { print "Hello!" } );
375              
376             $container->session->on(register => sub { ... });
377             $container->start();
378              
379             If set, the process will migrate itself into the cgroup.
380              
381             =head2 clean_cgroup
382              
383             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
384             use Mojo::IOLoop::ReadWriteProcess qw(process);
385              
386             my $container = container( clean_cgroup => 1, process => sub { print "Hello!" });
387              
388             $container->session->on(register => sub { ... });
389             $container->start();
390              
391             If set, attempts to destroy the cgroup after the process terminated its execution.
392              
393             =head2 subreaper
394              
395             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
396             use Mojo::IOLoop::ReadWriteProcess qw(process);
397              
398             my $c = container(subreaper => 1, name=>"test", process => sub { print "Hello!" });
399             $c->start();
400              
401             Enable subreaper mode inside the child process.
402              
403             =head2 process
404              
405             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
406             use Mojo::IOLoop::ReadWriteProcess qw(process);
407              
408             my $c = container(process => sub { print "Hello!" });
409             my $c = container(process => sub { print "Hello!" });
410              
411             $c->start();
412              
413             The process to run. It can be an anonymous function or a L object.
414              
415             =head2 namespace
416              
417             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
418             use Mojo::IOLoop::ReadWriteProcess qw(process);
419              
420             my $c = container(process => sub { print "Hello!" });
421             $c->namespace->unshare(0); # All
422             $c->start();
423              
424             Set/Return L object. It's main use is to invoke syscalls.
425              
426             =head2 session
427              
428             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
429             use Mojo::IOLoop::ReadWriteProcess qw(process);
430              
431             my $c = container(process => process(sub { print "Hello!" }));
432             $c->session->on(register => sub { ... });
433             $c->start();
434              
435             Returns/Set the L singleton object.
436              
437             =head2 unshare
438              
439             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
440             use Mojo::IOLoop::ReadWriteProcess qw(process);
441             use Mojo::IOLoop::ReadWriteProcess::Namespace qw( CLONE_NEWPID CLONE_NEWNS );
442              
443             my $c = container( unshare=> CLONE_NEWPID | CLONE_NEWNS, process => sub { print "Hello!" } );
444             $c->session->on(register => sub { ... });
445             $c->start();
446              
447             Returns/Set the unshare syscall options. See man unshare(2) for further documentation.
448              
449             =head2 cgroups
450              
451             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
452             use Mojo::IOLoop::ReadWriteProcess qw(process);
453             use Mojo::IOLoop::ReadWriteProcess::CGroup qw(cgroupv1);
454             use Mojo::Collection 'c';
455              
456             my $container = container(process => sub { print "Hello!" });
457             $container->cgroups( c(cgroupv1(controller => 'pids'), cgroupv1(controller => 'memory')) );
458              
459             $container->session->on(register => sub { ... });
460             $container->start();
461              
462             Returns/Set a L collection of CGroups where the process should belong to.
463             If used with a single CGroup, you don't need to pass the L object:
464              
465             use Mojo::IOLoop::ReadWriteProcess::Container qw(container);
466             use Mojo::IOLoop::ReadWriteProcess qw(process);
467             use Mojo::IOLoop::ReadWriteProcess::CGroup qw(cgroupv1);
468             use Mojo::Collection 'c';
469              
470             my $container = container(cgroups=> cgroupv1(controller => 'pids'), process => sub { print "Hello!" });
471              
472             $container->session->on(register => sub { ... });
473             $container->start();
474              
475             =head1 DEBUGGING
476              
477             You can set the MOJO_EVENTEMITTER_DEBUG environment variable to get some advanced diagnostics information printed to STDERR.
478              
479             MOJO_EVENTEMITTER_DEBUG=1
480              
481             Also, you can set MOJO_PROCESS_DEBUG environment variable to get diagnostics about the process execution.
482              
483             MOJO_PROCESS_DEBUG=1
484              
485             =head1 LICENSE
486              
487             Copyright (C) Ettore Di Giacinto.
488              
489             This library is free software; you can redistribute it and/or modify
490             it under the same terms as Perl itself.
491              
492             =head1 AUTHOR
493              
494             Ettore Di Giacinto Eedigiacinto@suse.comE
495              
496             =cut
497              
498             1;