File Coverage

blib/lib/KiokuDB/Test/Fixture/Concurrency.pm
Criterion Covered Total %
statement 49 140 35.0
branch 1 24 4.1
condition n/a
subroutine 16 27 59.2
pod 0 6 0.0
total 66 197 33.5


line stmt bran cond sub pod time code
1             package KiokuDB::Test::Fixture::Concurrency;
2 2     2   4901 use Moose;
  2         312486  
  2         17  
3              
4 2     2   13782 use Test::More;
  2         16  
  2         20  
5 2     2   2177 use Test::Exception;
  2         6627  
  2         10  
6              
7 2     2   596 use Try::Tiny;
  2         5  
  2         142  
8 2     2   12 use List::Util qw(sum);
  2         4  
  2         138  
9 2     2   415 use Scope::Guard;
  2         326  
  2         92  
10 2     2   1153 use POSIX qw(_exit :sys_wait_h);
  2         12692  
  2         14  
11              
12 2     2   2432 use namespace::clean -except => 'meta';
  2         4  
  2         19  
13              
14             with qw(KiokuDB::Test::Fixture) => { excludes => [qw/run required_backend_roles/] };
15              
16 2     2   1346 use constant required_backend_roles => qw(Clear TXN Concurrency::POSIX);
  2         4  
  2         161  
17              
18 2     2   10 use constant FORKS => 20;
  2         5  
  2         100  
19 2     2   10 use constant COUNTERS => 250;
  2         4  
  2         96  
20 2     2   11 use constant ACCOUNTS => 250;
  2         2  
  2         98  
21 2     2   11 use constant ITER => 10;
  2         4  
  2         145  
