File: 300_bl_sh.t

package info (click to toggle)
libfile-nfslock-perl 1.29-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 208 kB
  • sloc: perl: 429; makefile: 2
file content (205 lines) | stat: -rw-r--r-- 5,441 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# Blocking Shared Lock Test
use strict;
use warnings;
use File::Temp qw(tempfile);

use Test::More;
if( $^O eq 'MSWin32' ) {
  plan skip_all => 'Tests fail on Win32 due to forking';
}
else {
  plan tests => 13+3*20;
}
use File::NFSLock;
use Fcntl qw(O_CREAT O_RDWR O_RDONLY O_TRUNC O_APPEND LOCK_EX LOCK_NB LOCK_SH);

# $m simultaneous processes trying to obtain a shared lock
my $m = 20;
my $shared_delay = 5;

$| = 1; # Buffer must be autoflushed because of fork() below.

my $datafile = (tempfile 'XXXXXXXXXX')[1];

# Create a blank file
sysopen ( my $fh, $datafile, O_CREAT | O_RDWR | O_TRUNC );
close ($fh);
# test 1
ok (-e $datafile && !-s _);


my ($rd1, $wr1);
ok (pipe($rd1, $wr1)); # Connected pipe for child1
if (!fork) {
  # Child #1 process
  # Obtain exclusive lock to block the shared attempt later
  my $lock = new File::NFSLock {
    file => $datafile,
    lock_type => LOCK_EX,
  };
  print $wr1 !!$lock; # Send boolean success status down pipe
  close($wr1); # Signal to parent that the Blocking lock is done
  close($rd1);
  if ($lock) {
    sleep 2;  # hold the lock for a moment
    sysopen(my $fh, $datafile, O_RDWR | O_TRUNC);
    # And then put a magic word into the file
    print $fh "exclusive\n";
    close $fh;
  }
  exit;
}
# test 3
ok 1; # Fork successful
close ($wr1);
# Waiting for child1 to finish its lock status
my $child1_lock = <$rd1>;
close ($rd1);
# Report status of the child1_lock.
# It should have been successful
# test 4
ok ($child1_lock);


my ($rd2, $wr2);
ok (pipe($rd2, $wr2)); # Connected pipe for child2
if (!fork) {
  # This should block until the exclusive lock is done
  my $lock = new File::NFSLock {
    file => $datafile,
    lock_type => LOCK_SH,
  };
  if ($lock) {
    sysopen(my $fh, $datafile, O_RDWR | O_TRUNC);
    # Immediately put the magic word into the file
    print $fh "shared\n";
    truncate ($fh, tell $fh);
    close $fh;
    # Normally shared locks never modify the contents because
    # of the race condition.  (The last one to write wins.)
    # But in this case, the parent will wait until the lock
    # status is reported (close RD2) so it defines execution
    # sequence will be correct.  Hopefully the shared lock
    # will not happen until the exclusive lock has been released.
    # This is also a good test to make sure that other shared
    # locks can still be obtained simultaneously.
  }
  print $wr2 !!$lock; # Send boolean success status down pipe
  close($wr2); # Signal to parent that the Blocking lock is done
  close($rd2);
  # Then hold this shared lock for a moment
  # while other shared locks are attempted
  sleep($shared_delay*2);
  exit; # Release the shared lock
}
# test 6
ok 1; # Fork successful
close ($wr2);
# Waiting for child2 to finish its lock status
my $child2_lock = <$rd2>;
close ($rd2);
# Report status of the child2_lock.
# This should have eventually been successful.
# test 7
ok ($child2_lock);

# If all these processes take longer than $shared_delay seconds,
# then they are probably not running synronously
# and the shared lock is not working correctly.
# But if all the children obatin the lock simultaneously,
# like they're supposed to, then it shouldn't take
# much longer than the maximum delay of any of the
# shared locks (at least 5 seconds set above).
$SIG{ALRM} = sub {
  # test (unknown)
  ok 0;
  die "Shared locks not running simultaneously";
};

# Use pipe to read lock success status from children
# test 8
my ($rd3, $wr3);
ok (pipe($rd3, $wr3));

# Wait a few seconds less than if all locks were
# acquired asyncronously to ensure that they overlap.
alarm($m*$shared_delay-2);

for (my $i = 0; $i < $m ; $i++) {
  if (!fork) {
    # All of these locks should immediately be successful since
    # there already exist a shared lock.
    my $lock = new File::NFSLock {
      file => $datafile,
      lock_type => LOCK_SH,
    };
    # Send boolean success status down pipe
    print $wr3 !!$lock,"\n";
    close($wr3);
    if ($lock) {
      sleep $shared_delay;  # Hold the shared lock for a moment
      # Appending should always be safe across NFS
      sysopen(my $fh, $datafile, O_RDWR | O_APPEND);
      # Put one line to signal the lock was successful.
      print $fh "1\n";
      close $fh;
      $lock->unlock();
    } else {
      warn "Lock [$i] failed!";
    }
    exit;
  }
}

# Parent process never writes to pipe
close($wr3);


# There were $m children attempting the shared locks.
for (my $i = 0; $i < $m ; $i++) {
  # Report status of each lock attempt.
  my $got_shared_lock = <$rd3>;
  # test 9 .. 8+$m
  ok $got_shared_lock;
}

# There should not be anything left in the pipe.
my $extra = <$rd3>;
# test 9 + $m
ok !$extra;
close ($rd3);

# If we made it here, then it must have been faster
# than the timeout.  So reset the timer.
alarm(0);
# test 10 + $m
ok 1;

# There are $m children plus the child1 exclusive locker
# and the child2 obtaining the first shared lock.
for (my $i = 0; $i < $m + 2 ; $i++) {
  # Wait until all the children are finished.
  wait;
  # test 11+$m .. 12+2*$m
  ok 1;
}

# Load up whatever the file says now
sysopen(my $fh2, $datafile, O_RDONLY);

# The first line should say "shared" if child2 really
# waited for child1's exclusive lock to finish.
$_ = <$fh2>;
# test 13 + 2*$m
ok /shared/;

for (my $i = 0; $i < $m ; $i++) {
  $_ = <$fh2>;
  chomp;
  # test 14+2*$m .. 13+3*$m
  is $_, 1;
}
close $fh2;

# Wipe the temporary file
unlink $datafile;