22              
23             my @ids = qw(foo bar gorch baz);
24              
25             {
26             package Foo;
27 2     2   11 use Moose;
  2         3  
  2         17  
28              
29             has bar => ( is => 'rw' );
30             }
31              
32             has exit => (
33             isa => "Int",
34             is => "rw",
35             default => 0,
36             );
37              
38             before precheck => sub {
39             my $self = shift;
40              
41             };
42              
43             sub create {
44             return (
45 0         0 counter => { value => 0 },
46 0         0 (map { ( "counter_$_" => { value => 0 } ) } 1 .. COUNTERS ),
47 0     0 0 0 (map { ( "${_}_account" => { value => 0 } ) } 1 .. ACCOUNTS),
48             );
49             }
50              
51             sub run {
52 33     33 0 54 my $self = shift;
53              
54 33         81 SKIP: {
55 33         47 local $Test::Builder::Level = $Test::Builder::Level + 1;
56              
57 33 50       211 skip "Set KIOKUDB_STRESS_TEST to run this fixture", 1 unless $ENV{KIOKUDB_STRESS_TEST};
58              
59 0           $self->precheck;
60              
61             lives_ok {
62 0     0     local $Test::Builder::Level = $Test::Builder::Level - 1;
63             $self->txn_do(sub {
64 0           my $s = $self->new_scope;
65 0           $self->backend->clear;
66 0           $self->populate;
67 0           });
68 0           } "populated OK";
69              
70 0           $self->clear_directory;
71              
72 0           $self->verify;
73              
74 0           is_deeply( [ $self->live_objects ], [ ], "no live objects at end of " . $self->name . " fixture" );
75              
76 0           $self->clear_live_objects;
77             }
78             }
79              
80             sub verify {
81 0     0 0   my $self = shift;
82              
83 0           ok( !$self->has_directory, "no directory object" );
84              
85             # force re-instantiation of directory
86 0           $self->clear_directory;
87              
88 0           foreach my $num ( 1 .. FORKS ) {
89 0 0         defined(my $pid = fork) or die $!;
90 0 0         next if $pid;
91              
92             my $guard = Scope::Guard->new(sub {
93             # avoid cleanups on errors
94 2     2   14294 use POSIX qw(_exit);
  2         4  
  2         16  
95 0     0     _exit($self->exit);
96 0           });
97              
98             # make sure each child gets a different random seed
99 0           srand($$ ^ time);
100              
101 0           $self->run_child($num);
102             }
103              
104 0           my $skip = 0;
105              
106 0           while ( wait > 0 ) {
107 0           do {
108 0 0         $skip++ if $?
109             } while waitpid(-1, WNOHANG) > 0;
110              
111 0           $self->check_consistency;
112             }
113              
114 0           $self->check_counters($skip);
115             }
116              
117             sub check_consistency {
118 0     0 0   my $self = shift;
119              
120 0           my $ok;
121              
122 0           my ( $counter, @accounts );
123              
124 0           attempt: foreach my $attempt ( 1 .. FORKS ) {
125             last attempt if try {
126             $self->txn_do(sub {
127 0           my $s = $self->new_scope;
128              
129 0           $counter = $self->lookup("counter")->{value};
130              
131 0           @accounts = map { $_->{value} } $self->lookup(map { "${_}_account" } 1 .. ACCOUNTS);
  0            
  0            
132 0     0     });
133              
134 0           ++$ok;
135 0 0         };
136             }
137              
138              
139             SKIP: {
140 0 0         skip "lock contention", 3 unless $ok;
  0            
141              
142 0           cmp_ok( $counter, '>=', 0, "counter not 0" );
143 0           cmp_ok( $counter, '<=', FORKS, "counter <= counters" );
144              
145 0           is( sum(@accounts), 0, "account sum is 0 (state is consistent)" );
146             };
147             }
148              
149             sub check_counters {
150 0     0 0   my ( $self, $skip ) = @_;
151              
152             $self->txn_do(sub {
153 0     0     my $s = $self->new_scope;
154              
155 0           my $counter = $self->lookup_ok("counter");
156 0           is( $counter->{value}, FORKS-$skip, "total counter value" );
157              
158 0           my @counters = $self->lookup_ok(map { "counter_$_" } 1 .. COUNTERS);
  0            
159              
160 0           is( sum(map { $_->{value} } @counters), FORKS-$skip, "counters sum" );
  0            
161 0           });
162             }
163              
164             sub run_child {
165 0     0 0   my ( $self, $child ) = @_;
166              
167 0           for ( 1 .. ITER ) {
168             try {
169             $self->txn_do(sub {
170 0           my $s = $self->new_scope;
171              
172 0           my $id = @ids[int rand @ids];
173              
174 0 0         if ( my $foo = $self->lookup($id) ) {
175 0 0         if ( rand > 0.5 ) {
176 0           $foo->bar("foo");
177 0           $self->update($foo);
178             } else {
179 0           $self->delete($foo);
180             }
181             } else {
182 0           $self->insert( foo => Foo->new( bar => "bar" ) );
183             }
184              
185 0           my ( $one, $two ) = $self->lookup( map { int(rand ACCOUNTS) . "_account" } 1 .. 2 );
  0            
186              
187 0           my $amount = int(rand 10000);
188 0           $one->{value} += $amount;
189 0           $two->{value} -= $amount;
190              
191 0 0         select(undef,undef,undef,0.01) if rand > 0.5;
192              
193 0           $self->update($one, $two);
194 0     0     });
195 0           };
196              
197 0           select(undef,undef,undef, 0.01);
198             }
199              
200 0           my $ok;
201              
202 0           attempt: foreach my $attempt ( 1 .. FORKS*2 ) {
203             last attempt if try {
204             $self->txn_do(sub {
205 0           my $s = $self->new_scope;
206              
207 0           my $counter = $self->lookup("counter");
208              
209 0           my $counter_two = $self->lookup("counter_" . int rand COUNTERS);
210              
211 0 0         select(undef,undef,undef,0.02 * rand) if rand > 0.5;
212              
213 0           $counter_two->{value}++;
214 0           $self->update($counter_two);
215              
216 0           $counter->{value}++;
217 0           $self->update($counter);
218 0     0     });
219              
220 0           ++$ok;
221 0 0         };
222              
223 0           select(undef,undef,undef, 0.05);
224             }
225              
226 0 0         $self->exit(1) unless $ok;
227             }
228              
229             __PACKAGE__->meta->make_immutable;
230              
231             __PACKAGE__
232              
233             __END__