line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# ddb by Dan Brumleve |
2
|
|
|
|
|
|
|
# stupid berkeleydb always corrupts my files |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
package ddb; |
5
|
251
|
|
|
251
|
|
873982
|
use POSIX qw(:sys_wait_h); |
|
251
|
|
|
|
|
2067236
|
|
|
251
|
|
|
|
|
3514
|
|
6
|
251
|
|
|
251
|
|
416911
|
use Fcntl qw(:seek :flock O_RDONLY O_RDWR O_TRUNC O_CREAT); |
|
251
|
|
|
|
|
753
|
|
|
251
|
|
|
|
|
43925
|
|
7
|
251
|
|
|
251
|
|
1757
|
use Digest::MD5; |
|
251
|
|
|
|
|
2008
|
|
|
251
|
|
|
|
|
22339
|
|
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
BEGIN { |
10
|
251
|
|
|
251
|
|
753
|
eval { require File::Sync; }; |
|
251
|
|
|
|
|
254765
|
|
11
|
251
|
50
|
|
|
|
13054259
|
$@ and *File::Sync::fsync = sub { 1 }; |
|
0
|
|
|
|
|
0
|
|
12
|
|
|
|
|
|
|
} |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# usage |
15
|
|
|
|
|
|
|
# |
16
|
|
|
|
|
|
|
# use ddb; |
17
|
|
|
|
|
|
|
# $db = tie %db, ddb, 'file.ddb'; |
18
|
|
|
|
|
|
|
# |
19
|
|
|
|
|
|
|
# $db{$key} = $val; |
20
|
|
|
|
|
|
|
# ... |
21
|
|
|
|
|
|
|
# |
22
|
|
|
|
|
|
|
# $db->repair; |
23
|
|
|
|
|
|
|
# $db->defrag; |
24
|
|
|
|
|
|
|
# untie %db; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
# globals |
27
|
|
|
|
|
|
|
$VERSION = '1.3.1'; |
28
|
|
|
|
|
|
|
$hash_size = 16381; # default, or pass to tie after filename |
29
|
|
|
|
|
|
|
$sentinel = 1; |
30
|
|
|
|
|
|
|
$empty_buf_size = 256; |
31
|
|
|
|
|
|
|
$magic = 0xDDB10000; |
32
|
|
|
|
|
|
|
$debug = 0; |
33
|
|
|
|
|
|
|
$max_procs = 10; # for test |
34
|
|
|
|
|
|
|
$show_step = 100; |
35
|
|
|
|
|
|
|
$ptr_pos = undef; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# file format |
38
|
|
|
|
|
|
|
# |
39
|
|
|
|
|
|
|
# [magic, int32] [hash_size, int32] [hash_table, hash_size * int32] |
40
|
|
|
|
|
|
|
# ... [record] ... [record] ... [record] ... |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# record format |
43
|
|
|
|
|
|
|
# |
44
|
|
|
|
|
|
|
# [sentinel, byte] [next_pos, int32] |
45
|
|
|
|
|
|
|
# [key_len int32] [key, key_len * byte] |
46
|
|
|
|
|
|
|
# [padding, 0-3 bytes] [val_hash int32] |
47
|
|
|
|
|
|
|
# [val_len int32] [val, val_len * byte] |
48
|
|
|
|
|
|
|
# |
49
|
|
|
|
|
|
|
# in between each record can be zero or more null-bytes of free space. |
50
|
|
|
|
|
|
|
# the hash table values are absolute file offsets pointing to the |
51
|
|
|
|
|
|
|
# first byte of a record. all int32s are big-endian and aligned. |
52
|
|
|
|
|
|
|
# so every sentinel byte position % 4 == 3. |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
# tie implementation comes first |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub EXISTS { |
57
|
694
|
|
|
694
|
|
2034
|
my ($db, $key) = @_; |
58
|
|
|
|
|
|
|
|
59
|
694
|
|
|
|
|
2932
|
$db->lock_sh; |
60
|
694
|
|
|
|
|
4419
|
my ($pos, $next_pos) = $db->find($key); |
61
|
694
|
|
|
|
|
2522
|
$db->lock_un; |
62
|
|
|
|
|
|
|
|
63
|
694
|
|
|
|
|
7968
|
defined($pos) |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub FETCH { |
67
|
33028
|
|
|
33028
|
|
80251
|
my ($db, $key) = @_; |
68
|
33028
|
|
|
|
|
38370
|
my $val; |
69
|
|
|
|
|
|
|
|
70
|
33028
|
|
|
|
|
66192
|
$db->lock_sh; |
71
|
33028
|
|
|
|
|
71318
|
my ($pos, $next_pos) = $db->find($key); |
72
|
33028
|
50
|
|
|
|
108524
|
defined $pos or goto DONE; |
73
|
|
|
|
|
|
|
|
74
|
33028
|
|
|
|
|
126911
|
$val = $db->read_val(length($key)); |
75
|
|
|
|
|
|
|
|
76
|
33028
|
|
|
|
|
570769
|
DONE: |
77
|
|
|
|
|
|
|
$db->lock_un; |
78
|
33028
|
|
|
|
|
297948
|
$val |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
sub STORE { |
82
|
1679
|
|
|
1679
|
|
29015
|
my ($db, $key, $val) = @_; |
83
|
|
|
|
|
|
|
|
84
|
1679
|
50
|
|
|
|
15656
|
unless (defined $val) { |
85
|
|
|
|
|
|
|
# how else to make it undef? |
86
|
0
|
|
|
|
|
0
|
$db->DELETE($key); |
87
|
0
|
|
|
|
|
0
|
return undef; |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
|
90
|
1679
|
|
|
|
|
19754
|
$db->lock_ex; |
91
|
1679
|
|
|
|
|
40522
|
my ($pos, $next_pos) = $db->find($key); |
92
|
|
|
|
|
|
|
|
93
|
1679
|
100
|
|
|
|
5990
|
if (defined($pos)) { |
94
|
351
|
|
|
|
|
8824
|
my $key_len = length($key); |
95
|
351
|
|
|
|
|
2572
|
my $val_len = length($val); |
96
|
351
|
|
|
|
|
16648
|
$db->align_val($key_len); |
97
|
351
|
|
|
|
|
7224
|
$db->seek(4, SEEK_CUR); |
98
|
351
|
|
|
|
|
2457
|
my $old_val_len = $db->read_int; |
99
|
|
|
|
|
|
|
|
100
|
351
|
100
|
|
|
|
3672
|
if ($old_val_len < $val_len) { |
101
|
74
|
|
|
|
|
2065
|
my $rec = $db->pack_rec($key, $val, $next_pos); |
102
|
74
|
|
|
|
|
600822
|
$db->append_rec($rec); |
103
|
74
|
|
|
|
|
2568
|
my $old_rec_len = $db->rec_len($key_len, $old_val_len); |
104
|
74
|
|
|
|
|
2127
|
$db->erase($pos, $old_rec_len); |
105
|
|
|
|
|
|
|
} else { |
106
|
277
|
|
|
|
|
3122
|
$db->replace_val($key, $val, $pos, $next_pos, $old_val_len); |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
} else { |
109
|
1328
|
|
|
|
|
10985
|
my $rec = $db->pack_rec($key, $val, 0); |
110
|
1328
|
|
|
|
|
7576
|
$db->append_rec($rec); |
111
|
|
|
|
|
|
|
} |
112
|
|
|
|
|
|
|
|
113
|
1679
|
|
|
|
|
18223
|
$db->lock_un; |
114
|
1679
|
|
|
|
|
40624
|
$val |
115
|
|
|
|
|
|
|
} |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub DELETE { |
118
|
3509
|
|
|
3509
|
|
11568
|
my ($db, $key) = @_; |
119
|
3509
|
|
|
|
|
6761
|
my $val; |
120
|
|
|
|
|
|
|
|
121
|
3509
|
|
|
|
|
17892
|
$db->lock_ex; |
122
|
3509
|
|
|
|
|
18623
|
my ($pos, $next_pos) = $db->find($key); |
123
|
3509
|
100
|
|
|
|
15130
|
defined $pos or goto DONE; |
124
|
|
|
|
|
|
|
|
125
|
3457
|
|
|
|
|
5558
|
my $key_len = length($key); |
126
|
3457
|
|
|
|
|
11510
|
$val = $db->read_val($key_len); |
127
|
3457
|
|
|
|
|
7427
|
my $val_len = length($val); |
128
|
|
|
|
|
|
|
|
129
|
3457
|
|
|
|
|
21071
|
$db->seek($ptr_pos, SEEK_SET); |
130
|
3457
|
|
|
|
|
14938
|
$db->write_int($next_pos); |
131
|
3457
|
|
|
|
|
11809
|
$db->sync; |
132
|
|
|
|
|
|
|
|
133
|
3457
|
|
|
|
|
77015694
|
my $rec_len = $db->rec_len($key_len, $val_len); |
134
|
3457
|
|
|
|
|
28308
|
$db->erase($pos, $rec_len); |
135
|
3457
|
|
|
|
|
34120
|
$db->sync; |
136
|
|
|
|
|
|
|
|
137
|
3509
|
|
|
|
|
23703231
|
DONE: |
138
|
|
|
|
|
|
|
$db->lock_un; |
139
|
3509
|
|
|
|
|
183801
|
$val |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
sub CLEAR { |
143
|
255
|
|
|
255
|
|
2520
|
my $db = shift; |
144
|
|
|
|
|
|
|
|
145
|
255
|
|
|
|
|
1521
|
$db->lock_ex; |
146
|
255
|
|
|
|
|
1517
|
$db->seek(0, SEEK_SET); |
147
|
255
|
|
|
|
|
1273
|
$db->write_int($magic); |
148
|
255
|
|
|
|
|
1018
|
$db->write_int($db->{hash_size}); |
149
|
255
|
|
|
|
|
1526
|
$db->write_zero(4 * $db->{hash_size}); |
150
|
|
|
|
|
|
|
|
151
|
255
|
|
|
|
|
1013
|
my $pos = $db->tell; |
152
|
255
|
|
|
|
|
1013
|
$db->truncate($pos); |
153
|
|
|
|
|
|
|
|
154
|
255
|
|
|
|
|
1017
|
$db->sync; |
155
|
255
|
|
|
|
|
11750798
|
$db->lock_un; |
156
|
|
|
|
|
|
|
( ) |
157
|
255
|
|
|
|
|
1561
|
} |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
sub NEXTKEY { |
160
|
67380
|
|
|
67380
|
|
148855
|
my $db = shift; |
161
|
|
|
|
|
|
|
|
162
|
67380
|
|
|
|
|
374660
|
$db->lock_sh; |
163
|
67380
|
|
|
|
|
185010
|
my ($pos, $key) = $db->next_pos; |
164
|
67379
|
|
|
|
|
242481
|
$db->lock_un; |
165
|
|
|
|
|
|
|
|
166
|
67379
|
|
|
|
|
445060
|
$key |
167
|
|
|
|
|
|
|
} |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
sub FIRSTKEY { |
170
|
1037
|
|
|
1037
|
|
3314
|
my $db = shift; |
171
|
1037
|
|
|
|
|
2951
|
undef $db->{cur_hash}; |
172
|
1037
|
|
|
|
|
2279
|
@{$db->{cur_keys}} = ( ); |
|
1037
|
|
|
|
|
8384
|
|
173
|
1037
|
|
|
|
|
4887
|
$$db{rec_count} = 0; |
174
|
1037
|
|
|
|
|
3932
|
$db->NEXTKEY |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
sub TIEHASH { |
178
|
251
|
|
|
251
|
|
7279
|
my ($p, $filename, $hash_size) = @_; |
179
|
|
|
|
|
|
|
|
180
|
251
|
|
|
|
|
3012
|
my $db = bless { |
181
|
|
|
|
|
|
|
fh => undef, |
182
|
|
|
|
|
|
|
filename => $filename, |
183
|
|
|
|
|
|
|
hash_size => $hash_size, |
184
|
|
|
|
|
|
|
cur_hash => undef, |
185
|
|
|
|
|
|
|
cur_keys => [ ], |
186
|
|
|
|
|
|
|
rec_count => 0, |
187
|
|
|
|
|
|
|
lock_count => 0, |
188
|
|
|
|
|
|
|
lock_type => undef, |
189
|
|
|
|
|
|
|
}, $p; |
190
|
|
|
|
|
|
|
|
191
|
251
|
|
|
|
|
2008
|
$db->reopen; |
192
|
|
|
|
|
|
|
|
193
|
251
|
|
|
|
|
1757
|
$db->lock_ex; |
194
|
251
|
|
|
|
|
1506
|
my $end_pos = $db->seek(0, SEEK_END); |
195
|
|
|
|
|
|
|
|
196
|
251
|
50
|
|
|
|
4267
|
if ($end_pos == 0) { |
197
|
251
|
|
33
|
|
|
1757
|
my $hash_size = $db->{hash_size} || $ddb::hash_size; |
198
|
251
|
|
|
|
|
2008
|
$db->warn("empty, creating $hash_size hash entries"); |
199
|
251
|
|
|
|
|
1757
|
$db->write_int($magic); |
200
|
251
|
|
|
|
|
1255
|
$db->write_int($hash_size); |
201
|
251
|
|
|
|
|
1757
|
$db->write_zero(4 * $hash_size); |
202
|
251
|
|
|
|
|
4518
|
$end_pos = $db->tell; |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
|
205
|
251
|
|
|
|
|
1004
|
$db->seek(0, SEEK_SET); |
206
|
251
|
|
|
|
|
502
|
local $ptr_pos = 'magic'; |
207
|
251
|
|
|
|
|
1255
|
my $check_magic = $db->read_int; |
208
|
251
|
50
|
|
|
|
1506
|
pack('N', $check_magic) eq pack('N', $magic) or |
209
|
|
|
|
|
|
|
$db->die("bad magic $check_magic"); |
210
|
|
|
|
|
|
|
|
211
|
251
|
|
|
|
|
502
|
local $ptr_pos = 'hash_size'; |
212
|
251
|
|
|
|
|
502
|
$db->{hash_size} = $db->read_int; |
213
|
|
|
|
|
|
|
|
214
|
251
|
|
|
|
|
1004
|
my $min_size = $db->data_section; |
215
|
251
|
50
|
|
|
|
1255
|
$end_pos < $min_size and |
216
|
|
|
|
|
|
|
$db->die("file truncated, $end_pos / $min_size expected bytes"); |
217
|
251
|
|
|
|
|
1004
|
$db->sync; |
218
|
251
|
|
|
|
|
93133048
|
$db->lock_un; |
219
|
|
|
|
|
|
|
|
220
|
251
|
|
|
|
|
2510
|
$db |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
sub UNTIE { |
224
|
1
|
|
|
1
|
|
14
|
my $db = shift; |
225
|
|
|
|
|
|
|
|
226
|
1
|
|
|
|
|
3
|
$db->{lock_count} = 0; |
227
|
1
|
|
|
|
|
3
|
$db->{lock_type} = undef; |
228
|
1
|
|
|
|
|
2
|
@{$db->{cur_keys}} = ( ); |
|
1
|
|
|
|
|
4
|
|
229
|
1
|
|
|
|
|
3
|
$db->{rec_count} = 0; |
230
|
1
|
|
|
|
|
1
|
$db->{cur_hash} = undef; |
231
|
|
|
|
|
|
|
|
232
|
1
|
|
|
|
|
22
|
close $db->{fh}; |
233
|
1
|
|
|
|
|
9
|
undef $db->{fh}; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
# now everything else, bottom-up |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
sub data_section { |
240
|
321
|
|
|
321
|
0
|
644
|
my $db = shift; |
241
|
321
|
|
|
|
|
1174
|
8 + 4 * $db->{hash_size} |
242
|
|
|
|
|
|
|
} |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
sub rec_len { |
245
|
115941
|
|
|
115941
|
0
|
228911
|
my ($db, $key_len, $val_len) = @_; |
246
|
115941
|
|
|
|
|
615533
|
17 + $key_len + (-$key_len % 4) + $val_len |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
sub key_hash { |
250
|
177535
|
|
|
177535
|
0
|
307165
|
my ($db, $key) = @_; |
251
|
177535
|
|
|
|
|
293401
|
my $hash = 0; |
252
|
177535
|
|
|
|
|
1799896
|
$hash ^= $_ for unpack 'N4', Digest::MD5::md5($key); |
253
|
177535
|
|
|
|
|
753328
|
$hash % $db->{hash_size} |
254
|
|
|
|
|
|
|
} |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
sub val_hash { |
257
|
72945
|
|
|
72945
|
0
|
353111
|
my ($db, $val) = @_; |
258
|
72945
|
|
|
|
|
114481
|
my $hash = 0; |
259
|
72945
|
|
|
|
|
716027
|
$hash ^= $_ for unpack 'N4', Digest::MD5::md5($val); |
260
|
|
|
|
|
|
|
# no modulus |
261
|
72945
|
|
|
|
|
360113
|
unpack 'l', pack 'l', $hash |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
sub key_hash_pos { |
265
|
129519
|
|
|
129519
|
0
|
207846
|
my ($db, $hash) = @_; |
266
|
129519
|
|
|
|
|
627293
|
8 + 4 * $hash |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
sub cur_keys { |
270
|
0
|
|
|
0
|
0
|
0
|
my $db = shift; |
271
|
0
|
|
|
|
|
0
|
@{$db->{cur_keys}} |
|
0
|
|
|
|
|
0
|
|
272
|
|
|
|
|
|
|
} |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub die { |
275
|
31
|
|
|
31
|
0
|
62
|
my ($db, $msg) = @_; |
276
|
|
|
|
|
|
|
|
277
|
31
|
|
33
|
|
|
60
|
$msg ||= $! . "\n"; |
278
|
31
|
50
|
|
|
|
99
|
unless ($msg =~ /\n$/) { |
279
|
31
|
|
|
|
|
67
|
my $pos = $db->tell; |
280
|
31
|
|
|
|
|
67
|
$msg .= " at $pos"; |
281
|
31
|
50
|
|
|
|
89
|
defined($ptr_pos) and $msg .= " from $ptr_pos"; |
282
|
31
|
|
|
|
|
49
|
$msg .= "\n"; |
283
|
|
|
|
|
|
|
} |
284
|
|
|
|
|
|
|
|
285
|
31
|
50
|
|
|
|
1600
|
$db->{lock_count} > 0 and $db->lock_un; |
286
|
31
|
|
|
|
|
301
|
die "$0: $$db{filename}: $msg"; |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
sub warn { |
290
|
331
|
|
|
331
|
0
|
947
|
my ($db, $msg) = @_; |
291
|
|
|
|
|
|
|
|
292
|
331
|
|
33
|
|
|
993
|
$msg ||= $! . "\n"; |
293
|
331
|
50
|
|
|
|
1638
|
unless ($msg =~ /\n$/) { |
294
|
331
|
|
|
|
|
628
|
$msg .= "\n"; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
|
297
|
331
|
|
|
|
|
30638
|
warn "$0: $$db{filename}: $msg"; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
sub show_status { |
301
|
0
|
|
|
0
|
0
|
0
|
my $db = shift; |
302
|
|
|
|
|
|
|
|
303
|
0
|
0
|
|
|
|
0
|
defined($$db{cur_hash}) or return; |
304
|
0
|
|
|
|
|
0
|
my $last_complete = int(100 * ($$db{cur_hash} - 1) / $$db{hash_size}); |
305
|
0
|
|
|
|
|
0
|
my $complete = int(100 * $$db{cur_hash} / $$db{hash_size}); |
306
|
0
|
0
|
0
|
|
|
0
|
$last_complete == $complete && $$db{rec_count} % $show_step and return; |
307
|
0
|
0
|
|
|
|
0
|
my $nl = ($complete == 100) ? " \r\n" : " \r"; |
308
|
0
|
|
|
|
|
0
|
print STDERR "$0: $$db{rec_count} records, $complete% complete $nl"; |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# file operations |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
sub sync { |
315
|
17631
|
|
|
17631
|
0
|
34997
|
my $db = shift; |
316
|
17631
|
50
|
|
|
|
146168
|
File::Sync::fsync($db->{fh}) or $db->warn('fsync failed'); |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
sub tell { |
320
|
1283324
|
|
|
1283324
|
0
|
2068132
|
my $db = shift; |
321
|
1283324
|
|
|
|
|
7111501
|
sysseek $db->{fh}, 0, SEEK_CUR |
322
|
|
|
|
|
|
|
} |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
sub seek { |
325
|
793590
|
|
|
793590
|
0
|
1808865
|
my ($db, $where, $whence) = @_; |
326
|
793590
|
|
|
|
|
5112127
|
sysseek $db->{fh}, $where, $whence |
327
|
|
|
|
|
|
|
} |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
sub truncate { |
330
|
847
|
|
|
847
|
0
|
3704
|
my ($db, $size) = @_; |
331
|
847
|
|
|
|
|
91105
|
truncate($db->{fh}, $size) |
332
|
|
|
|
|
|
|
} |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
sub read { |
335
|
2293013
|
|
|
2293013
|
0
|
3438421
|
my ($db, undef, $len) = @_; |
336
|
2293013
|
|
|
|
|
18844329
|
my $check_len = sysread($db->{fh}, $_[1], $len); |
337
|
2293013
|
100
|
|
|
|
7600911
|
unless ($check_len == $len) { |
338
|
6
|
|
|
|
|
14
|
my $pos = $db->tell - $check_len; |
339
|
6
|
|
|
|
|
27
|
$db->die("cannot read $len bytes"); |
340
|
|
|
|
|
|
|
} |
341
|
2293007
|
|
|
|
|
5472663
|
$_[0] |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
sub read_byte { |
345
|
459856
|
|
|
459856
|
0
|
660286
|
my $db = shift; |
346
|
459856
|
|
|
|
|
1007534
|
$db->read(my $p_byte, 1); |
347
|
459850
|
|
|
|
|
1464612
|
unpack C => $p_byte |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
sub read_sentinel { |
351
|
459856
|
|
|
459856
|
0
|
1617253
|
my $db = shift; |
352
|
459856
|
|
|
|
|
1203445
|
my $byte = $db->read_byte; |
353
|
459850
|
100
|
|
|
|
1914421
|
$byte eq $sentinel or $db->die("bad sentinel $byte"); |
354
|
|
|
|
|
|
|
} |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub read_int { |
357
|
1265563
|
|
|
1265563
|
0
|
2064750
|
my $db = shift; |
358
|
|
|
|
|
|
|
|
359
|
1265563
|
100
|
|
|
|
3576001
|
if ($debug) { |
360
|
1265061
|
|
|
|
|
5379760
|
my $pos = $db->tell; |
361
|
1265061
|
0
|
|
|
|
3817716
|
$pos % 4 and $db->warn( |
|
|
50
|
|
|
|
|
|
362
|
|
|
|
|
|
|
"misaligned read_int at $pos" . |
363
|
|
|
|
|
|
|
(defined($ptr_pos) ? " from $ptr_pos" : "") |
364
|
|
|
|
|
|
|
); |
365
|
|
|
|
|
|
|
} |
366
|
|
|
|
|
|
|
|
367
|
1265563
|
|
|
|
|
2946440
|
$db->read(my $p_int, 4); |
368
|
1265563
|
|
|
|
|
4960150
|
my $int = unpack 'l', pack 'l', unpack 'N', $p_int; |
369
|
|
|
|
|
|
|
|
370
|
1265563
|
|
|
|
|
2969383
|
$int |
371
|
|
|
|
|
|
|
} |
372
|
|
|
|
|
|
|
|
373
|
|
|
|
|
|
|
sub read_empty { |
374
|
3881
|
|
|
3881
|
0
|
7483
|
my $db = shift; |
375
|
3881
|
|
|
|
|
8514
|
my $total = 0; |
376
|
|
|
|
|
|
|
|
377
|
3881
|
|
|
|
|
46654
|
while ((my $buf_size = sysread($db->{fh}, my $buf, $empty_buf_size)) > 0) { |
378
|
4738
|
|
|
|
|
55036
|
$buf =~ /^(\0*)/; |
379
|
4738
|
|
|
|
|
20626
|
my $empty = length($1); |
380
|
4738
|
|
|
|
|
12354
|
$total += $empty; |
381
|
|
|
|
|
|
|
|
382
|
4738
|
100
|
|
|
|
25404
|
if ($empty < $buf_size) { |
383
|
3822
|
|
|
|
|
15484
|
$db->seek($empty - $buf_size, SEEK_CUR); |
384
|
3822
|
|
|
|
|
13966
|
last; |
385
|
|
|
|
|
|
|
} |
386
|
|
|
|
|
|
|
} |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
$total |
389
|
3881
|
|
|
|
|
11627
|
} |
390
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
sub read_key { |
392
|
459856
|
|
|
459856
|
0
|
836495
|
my ($db, $pos, $end_pos) = @_; |
393
|
|
|
|
|
|
|
|
394
|
459856
|
|
|
|
|
983135
|
$db->read_sentinel; |
395
|
459843
|
|
|
|
|
1124623
|
my $next_pos = $db->read_int; |
396
|
459843
|
|
|
|
|
1055685
|
my $key_len = $db->read_int; |
397
|
|
|
|
|
|
|
|
398
|
459843
|
100
|
|
|
|
1558002
|
if (@_ > 1) { |
399
|
71268
|
50
|
33
|
|
|
488727
|
$key_len < 0 || $pos + 9 + $key_len > $end_pos and |
400
|
|
|
|
|
|
|
$db->die("key_len $key_len out of bounds"); |
401
|
|
|
|
|
|
|
} |
402
|
|
|
|
|
|
|
|
403
|
459843
|
|
|
|
|
1130783
|
$db->read(my $key, $key_len); |
404
|
459843
|
50
|
|
|
|
2480757
|
wantarray ? ($key, $next_pos, $key_len) : $key |
405
|
|
|
|
|
|
|
} |
406
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
sub read_val { |
408
|
107753
|
|
|
107753
|
0
|
267584
|
my ($db, $key_len, $pos, $end_pos) = @_; |
409
|
|
|
|
|
|
|
|
410
|
107753
|
|
|
|
|
492193
|
$db->align_val($key_len); |
411
|
107753
|
|
|
|
|
271464
|
my $val_hash = $db->read_int; |
412
|
107753
|
|
|
|
|
567783
|
my $val_len = $db->read_int; |
413
|
107753
|
|
|
|
|
333667
|
my $rec_len = $db->rec_len($key_len, $val_len); |
414
|
|
|
|
|
|
|
|
415
|
107753
|
100
|
|
|
|
274607
|
if (@_ > 2) { |
416
|
71268
|
100
|
66
|
|
|
608746
|
$val_len < 0 || $pos + $rec_len > $end_pos and |
417
|
|
|
|
|
|
|
$db->die("val_len $val_len out of bounds"); |
418
|
|
|
|
|
|
|
} |
419
|
|
|
|
|
|
|
|
420
|
107751
|
|
|
|
|
420041
|
$db->read(my $val, $val_len); |
421
|
107751
|
100
|
|
|
|
495678
|
wantarray ? ($val, $val_hash, $rec_len) : $val |
422
|
|
|
|
|
|
|
} |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
sub read_rec { |
425
|
3822
|
|
|
3822
|
0
|
11353
|
my ($db, $pos, $end_pos) = @_; |
426
|
3822
|
|
|
|
|
19871
|
my ($key, $next_pos, $key_len) = $db->read_key($pos, $end_pos); |
427
|
3820
|
|
|
|
|
20651
|
my ($val, $val_hash, $rec_len) = $db->read_val($key_len, $pos, $end_pos); |
428
|
3819
|
|
|
|
|
21582
|
($key, $val, $next_pos, $val_hash, $rec_len) |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
sub align_val { |
432
|
108104
|
|
|
108104
|
0
|
215187
|
my ($db, $key_len) = @_; |
433
|
108104
|
50
|
|
|
|
623766
|
$db->seek((defined($key_len) ? -$key_len : -$db->tell) % 4, SEEK_CUR); |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
sub write { |
437
|
27458
|
|
|
27458
|
0
|
67145
|
my ($db, $str) = @_; |
438
|
|
|
|
|
|
|
|
439
|
27458
|
|
|
|
|
78373
|
my $len = length($str); |
440
|
27458
|
|
|
|
|
1134698
|
my $check_len = syswrite($db->{fh}, $str, $len); |
441
|
|
|
|
|
|
|
|
442
|
27458
|
50
|
|
|
|
78204
|
unless ($check_len == $len) { |
443
|
0
|
|
|
|
|
0
|
my $missed = $check_len - $len; |
444
|
0
|
|
|
|
|
0
|
$db->die("cannot write $missed/$check_len bytes"); |
445
|
|
|
|
|
|
|
} |
446
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
$len |
448
|
27458
|
|
|
|
|
65415
|
} |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
sub write_byte { |
451
|
0
|
|
|
0
|
0
|
0
|
my ($db, $byte) = @_; |
452
|
0
|
|
|
|
|
0
|
$db->write(pack C => $byte) |
453
|
|
|
|
|
|
|
} |
454
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
sub write_sentinel { |
456
|
0
|
|
|
0
|
0
|
0
|
my $db = shift; |
457
|
0
|
|
|
|
|
0
|
$db->write_byte($sentinel) |
458
|
|
|
|
|
|
|
} |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
sub write_int { |
461
|
13310
|
|
|
13310
|
0
|
43728
|
my ($db, $int) = @_; |
462
|
|
|
|
|
|
|
|
463
|
13310
|
100
|
|
|
|
77966
|
if ($debug) { |
464
|
12808
|
|
|
|
|
62264
|
my $pos = $db->tell; |
465
|
12808
|
0
|
|
|
|
66567
|
$pos % 4 and $db->warn( |
|
|
50
|
|
|
|
|
|
466
|
|
|
|
|
|
|
"misaligned write_int at $pos" . |
467
|
|
|
|
|
|
|
(defined($ptr_pos) ? " from $ptr_pos" : "") |
468
|
|
|
|
|
|
|
); |
469
|
|
|
|
|
|
|
} |
470
|
|
|
|
|
|
|
|
471
|
13310
|
|
|
|
|
134716
|
$db->write(pack 'N', $int); |
472
|
|
|
|
|
|
|
} |
473
|
|
|
|
|
|
|
|
474
|
|
|
|
|
|
|
sub write_key { |
475
|
0
|
|
|
0
|
0
|
0
|
my ($db, $key) = @_; |
476
|
0
|
|
|
|
|
0
|
my $lkey = pack('N', length($key)) . $key; |
477
|
0
|
|
|
|
|
0
|
$db->write($lkey) |
478
|
|
|
|
|
|
|
} |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
sub write_val { |
481
|
0
|
|
|
0
|
0
|
0
|
my ($db, $val) = @_; |
482
|
0
|
|
|
|
|
0
|
my $val_hash = $db->val_hash($val); |
483
|
0
|
|
|
|
|
0
|
my $lval = pack('NN', $val_hash, length($val)) . $val; |
484
|
0
|
|
|
|
|
0
|
$db->write($lval) |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
sub write_zero { |
488
|
8953
|
|
|
8953
|
0
|
35726
|
my ($db, $len) = @_; |
489
|
8953
|
|
|
|
|
88700
|
$db->write("\0" x $len) |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
sub pack_rec { |
493
|
4656
|
|
|
4656
|
0
|
14419
|
my ($db, $key, $val, $next_pos, $val_hash) = @_; |
494
|
|
|
|
|
|
|
|
495
|
4656
|
|
|
|
|
22346
|
my $val_align = "\0" x (-length($key) % 4); |
496
|
4656
|
100
|
|
|
|
30562
|
defined($val_hash) or $val_hash = $db->val_hash($val); |
497
|
|
|
|
|
|
|
|
498
|
4656
|
|
|
|
|
121782
|
my $rec = join '', |
499
|
|
|
|
|
|
|
pack('C', $sentinel), |
500
|
|
|
|
|
|
|
pack('N', $next_pos), |
501
|
|
|
|
|
|
|
pack('N', length($key)), $key, |
502
|
|
|
|
|
|
|
$val_align, |
503
|
|
|
|
|
|
|
pack('N', $val_hash), |
504
|
|
|
|
|
|
|
pack('N', length($val)), $val, |
505
|
|
|
|
|
|
|
; |
506
|
|
|
|
|
|
|
|
507
|
4656
|
50
|
|
|
|
14992
|
if ($debug) { |
508
|
4656
|
50
|
|
|
|
30539
|
length($rec) == $db->rec_len(length($key), length($val)) |
509
|
|
|
|
|
|
|
or $db->warn('record length problem'); |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
|
512
|
|
|
|
|
|
|
$rec |
513
|
4656
|
|
|
|
|
20260
|
} |
514
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
sub write_rec { |
516
|
4912
|
|
|
4912
|
0
|
22512
|
my ($db, $pos, $rec) = @_; |
517
|
|
|
|
|
|
|
|
518
|
4912
|
|
|
|
|
14443
|
$db->seek($pos, SEEK_SET); |
519
|
|
|
|
|
|
|
|
520
|
4912
|
50
|
|
|
|
15444
|
if ($debug) { |
521
|
4912
|
50
|
|
|
|
16216
|
$db->tell % 4 == 3 or $db->warn("writing misaligned record at $pos"); |
522
|
|
|
|
|
|
|
} |
523
|
|
|
|
|
|
|
|
524
|
4912
|
|
|
|
|
18045
|
$db->write($rec); |
525
|
4912
|
|
|
|
|
52980
|
$db->sync; |
526
|
|
|
|
|
|
|
|
527
|
4912
|
|
|
|
|
108170897
|
$db->seek($ptr_pos, SEEK_SET); |
528
|
4912
|
|
|
|
|
4648925
|
$db->write_int($pos); |
529
|
4912
|
|
|
|
|
24869
|
$db->sync; |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub append_rec { |
533
|
1935
|
|
|
1935
|
0
|
20232
|
my ($db, $rec) = @_; |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
# prewrite zero for file integrity |
536
|
1935
|
|
|
|
|
7463
|
my $pos = $db->seek(0, SEEK_END); |
537
|
1935
|
|
|
|
|
5473
|
my $align = 3 - $pos % 4; |
538
|
1935
|
|
|
|
|
3094
|
$pos += $align; |
539
|
1935
|
|
|
|
|
15178
|
$db->write_zero($align + length($rec)); |
540
|
|
|
|
|
|
|
|
541
|
1935
|
|
|
|
|
10478
|
$db->write_rec($pos, $rec); |
542
|
|
|
|
|
|
|
|
543
|
1935
|
|
|
|
|
13629905
|
$pos |
544
|
|
|
|
|
|
|
} |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
sub move_rec { |
547
|
2977
|
|
|
2977
|
0
|
137965
|
my ($db, $rec, $old_pos, $new_pos) = @_; |
548
|
2977
|
|
|
|
|
4839
|
my $rec_len = length($rec); |
549
|
|
|
|
|
|
|
|
550
|
|
|
|
|
|
|
# always move backwards |
551
|
2977
|
100
|
|
|
|
10726
|
if ($old_pos < $new_pos + $rec_len) { |
552
|
|
|
|
|
|
|
# swap using the end of the file as a buffer |
553
|
256
|
|
|
|
|
1486
|
my $tmp_pos = $db->append_rec($rec); |
554
|
256
|
|
|
|
|
4211
|
$db->erase($old_pos, $rec_len); |
555
|
256
|
|
|
|
|
1245
|
$db->write_rec($new_pos, $rec); |
556
|
256
|
|
|
|
|
873823
|
$db->truncate($tmp_pos); |
557
|
|
|
|
|
|
|
} else { |
558
|
2721
|
|
|
|
|
11745
|
$db->write_rec($new_pos, $rec); |
559
|
2721
|
|
|
|
|
27677680
|
$db->erase($old_pos, $rec_len); |
560
|
|
|
|
|
|
|
} |
561
|
|
|
|
|
|
|
|
562
|
2977
|
|
|
|
|
17875
|
$new_pos |
563
|
|
|
|
|
|
|
} |
564
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub replace_val { |
566
|
277
|
|
|
277
|
0
|
1639
|
my ($db, $key, $val, $pos, $next_pos, $old_val_len) = @_; |
567
|
|
|
|
|
|
|
|
568
|
277
|
|
|
|
|
1837
|
my $val_len = length($val); |
569
|
277
|
|
|
|
|
2265
|
my $val_hash = $db->val_hash($val); |
570
|
277
|
|
|
|
|
3059
|
my $rec = $db->pack_rec($key, $val, $next_pos, $val_hash); |
571
|
277
|
|
|
|
|
998
|
my $val_pos = $pos + length($rec) - $val_len - 8; |
572
|
|
|
|
|
|
|
|
573
|
277
|
|
|
|
|
1418
|
my $new_pos = $db->append_rec($rec); |
574
|
|
|
|
|
|
|
|
575
|
|
|
|
|
|
|
# put it back where it was |
576
|
277
|
|
|
|
|
2725
|
$db->seek($val_pos + 8, SEEK_SET); |
577
|
277
|
|
|
|
|
4121
|
$db->write($val . ("\0" x ($old_val_len - $val_len))); |
578
|
|
|
|
|
|
|
|
579
|
277
|
|
|
|
|
2790
|
$db->seek($val_pos, SEEK_SET); |
580
|
277
|
|
|
|
|
1250
|
$db->write_int($val_hash); |
581
|
277
|
|
|
|
|
3409
|
$db->write_int($val_len); |
582
|
|
|
|
|
|
|
|
583
|
277
|
|
|
|
|
1559
|
$db->seek($ptr_pos, SEEK_SET); |
584
|
277
|
|
|
|
|
1095
|
$db->write_int($pos); |
585
|
277
|
|
|
|
|
1234
|
$db->sync; |
586
|
|
|
|
|
|
|
|
587
|
277
|
|
|
|
|
1520576
|
$db->truncate($new_pos); |
588
|
|
|
|
|
|
|
|
589
|
277
|
|
|
|
|
1891
|
$pos |
590
|
|
|
|
|
|
|
} |
591
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
sub lock_ex { |
593
|
9691
|
|
|
9691
|
0
|
46864
|
my $db = shift; |
594
|
9691
|
100
|
|
|
|
96204
|
$$ == $db->{pid} or $db->reopen; |
595
|
|
|
|
|
|
|
|
596
|
9691
|
100
|
|
|
|
71231
|
if ($db->{lock_count} > 0) { |
|
|
50
|
|
|
|
|
|
597
|
|
|
|
|
|
|
# this is allowed by flock but it releases the LOCK_SH |
598
|
|
|
|
|
|
|
# while waiting for the LOCK_EX to avoid deadlock. |
599
|
|
|
|
|
|
|
# ddb disallows it to avoid any confusion; just |
600
|
|
|
|
|
|
|
# LOCK_UN first if you want the flock behavior. |
601
|
4020
|
50
|
|
|
|
105307
|
$db->{lock_type} == LOCK_EX or $db->die("lock conversion"); |
602
|
|
|
|
|
|
|
} elsif ($db->{lock_count} == 0) { |
603
|
5671
|
50
|
|
|
|
32418893
|
RETRY: unless (flock($db->{fh}, LOCK_EX)) { |
604
|
0
|
|
|
|
|
0
|
$db->warn("flock error, retrying: $!"); |
605
|
0
|
|
|
|
|
0
|
$db->reopen; |
606
|
0
|
|
|
|
|
0
|
goto RETRY; |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
} else { |
609
|
0
|
|
|
|
|
0
|
$db->die("negative lock count"); |
610
|
|
|
|
|
|
|
} |
611
|
|
|
|
|
|
|
|
612
|
9691
|
|
|
|
|
29436
|
$db->{lock_type} = LOCK_EX; |
613
|
9691
|
|
|
|
|
26995
|
++$db->{lock_count} |
614
|
|
|
|
|
|
|
} |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
sub lock_sh { |
617
|
101899
|
|
|
101899
|
0
|
144844
|
my $db = shift; |
618
|
101899
|
50
|
|
|
|
462447
|
$$ == $db->{pid} or $db->reopen; |
619
|
|
|
|
|
|
|
|
620
|
101899
|
100
|
|
|
|
212621
|
if ($db->{lock_count} == 0) { |
|
|
50
|
|
|
|
|
|
621
|
100905
|
50
|
|
|
|
95223815
|
RETRY: unless (flock($db->{fh}, LOCK_SH)) { |
622
|
0
|
|
|
|
|
0
|
$db->warn("flock error, retrying: $!"); |
623
|
0
|
|
|
|
|
0
|
$db->reopen; |
624
|
0
|
|
|
|
|
0
|
goto RETRY; |
625
|
|
|
|
|
|
|
} |
626
|
100905
|
|
|
|
|
218485
|
$db->{lock_type} = LOCK_SH; |
627
|
|
|
|
|
|
|
} elsif ($db->{lock_count} < 0) { |
628
|
0
|
|
|
|
|
0
|
$db->die("negative lock count"); |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
|
631
|
101899
|
|
|
|
|
198340
|
++$db->{lock_count} |
632
|
|
|
|
|
|
|
} |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
sub lock_un { |
635
|
111590
|
|
|
111590
|
0
|
190567
|
my $db = shift; |
636
|
|
|
|
|
|
|
|
637
|
111590
|
50
|
|
|
|
491132
|
if ($db->{lock_count} < 1) { |
|
|
100
|
|
|
|
|
|
638
|
0
|
|
|
|
|
0
|
$db->warn("no locks held"); |
639
|
0
|
|
|
|
|
0
|
flock($db->{fh}, LOCK_UN); |
640
|
0
|
|
|
|
|
0
|
0 |
641
|
|
|
|
|
|
|
} elsif ($db->{lock_count} == 1) { |
642
|
106576
|
|
|
|
|
1304308
|
flock($db->{fh}, LOCK_UN); |
643
|
106576
|
|
|
|
|
309633
|
undef $db->{lock_type}; |
644
|
106576
|
|
|
|
|
264031
|
--$db->{lock_count} |
645
|
|
|
|
|
|
|
} else { |
646
|
5014
|
|
|
|
|
29982
|
--$db->{lock_count} |
647
|
|
|
|
|
|
|
} |
648
|
|
|
|
|
|
|
} |
649
|
|
|
|
|
|
|
|
650
|
0
|
|
|
0
|
0
|
0
|
sub lock { shift->lock_ex } |
651
|
0
|
|
|
0
|
0
|
0
|
sub unlock { shift->lock_un } |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# we call this after fork so locks work again |
654
|
|
|
|
|
|
|
sub reopen { |
655
|
502
|
|
|
502
|
0
|
1205
|
my $db = shift; |
656
|
|
|
|
|
|
|
|
657
|
502
|
100
|
|
|
|
44561
|
$db->{fh} and close $db->{fh}; |
658
|
502
|
|
|
|
|
7754
|
undef $db->{fh}; |
659
|
502
|
50
|
|
|
|
36135
|
if ($db->{lock_count} > 0) { |
660
|
0
|
|
|
|
|
0
|
$db->warn('reopening with held locks'); |
661
|
0
|
|
|
|
|
0
|
undef $db->{lock_type}; |
662
|
0
|
|
|
|
|
0
|
$db->{lock_count} = 0; |
663
|
|
|
|
|
|
|
} |
664
|
|
|
|
|
|
|
|
665
|
502
|
50
|
|
|
|
24564701
|
sysopen($db->{fh}, $db->{filename}, O_RDWR | O_CREAT) or $db->die; |
666
|
502
|
|
|
|
|
4186
|
binmode $db->{fh}; |
667
|
|
|
|
|
|
|
|
668
|
502
|
|
|
|
|
10262219
|
$db->{pid} = $$; # keep track of forks |
669
|
|
|
|
|
|
|
|
670
|
502
|
|
|
|
|
4856
|
$db |
671
|
|
|
|
|
|
|
} |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
sub find { |
674
|
110086
|
|
|
110086
|
0
|
211146
|
my ($db, $key) = @_; |
675
|
|
|
|
|
|
|
|
676
|
110086
|
|
|
|
|
272761
|
my $hash = $db->key_hash($key); |
677
|
110086
|
|
|
|
|
309134
|
$ptr_pos = $db->key_hash_pos($hash); |
678
|
|
|
|
|
|
|
|
679
|
110086
|
|
|
|
|
363091
|
$db->seek($ptr_pos, SEEK_SET); |
680
|
110086
|
|
|
|
|
833440
|
my $pos = $db->read_int; |
681
|
110086
|
|
|
|
|
174134
|
my %loop_test; # debug |
682
|
|
|
|
|
|
|
|
683
|
110086
|
|
|
|
|
315804
|
while ($pos != 0) { |
684
|
388576
|
50
|
33
|
|
|
2559991
|
$pos % 4 == 3 && $pos >= 0 or |
685
|
|
|
|
|
|
|
$db->die("found misaligned record"); |
686
|
|
|
|
|
|
|
|
687
|
388576
|
50
|
|
|
|
1090589
|
if ($debug) { |
688
|
388576
|
50
|
|
|
|
1682460
|
$loop_test{$pos}++ and |
689
|
|
|
|
|
|
|
$db->die("loop record"); |
690
|
|
|
|
|
|
|
} |
691
|
|
|
|
|
|
|
|
692
|
388576
|
|
|
|
|
1016241
|
$db->seek($pos, SEEK_SET); |
693
|
388576
|
|
|
|
|
2873107
|
my ($check_key, $next_pos) = $db->read_key; |
694
|
|
|
|
|
|
|
|
695
|
388575
|
100
|
|
|
|
1874625
|
$check_key eq $key and |
|
|
100
|
|
|
|
|
|
696
|
|
|
|
|
|
|
return wantarray ? ($pos, $next_pos) : $pos; |
697
|
|
|
|
|
|
|
|
698
|
280238
|
|
|
|
|
471716
|
$ptr_pos = $pos + 1; |
699
|
280238
|
|
|
|
|
793294
|
$pos = $next_pos |
700
|
|
|
|
|
|
|
} |
701
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
( ) |
703
|
1748
|
|
|
|
|
8367
|
} |
704
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
sub erase { |
706
|
6508
|
|
|
6508
|
0
|
21291
|
my ($db, $pos, $rec_len) = @_; |
707
|
6508
|
|
|
|
|
37322
|
$db->seek($pos, SEEK_SET); |
708
|
6508
|
|
|
|
|
158811
|
$db->write_zero($rec_len); |
709
|
6508
|
|
|
|
|
43876
|
$rec_len |
710
|
|
|
|
|
|
|
} |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
# no rec_len known |
713
|
|
|
|
|
|
|
sub erase_panic { |
714
|
4
|
|
|
4
|
0
|
13
|
my ($db, $pos, $status_cb) = @_; |
715
|
4
|
|
50
|
0
|
|
18
|
$status_cb ||= sub { }; |
|
0
|
|
|
|
|
0
|
|
716
|
4
|
|
|
|
|
12
|
$db->$status_cb(0); |
717
|
|
|
|
|
|
|
|
718
|
4
|
|
|
|
|
12
|
my $end_pos = $db->seek(0, SEEK_END); |
719
|
|
|
|
|
|
|
|
720
|
4
|
|
|
|
|
15
|
local $db->{cur_keys} = [ ]; |
721
|
4
|
|
|
|
|
27
|
local $db->{cur_hash} = undef; |
722
|
4
|
|
|
|
|
11
|
local $db->{rec_count} = 0; |
723
|
4
|
|
|
|
|
7
|
my $count = 0; |
724
|
|
|
|
|
|
|
|
725
|
4
|
|
|
|
|
7
|
while (1) { |
726
|
198
|
|
|
|
|
569
|
my ($k_pos, $k) = $db->next_pos; |
727
|
198
|
100
|
|
|
|
398
|
defined($k_pos) or last; |
728
|
194
|
|
|
|
|
471
|
$db->$status_cb(++$count); |
729
|
194
|
100
|
|
|
|
422
|
$k_pos > $pos or next; |
730
|
66
|
100
|
|
|
|
148
|
$k_pos < $end_pos and $end_pos = $k_pos; |
731
|
|
|
|
|
|
|
} |
732
|
|
|
|
|
|
|
|
733
|
4
|
|
|
|
|
8
|
my $rec_len = $end_pos - $pos; |
734
|
4
|
|
|
|
|
24
|
$db->warn("erasing corrupted record at $pos+$rec_len"); |
735
|
|
|
|
|
|
|
|
736
|
4
|
|
|
|
|
18
|
$db->seek($pos, SEEK_SET); |
737
|
4
|
|
|
|
|
24
|
$db->write_zero($rec_len); |
738
|
|
|
|
|
|
|
|
739
|
4
|
|
|
|
|
24
|
$rec_len |
740
|
|
|
|
|
|
|
} |
741
|
|
|
|
|
|
|
|
742
|
|
|
|
|
|
|
# during iteration we preload a hash-bucket at a time and |
743
|
|
|
|
|
|
|
# check each key right before returning it. |
744
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
sub next_pos { |
746
|
68378
|
|
|
68378
|
0
|
85884
|
my ($db, $status_cb) = @_; |
747
|
68378
|
|
100
|
20250
|
|
1203756
|
$status_cb ||= sub { }; |
|
20250
|
|
|
|
|
30563
|
|
748
|
|
|
|
|
|
|
|
749
|
68378
|
|
50
|
|
|
375951
|
$db->{cur_keys} ||= [ ]; |
750
|
68378
|
|
|
|
|
126427
|
my $cur_keys = $db->{cur_keys}; |
751
|
68378
|
|
|
|
|
213038
|
my $end_pos = $db->seek(0, SEEK_END); |
752
|
|
|
|
|
|
|
|
753
|
68378
|
|
|
|
|
97845
|
while (1) { |
754
|
87783
|
|
|
|
|
306043
|
while (defined(my $key = shift @$cur_keys)) { |
755
|
67354
|
|
|
|
|
173286
|
my ($pos, $next_pos) = $db->find($key); |
756
|
67354
|
100
|
|
|
|
395035
|
if (defined($pos)) { |
757
|
67353
|
|
|
|
|
93100
|
++$db->{rec_count}; |
758
|
67353
|
|
|
|
|
478100
|
return ($pos, $key); |
759
|
|
|
|
|
|
|
} |
760
|
1
|
50
|
|
|
|
11
|
$debug and $db->warn("skipping unlinked cached record"); |
761
|
|
|
|
|
|
|
} |
762
|
|
|
|
|
|
|
|
763
|
20430
|
100
|
|
|
|
57961
|
$db->{cur_hash} = |
764
|
|
|
|
|
|
|
defined($db->{cur_hash}) ? |
765
|
|
|
|
|
|
|
$db->{cur_hash} + 1 : 0; |
766
|
20430
|
|
|
|
|
42705
|
$db->$status_cb; |
767
|
20430
|
100
|
|
|
|
118288
|
unless ($db->{cur_hash} < $db->{hash_size}) { |
768
|
998
|
|
|
|
|
2169
|
undef $db->{cur_hash}; |
769
|
998
|
|
|
|
|
4091
|
return ( ); |
770
|
|
|
|
|
|
|
} |
771
|
|
|
|
|
|
|
|
772
|
19432
|
|
|
|
|
127953
|
$ptr_pos = $db->key_hash_pos($db->{cur_hash}); |
773
|
19432
|
|
|
|
|
45422
|
$db->seek($ptr_pos, SEEK_SET); |
774
|
19432
|
|
|
|
|
53797
|
my $pos = $db->read_int; |
775
|
|
|
|
|
|
|
|
776
|
19432
|
|
|
|
|
27157
|
my %loop_test; # debug-only |
777
|
|
|
|
|
|
|
|
778
|
19432
|
|
|
|
|
48436
|
while ($pos != 0) { |
779
|
67474
|
100
|
100
|
|
|
524679
|
$pos % 4 == 3 && $pos >= 0 or |
780
|
|
|
|
|
|
|
$db->die("misaligned record"); |
781
|
|
|
|
|
|
|
|
782
|
67459
|
50
|
|
|
|
153738
|
if ($debug) { |
783
|
67459
|
100
|
|
|
|
317902
|
$loop_test{$pos}++ and |
784
|
|
|
|
|
|
|
$db->die("loop found"); |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
|
787
|
67458
|
|
|
|
|
179045
|
$db->seek($pos, SEEK_SET); |
788
|
67458
|
|
|
|
|
151830
|
my ($key, $next_pos, $key_len) = $db->read_key($pos, $end_pos); |
789
|
|
|
|
|
|
|
|
790
|
67448
|
50
|
|
|
|
204768
|
if ($debug) { |
791
|
67448
|
50
|
|
|
|
562781
|
$db->{cur_hash} == $db->key_hash($key) or |
792
|
|
|
|
|
|
|
$db->die("key_hash mismatch"); |
793
|
|
|
|
|
|
|
|
794
|
67448
|
|
|
|
|
189206
|
my ($val, $val_hash) = $db->read_val($key_len, $pos, $end_pos); |
795
|
67447
|
|
|
|
|
169013
|
my $check_val_hash = $db->val_hash($val); |
796
|
67447
|
50
|
|
|
|
191139
|
$check_val_hash == $val_hash or |
797
|
|
|
|
|
|
|
$db->die("val_hash mismatch"); |
798
|
|
|
|
|
|
|
} |
799
|
|
|
|
|
|
|
|
800
|
67447
|
|
|
|
|
140755
|
push @$cur_keys, $key; |
801
|
67447
|
|
|
|
|
143260
|
$ptr_pos = $pos + 1; |
802
|
67447
|
|
|
|
|
186645
|
$pos = $next_pos |
803
|
|
|
|
|
|
|
} |
804
|
|
|
|
|
|
|
} |
805
|
|
|
|
|
|
|
} |
806
|
|
|
|
|
|
|
|
807
|
|
|
|
|
|
|
# scan the data section linearly and remove empty space |
808
|
|
|
|
|
|
|
sub defrag { |
809
|
67
|
|
|
67
|
0
|
214
|
my ($db, $status_cb) = @_; |
810
|
67
|
|
50
|
4153
|
|
1346
|
$status_cb ||= sub { }; |
|
4153
|
|
|
|
|
7020
|
|
811
|
|
|
|
|
|
|
|
812
|
67
|
|
|
|
|
307
|
local $debug = 1; |
813
|
|
|
|
|
|
|
|
814
|
67
|
|
|
|
|
389
|
$db->lock_ex; |
815
|
67
|
|
|
|
|
1106
|
my $end_pos = $db->seek(0, SEEK_END); |
816
|
67
|
|
|
|
|
254
|
$db->$status_cb(0, $end_pos); |
817
|
|
|
|
|
|
|
|
818
|
67
|
|
|
|
|
224
|
my $empty_pos = $db->data_section; |
819
|
67
|
|
|
|
|
191
|
my $empty_len = 0; |
820
|
|
|
|
|
|
|
|
821
|
67
|
|
|
|
|
261
|
while ($empty_pos < $end_pos) { |
822
|
3881
|
|
|
|
|
33983
|
$db->seek($empty_pos + $empty_len, SEEK_SET); |
823
|
3881
|
|
|
|
|
17404
|
$empty_len += $db->read_empty; |
824
|
3881
|
|
|
|
|
8973
|
my $pos = $empty_pos + $empty_len; |
825
|
|
|
|
|
|
|
|
826
|
3881
|
100
|
|
|
|
14966
|
unless ($pos < $end_pos) { |
827
|
59
|
50
|
|
|
|
668
|
$empty_pos < $end_pos and $db->truncate($end_pos = $empty_pos); |
828
|
59
|
|
|
|
|
174
|
last; |
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
|
831
|
|
|
|
|
|
|
sub ep_status_cb |
832
|
4020
|
|
|
4020
|
0
|
19533
|
{ shift->$status_cb($empty_pos, $end_pos - $empty_len, @_) } |
833
|
3822
|
|
|
|
|
14080
|
ep_status_cb($db); |
834
|
3822
|
|
|
|
|
18376
|
$ptr_pos = "defrag $pos"; |
835
|
|
|
|
|
|
|
|
836
|
3822
|
|
|
|
|
18557
|
$db->lock_ex; |
837
|
3822
|
|
|
|
|
9215
|
my ($key, $val, $next_pos, $val_hash, $rec_len) = eval { |
838
|
3822
|
|
|
|
|
19826
|
$db->read_rec($pos, $end_pos) |
839
|
|
|
|
|
|
|
}; |
840
|
3822
|
100
|
|
|
|
13532
|
if ($@) { |
841
|
3
|
|
|
|
|
385
|
warn($@); |
842
|
3
|
|
|
|
|
23
|
$empty_len += $db->erase_panic($pos, \&ep_status_cb); |
843
|
3
|
|
|
|
|
17
|
next; |
844
|
|
|
|
|
|
|
} |
845
|
3819
|
|
|
|
|
25178
|
$db->lock_un; |
846
|
|
|
|
|
|
|
|
847
|
3819
|
|
|
|
|
15311
|
my $check_val_hash = $db->val_hash($val); |
848
|
|
|
|
|
|
|
|
849
|
3819
|
|
|
|
|
15737
|
my $check_pos = $db->find($key); |
850
|
3818
|
100
|
|
|
|
11976
|
unless ($check_pos == $pos) { |
851
|
36
|
100
|
|
|
|
112
|
if ($check_val_hash == $val_hash) { |
852
|
35
|
50
|
|
|
|
94
|
if (defined($check_pos)) { |
853
|
|
|
|
|
|
|
# this can delete indexed data in a pathological case |
854
|
|
|
|
|
|
|
# (a corrupted record with valid hash that overlaps indexed |
855
|
|
|
|
|
|
|
# records, very unlikely by accident). but it's doesn't |
856
|
|
|
|
|
|
|
# have to scan the entire database like erase_panic. |
857
|
|
|
|
|
|
|
|
858
|
0
|
|
|
|
|
0
|
$db->warn("erasing unlinked record at $pos+$rec_len"); |
859
|
0
|
|
|
|
|
0
|
$empty_len += $db->erase($pos, $rec_len); |
860
|
|
|
|
|
|
|
} else { |
861
|
|
|
|
|
|
|
# this record is left over from an aborted delete or |
862
|
|
|
|
|
|
|
# part of a chain after an erased corrupted record, |
863
|
|
|
|
|
|
|
# so we relink it. |
864
|
|
|
|
|
|
|
|
865
|
35
|
|
|
|
|
259
|
$db->warn("relinking unlinked record at $pos+$rec_len"); |
866
|
|
|
|
|
|
|
|
867
|
35
|
|
|
|
|
314
|
$db->seek($pos + 1, SEEK_SET); |
868
|
35
|
|
|
|
|
209
|
$db->write_int(0); |
869
|
|
|
|
|
|
|
|
870
|
35
|
|
|
|
|
94
|
$db->seek($ptr_pos, SEEK_SET); |
871
|
35
|
|
|
|
|
87
|
$db->write_int($pos); |
872
|
35
|
|
|
|
|
121
|
$db->sync; |
873
|
|
|
|
|
|
|
} |
874
|
|
|
|
|
|
|
} else { |
875
|
1
|
|
|
|
|
10
|
$db->warn("val_hash mismatch at $pos+$rec_len"); |
876
|
1
|
|
|
|
|
10
|
$empty_len += $db->erase_panic($pos, \&ep_status_cb); |
877
|
|
|
|
|
|
|
} |
878
|
36
|
|
|
|
|
155183
|
next; |
879
|
|
|
|
|
|
|
} |
880
|
|
|
|
|
|
|
|
881
|
3782
|
50
|
|
|
|
9589
|
$check_val_hash == $val_hash or |
882
|
|
|
|
|
|
|
$db->die("val_hash mismatch"); |
883
|
|
|
|
|
|
|
|
884
|
3782
|
|
|
|
|
12299
|
my $align = 3 - $empty_pos % 4; |
885
|
3782
|
|
|
|
|
5153
|
$empty_pos += $align; |
886
|
3782
|
|
|
|
|
5418
|
$empty_len -= $align; |
887
|
|
|
|
|
|
|
|
888
|
3782
|
100
|
|
|
|
9669
|
if ($empty_len > 0) { |
889
|
2977
|
|
|
|
|
12901
|
my $rec = $db->pack_rec($key, $val, $next_pos, $val_hash); |
890
|
2977
|
|
|
|
|
10845
|
$db->move_rec($rec, $pos, $empty_pos); |
891
|
|
|
|
|
|
|
} else { |
892
|
|
|
|
|
|
|
# should never happen |
893
|
805
|
|
|
|
|
1116
|
$empty_len = 0; |
894
|
|
|
|
|
|
|
} |
895
|
|
|
|
|
|
|
|
896
|
3782
|
|
|
|
|
29450
|
$empty_pos += $rec_len; |
897
|
|
|
|
|
|
|
} |
898
|
|
|
|
|
|
|
|
899
|
66
|
|
|
|
|
305
|
$db->sync; |
900
|
66
|
|
|
|
|
2625218
|
$db->$status_cb($end_pos, $end_pos); |
901
|
66
|
|
|
|
|
478
|
$db->lock_un; |
902
|
|
|
|
|
|
|
} |
903
|
|
|
|
|
|
|
|
904
|
|
|
|
|
|
|
# this will null out any pointers to corrupted records |
905
|
|
|
|
|
|
|
sub repair { |
906
|
9
|
|
|
9
|
0
|
35
|
my ($db, $status_cb) = @_; |
907
|
9
|
|
50
|
941
|
|
105
|
$status_cb ||= sub { }; |
|
941
|
|
|
|
|
1229
|
|
908
|
|
|
|
|
|
|
|
909
|
9
|
|
|
|
|
21
|
local $debug = 1; |
910
|
9
|
|
|
|
|
36
|
local $db->{cur_keys} = [ ]; |
911
|
9
|
|
|
|
|
25
|
local $db->{cur_hash} = undef; |
912
|
9
|
|
|
|
|
23
|
local $db->{rec_count} = 0; |
913
|
|
|
|
|
|
|
|
914
|
9
|
|
|
|
|
39
|
$db->lock_ex; |
915
|
|
|
|
|
|
|
|
916
|
9
|
|
|
|
|
12
|
while (1) { |
917
|
796
|
|
|
|
|
1929
|
$db->lock_sh; |
918
|
796
|
|
|
|
|
890
|
my $pos = eval { $db->next_pos($status_cb) }; |
|
796
|
|
|
|
|
1798
|
|
919
|
|
|
|
|
|
|
|
920
|
796
|
100
|
|
|
|
1832
|
unless ($@) { |
921
|
770
|
|
|
|
|
1626
|
$db->lock_un; |
922
|
770
|
100
|
|
|
|
1782
|
defined($pos) or last; |
923
|
761
|
|
|
|
|
1343
|
$db->$status_cb; |
924
|
761
|
|
|
|
|
998
|
next; |
925
|
|
|
|
|
|
|
} |
926
|
26
|
|
|
|
|
1900
|
warn $@; |
927
|
|
|
|
|
|
|
|
928
|
26
|
50
|
|
|
|
94
|
unless ($ptr_pos > 0) { |
929
|
0
|
|
|
|
|
0
|
$db->warn("bad ptr $ptr_pos, cannot repair bucket $$db{cur_hash}"); |
930
|
0
|
|
|
|
|
0
|
next; |
931
|
|
|
|
|
|
|
} |
932
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
# $db->seek($ptr_pos, SEEK_SET); |
934
|
|
|
|
|
|
|
# my $pos = $db->read_int; |
935
|
|
|
|
|
|
|
# $db->seek($pos, SEEK_SET); |
936
|
|
|
|
|
|
|
# $db->lock_sh; |
937
|
|
|
|
|
|
|
# my ($key, $next_pos) = eval { $db->read_key }; |
938
|
|
|
|
|
|
|
# $@ or $db->lock_un; |
939
|
|
|
|
|
|
|
# $next_pos ||= 0; |
940
|
|
|
|
|
|
|
# $next_pos == $ptr_pos - 1 and $next_pos = 0; # loops |
941
|
|
|
|
|
|
|
|
942
|
26
|
|
|
|
|
34
|
my $next_pos = 0; |
943
|
26
|
|
|
|
|
143
|
$db->warn("unlinking from $ptr_pos, to $next_pos (run defrag)"); |
944
|
26
|
|
|
|
|
93
|
$db->seek($ptr_pos, SEEK_SET); |
945
|
26
|
|
|
|
|
71
|
$db->write_int($next_pos); |
946
|
|
|
|
|
|
|
} |
947
|
|
|
|
|
|
|
|
948
|
9
|
|
|
|
|
28
|
$db->sync; |
949
|
9
|
|
|
|
|
38106
|
$db->lock_un; |
950
|
|
|
|
|
|
|
} |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
# run a bunch of tests. this will erase your database. |
953
|
|
|
|
|
|
|
sub test { |
954
|
251
|
|
|
251
|
0
|
12550
|
my ($db, $db_hash, $ok_cb) = @_; |
955
|
|
|
|
|
|
|
|
956
|
251
|
50
|
|
|
|
1255
|
ref($db_hash) or $db->die('test requires ref to tied hash'); |
957
|
251
|
|
|
|
|
1004
|
local *db = \%$db_hash; |
958
|
251
|
50
|
|
|
|
1255
|
tied(%db) == $db or $db->die('tied hash does not match object'); |
959
|
|
|
|
|
|
|
|
960
|
251
|
0
|
50
|
0
|
|
1004
|
$ok_cb ||= sub { $_[2] or $_[0]->die("not ok $_[1]\n") }; |
|
0
|
|
|
|
|
0
|
|
961
|
3072
|
|
|
3072
|
0
|
16808
|
sub ok { $db->$ok_cb(@_) } |
962
|
|
|
|
|
|
|
|
963
|
251
|
|
|
0
|
|
16064
|
local $SIG{PIPE} = sub { }; |
|
0
|
|
|
|
|
0
|
|
964
|
251
|
|
|
|
|
1004
|
local $debug = 1; |
965
|
251
|
|
|
|
|
4267
|
my $procs = 0; |
966
|
|
|
|
|
|
|
|
967
|
251
|
|
|
|
|
1004
|
ok 0, 65; |
968
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
# clear |
970
|
251
|
|
|
|
|
8785
|
$db->{hash_size} = 19; |
971
|
251
|
|
|
|
|
1757
|
%db = ( ); |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
# store, fetch, delete, exists |
974
|
251
|
|
|
|
|
6275
|
$db{hello} = 'world'; |
975
|
251
|
|
|
|
|
3765
|
ok 1, $db{hello} eq 'world'; |
976
|
251
|
|
|
|
|
115209
|
ok 2, 'world' eq delete $db{hello}; |
977
|
251
|
|
|
|
|
92619
|
ok 3, !exists $db{hello}; |
978
|
|
|
|
|
|
|
|
979
|
|
|
|
|
|
|
# small key and value |
980
|
251
|
|
|
|
|
53212
|
$db{''} = ''; |
981
|
251
|
|
|
|
|
3263
|
ok 4, exists $db{''}; |
982
|
251
|
|
|
|
|
95129
|
ok 5, defined $db{''}; |
983
|
251
|
|
|
|
|
39658
|
ok 6, $db{''} eq ''; |
984
|
251
|
|
|
|
|
56475
|
ok 7, '' eq delete $db{''}; |
985
|
251
|
|
|
|
|
90862
|
ok 8, keys(%db) == 0; |
986
|
|
|
|
|
|
|
|
987
|
|
|
|
|
|
|
# parallel inserts |
988
|
251
|
|
|
|
|
56475
|
for my $key (1 .. 100) { |
989
|
20150
|
|
|
|
|
6076793029
|
wait, --$procs until $procs < $max_procs; |
990
|
20150
|
100
|
|
|
|
167319
|
++$procs; fork and next; |
|
20150
|
|
|
|
|
279988199
|
|
991
|
100
|
|
|
|
|
55938
|
$db{$key} = $key; |
992
|
100
|
|
|
|
|
0
|
exit 0; |
993
|
|
|
|
|
|
|
} |
994
|
151
|
|
|
|
|
253262032
|
--$procs until wait < 0; |
995
|
151
|
|
|
|
|
449980
|
delete $db{50}; |
996
|
|
|
|
|
|
|
|
997
|
151
|
|
|
|
|
1510
|
my ($ksum, $vsum); |
998
|
151
|
|
|
|
|
2869
|
$ksum += $_ for keys %db; |
999
|
151
|
|
|
|
|
4077
|
$vsum += $_ for values %db; |
1000
|
151
|
|
|
|
|
10117
|
ok 9, keys(%db) == 99; |
1001
|
151
|
|
|
|
|
25217
|
ok 10, $ksum == 5000; |
1002
|
151
|
|
|
|
|
184975
|
ok 11, $vsum == 5000; |
1003
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
# swap a bunch of values with recursive locks in parallel |
1005
|
151
|
|
|
|
|
1963
|
for (1 .. 99) { |
1006
|
10098
|
|
|
|
|
1891113064
|
wait, --$procs until $procs < $max_procs; |
1007
|
10098
|
100
|
|
|
|
237018
|
++$procs; fork and next; |
|
10098
|
|
|
|
|
32821060
|
|
1008
|
99
|
|
|
|
|
26639
|
my $key1 = 1 + int rand 49; |
1009
|
99
|
|
|
|
|
25917
|
my $key2 = 51 + int rand 49; |
1010
|
99
|
|
|
|
|
50413
|
$db->lock_ex; |
1011
|
99
|
|
|
|
|
48784
|
@db{$key1, $key2} = @db{$key2, $key1}; |
1012
|
99
|
|
|
|
|
6358
|
$db->lock_un; |
1013
|
99
|
|
|
|
|
0
|
exit 0; |
1014
|
|
|
|
|
|
|
} |
1015
|
52
|
|
|
|
|
93090504
|
--$procs until wait < 0; |
1016
|
|
|
|
|
|
|
|
1017
|
52
|
|
|
|
|
468
|
my $sum = 0; $sum += $_ for values %db; |
|
52
|
|
|
|
|
2652
|
|
1018
|
52
|
|
|
|
|
6500
|
ok 12, $sum == 5000; |
1019
|
52
|
|
|
|
|
23140
|
ok 13, scalar grep $_ ne $db{$_}, keys %db; # odd number of swaps |
1020
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
# remove half the keys, making holes for defragging |
1022
|
52
|
|
66
|
|
|
21008
|
$_ & 1 or delete $db{$_} for 1 .. 100; |
1023
|
52
|
|
|
|
|
468
|
ok 14, keys(%db) == 50; |
1024
|
|
|
|
|
|
|
|
1025
|
|
|
|
|
|
|
# defragging does not change iteration order |
1026
|
52
|
|
|
|
|
19552
|
my $db_str0 = join ":", map "$_-$db{$_}", keys %db; |
1027
|
52
|
|
|
|
|
1872
|
$db->defrag; |
1028
|
52
|
|
|
|
|
624
|
my $db_str1 = join ":", map "$_-$db{$_}", keys %db; |
1029
|
52
|
|
|
|
|
1352
|
ok 15, $db_str0 eq $db_str1; |
1030
|
|
|
|
|
|
|
|
1031
|
|
|
|
|
|
|
# big values |
1032
|
52
|
|
|
|
|
17836
|
my $big = 100000; |
1033
|
52
|
|
|
|
|
25012
|
$db{'x' x $big} = 'y' x $big; |
1034
|
52
|
|
|
|
|
5616
|
ok 16, $db{'x' x $big} eq 'y' x $big; |
1035
|
52
|
|
|
|
|
19500
|
ok 17, $procs == 0; |
1036
|
|
|
|
|
|
|
|
1037
|
|
|
|
|
|
|
# growing values in parallel |
1038
|
52
|
|
|
|
|
9828
|
while (my ($k, $v) = each %db) { |
1039
|
1377
|
|
|
|
|
366853050
|
wait, --$procs until $procs < $max_procs; |
1040
|
1377
|
100
|
|
|
|
5218
|
++$procs; fork and next; |
|
1377
|
|
|
|
|
24376139
|
|
1041
|
51
|
|
|
|
|
20234
|
$db{$k} = $v . $v; |
1042
|
51
|
|
|
|
|
0
|
exit 0; |
1043
|
|
|
|
|
|
|
} |
1044
|
1
|
|
|
|
|
1566620
|
--$procs until wait < 0; |
1045
|
|
|
|
|
|
|
|
1046
|
1
|
|
|
|
|
23
|
ok 18, keys(%db) == 51; |
1047
|
1
|
|
|
|
|
721
|
ok 19, $db{'x' x $big} eq 'y' x (2 * $big); |
1048
|
1
|
|
|
|
|
348
|
ok 20, exists $db{51}; |
1049
|
|
|
|
|
|
|
|
1050
|
|
|
|
|
|
|
# defrag should shrink after value growth |
1051
|
1
|
|
|
|
|
208
|
my $end0 = $db->seek(0, SEEK_END); |
1052
|
1
|
|
|
|
|
13
|
$db->defrag; |
1053
|
1
|
|
|
|
|
8
|
my $end1 = $db->seek(0, SEEK_END); |
1054
|
1
|
|
|
|
|
7
|
ok 21, $end1 < $end0; |
1055
|
|
|
|
|
|
|
|
1056
|
|
|
|
|
|
|
# but not again |
1057
|
1
|
|
|
|
|
387
|
$db->defrag; |
1058
|
1
|
|
|
|
|
5
|
my $end2 = $db->seek(0, SEEK_END); |
1059
|
1
|
|
|
|
|
13
|
ok 22, $end1 == $end2; |
1060
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
# clear should truncate |
1062
|
1
|
|
|
|
|
394
|
%db = ('a' .. 'z'); |
1063
|
1
|
|
|
|
|
18
|
my $end3 = $db->seek(0, SEEK_END); |
1064
|
1
|
|
|
|
|
7
|
ok 23, $end3 < $end2; |
1065
|
1
|
|
|
|
|
348
|
ok 24, values(%db) == 13; |
1066
|
|
|
|
|
|
|
|
1067
|
1
|
|
|
|
|
388
|
$db->reopen; |
1068
|
1
|
|
|
|
|
8
|
ok 25, join('', map $_ . $db{$_}, sort keys %db) eq join('', 'a' .. 'z'); |
1069
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
# grow a value for a while and add noise in front of it |
1071
|
1
|
|
|
|
|
369
|
%db = ( ); |
1072
|
1
|
|
|
|
|
21
|
$db{a} = 'a' x $_ for 1 .. 5; |
1073
|
1
|
|
|
|
|
8
|
my $offset = $db->data_section + 20; |
1074
|
1
|
|
|
|
|
3
|
$offset += 3 - $offset % 4; |
1075
|
1
|
|
|
|
|
6
|
$db->seek($offset, SEEK_SET); |
1076
|
1
|
|
|
|
|
9
|
$db->write(pack('C', $sentinel) . "\x02\x03\x04\x05"); |
1077
|
|
|
|
|
|
|
|
1078
|
|
|
|
|
|
|
# defrag should erase the noise and warn |
1079
|
1
|
|
|
|
|
8
|
$db->warn("warnings expected on test 26"); |
1080
|
1
|
|
|
|
|
8
|
$db->defrag; |
1081
|
1
|
|
|
|
|
9
|
my $end4 = $db->seek(0, SEEK_END); |
1082
|
1
|
|
|
|
|
7
|
my $check_end4 = $db->data_section; |
1083
|
1
|
|
|
|
|
4
|
$check_end4 += 3 - $check_end4 % 4; |
1084
|
1
|
|
|
|
|
7
|
$check_end4 += $db->rec_len(1, 5); |
1085
|
1
|
|
|
|
|
7
|
ok 26, $end4 == $check_end4; |
1086
|
1
|
|
|
|
|
425
|
ok 27, $db{a} eq 'a' x 5; |
1087
|
|
|
|
|
|
|
|
1088
|
1
|
|
|
|
|
206
|
$db{pack 'C', $_} = $_ for 0 .. 255; |
1089
|
1
|
|
|
|
|
14
|
ok 28, $db{a} == ord 'a'; |
1090
|
|
|
|
|
|
|
|
1091
|
|
|
|
|
|
|
# skeet-shooting test |
1092
|
1
|
|
|
|
|
50
|
$db->warn("warnings permitted on test 29"); |
1093
|
1
|
|
|
|
|
3
|
my @pid; |
1094
|
|
|
|
|
|
|
|
1095
|
1
|
|
|
0
|
|
33
|
$SIG{ALRM} = sub { }; |
|
0
|
|
|
|
|
0
|
|
1096
|
1
|
|
|
|
|
6
|
for (1 .. $max_procs) { |
1097
|
10
|
50
|
|
|
|
12367
|
if (my $pid = fork) { |
1098
|
10
|
|
|
|
|
157
|
++$procs; |
1099
|
10
|
|
|
|
|
239
|
push @pid, $pid; |
1100
|
10
|
|
|
|
|
136
|
next; |
1101
|
|
|
|
|
|
|
} |
1102
|
0
|
|
|
|
|
0
|
$db{pack 'C', int rand 256} = 'x'; |
1103
|
0
|
|
|
|
|
0
|
exit 0; |
1104
|
|
|
|
|
|
|
} |
1105
|
1
|
|
|
|
|
73
|
undef $SIG{ALRM}; |
1106
|
|
|
|
|
|
|
|
1107
|
1
|
|
|
|
|
80
|
while ($procs > 0) { |
1108
|
4
|
|
|
|
|
9449
|
kill ALRM => $_ for @pid; |
1109
|
4
|
|
|
|
|
416466
|
select undef, undef, undef, 0.1; |
1110
|
4
|
|
|
|
|
9217
|
--$procs while waitpid(-1, &WNOHANG) > 0; |
1111
|
|
|
|
|
|
|
} |
1112
|
|
|
|
|
|
|
|
1113
|
1
|
|
|
|
|
27
|
$db->defrag; |
1114
|
1
|
|
|
|
|
31
|
ok 29, join('', sort keys %db) eq pack('C*', 0 .. 255); |
1115
|
1
|
|
|
|
|
127
|
ok 30, $procs == 0; |
1116
|
|
|
|
|
|
|
|
1117
|
|
|
|
|
|
|
# delete future records while iterating |
1118
|
1
|
|
|
|
|
16
|
$db->warn("warnings permitted on test 31"); |
1119
|
1
|
|
|
|
|
2
|
my $total = 256; |
1120
|
1
|
|
|
|
|
6
|
while (my ($k, $v) = each %db) { |
1121
|
184
|
|
|
|
|
552
|
my $unp_k2 = 2 * unpack('C', $k); |
1122
|
184
|
|
|
|
|
9445
|
my $k2 = pack('C', $unp_k2); |
1123
|
184
|
100
|
|
|
|
1308
|
if (exists $db{$k2}) { |
1124
|
107
|
|
|
|
|
173
|
--$total; |
1125
|
107
|
|
|
|
|
1265
|
delete $db{$k2}; |
1126
|
|
|
|
|
|
|
} |
1127
|
|
|
|
|
|
|
} |
1128
|
|
|
|
|
|
|
|
1129
|
1
|
|
|
|
|
14
|
ok 31, keys(%db) == $total; |
1130
|
|
|
|
|
|
|
|
1131
|
1
|
|
|
|
|
462
|
while (my $k = each %db) { delete $db{$k}; } |
|
149
|
|
|
|
|
3644
|
|
1132
|
1
|
|
|
|
|
8
|
ok 32, keys(%db) == 0; |
1133
|
|
|
|
|
|
|
|
1134
|
1
|
|
|
|
|
350
|
$db->defrag; |
1135
|
1
|
|
|
|
|
9
|
my $size = $db->seek(0, SEEK_END); |
1136
|
1
|
|
|
|
|
7
|
ok 33, $size == $db->data_section; |
1137
|
|
|
|
|
|
|
|
1138
|
1
|
|
|
|
|
455
|
for (1 .. 100) { |
1139
|
100
|
100
|
|
|
|
473
|
if ($_ & 1) { |
1140
|
50
|
|
|
|
|
538
|
$db{$_} = 'x' x $_; |
1141
|
|
|
|
|
|
|
} else { |
1142
|
50
|
|
|
|
|
1074
|
$db{'x' x $_} = $_; |
1143
|
|
|
|
|
|
|
} |
1144
|
|
|
|
|
|
|
} |
1145
|
|
|
|
|
|
|
|
1146
|
1
|
|
|
|
|
13
|
ok 34, length($db{87}) == 87; |
1147
|
1
|
|
|
|
|
358
|
ok 35, $db{'x' x 50} == 50; |
1148
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
# link corruption |
1150
|
1
|
|
|
|
|
201
|
%db = (1 .. 200); |
1151
|
|
|
|
|
|
|
|
1152
|
1
|
|
|
|
|
58
|
my ($pos, $next_pos) = $db->find(101); |
1153
|
1
|
|
|
|
|
6
|
ok 36, defined($pos); |
1154
|
1
|
|
|
|
|
443
|
$db->seek($ptr_pos, SEEK_SET); |
1155
|
1
|
|
|
|
|
7
|
$db->write("\xFF" x 4); # oops |
1156
|
|
|
|
|
|
|
|
1157
|
1
|
|
|
|
|
3
|
my ($pos, $next_pos) = $db->find(99); |
1158
|
1
|
|
|
|
|
5
|
ok 37, defined($pos); |
1159
|
1
|
|
|
|
|
172
|
$db->seek($ptr_pos, SEEK_SET); |
1160
|
1
|
|
|
|
|
6
|
$db->write("\xFE" x 4); # oops again |
1161
|
|
|
|
|
|
|
|
1162
|
1
|
|
|
|
|
6
|
$db->warn("warnings expected on test 38"); |
1163
|
1
|
|
|
|
|
8
|
$db->repair; |
1164
|
1
|
|
|
|
|
7
|
ok 38, keys(%db) <= 98; |
1165
|
1
|
|
|
|
|
564
|
ok 39, !exists $db{101}; |
1166
|
1
|
|
|
|
|
213
|
ok 40, !exists $db{99}; |
1167
|
|
|
|
|
|
|
|
1168
|
1
|
|
|
|
|
198
|
$db->warn("warnings expected on test 41"); |
1169
|
1
|
|
|
|
|
6
|
$db->defrag; |
1170
|
1
|
|
|
|
|
8
|
ok 41, keys(%db) == 100; |
1171
|
|
|
|
|
|
|
|
1172
|
|
|
|
|
|
|
# no warnings |
1173
|
1
|
|
|
|
|
523
|
my $keys = keys(%db); |
1174
|
1
|
|
|
|
|
33
|
$db->repair; |
1175
|
1
|
|
|
|
|
6
|
$db->defrag; |
1176
|
1
|
|
|
|
|
8
|
ok 42, keys(%db) == $keys; |
1177
|
|
|
|
|
|
|
|
1178
|
1
|
|
|
|
|
460
|
my $end_pos = $db->seek(0, SEEK_END); |
1179
|
1
|
|
|
|
|
9
|
my $key = 'hello'; |
1180
|
1
|
|
|
|
|
14
|
$db{$key} = 'world'; |
1181
|
1
|
|
|
|
|
13
|
my $keys = keys(%db); |
1182
|
1
|
|
|
|
|
140
|
my ($pos) = $db->find($key); |
1183
|
1
|
|
|
|
|
5
|
ok 43, defined($pos); |
1184
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
# corrupt a sentinel, expect error |
1186
|
1
|
|
|
|
|
354
|
$db->warn('warnings expected on test 44'); |
1187
|
1
|
|
|
|
|
5
|
$db->seek($pos, SEEK_SET); |
1188
|
1
|
|
|
|
|
6
|
$db->write("\x03"); |
1189
|
1
|
|
|
|
|
2
|
eval { my @keys = keys %db }; |
|
1
|
|
|
|
|
6
|
|
1190
|
1
|
50
|
|
|
|
185
|
$@ and warn $@; |
1191
|
1
|
|
|
|
|
7
|
ok 44, $@; |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
# fix the hash table |
1194
|
1
|
|
|
|
|
327
|
$db->warn('warnings expected on test 45'); |
1195
|
1
|
|
|
|
|
5
|
$db->repair; |
1196
|
1
|
|
|
|
|
12
|
ok 45, !exists $db{$key}; |
1197
|
1
|
|
|
|
|
1936
|
ok 46, keys(%db) < $keys; |
1198
|
|
|
|
|
|
|
|
1199
|
|
|
|
|
|
|
# fix the data |
1200
|
1
|
|
|
|
|
392
|
$db->warn('warnings expected on test 47'); |
1201
|
1
|
|
|
|
|
6
|
my $keys = keys(%db); |
1202
|
1
|
|
|
|
|
40
|
$db->defrag; |
1203
|
1
|
|
|
|
|
11
|
ok 47, keys(%db) == $keys; |
1204
|
1
|
|
|
|
|
499
|
ok 48, $db->seek(0, SEEK_END) == $end_pos; |
1205
|
|
|
|
|
|
|
|
1206
|
1
|
|
|
|
|
219
|
$db->lock_sh; |
1207
|
1
|
|
|
|
|
4
|
my ($k_pos, $k) = $db->next_pos; |
1208
|
1
|
|
|
|
|
5
|
$db->lock_un; |
1209
|
1
|
|
33
|
|
|
15
|
ok 49, defined($k_pos) && defined($k); |
1210
|
|
|
|
|
|
|
|
1211
|
|
|
|
|
|
|
# defrag fails on bad link |
1212
|
1
|
|
|
|
|
233
|
$db->warn('warnings expected on test 50'); |
1213
|
1
|
|
|
|
|
6
|
$db->seek($k_pos + 1, SEEK_SET); |
1214
|
1
|
|
|
|
|
6
|
$db->write("\x07" x 4); |
1215
|
1
|
|
|
|
|
2
|
eval { $db->defrag }; |
|
1
|
|
|
|
|
6
|
|
1216
|
1
|
50
|
|
|
|
131
|
$@ and warn $@; |
1217
|
1
|
|
|
|
|
7
|
ok 50, $@; |
1218
|
|
|
|
|
|
|
|
1219
|
|
|
|
|
|
|
# fix it |
1220
|
1
|
|
|
|
|
347
|
$db->warn('warnings expected on test 51'); |
1221
|
1
|
|
|
|
|
5
|
$db->repair; |
1222
|
1
|
|
|
|
|
7
|
$db->defrag; |
1223
|
1
|
|
|
|
|
10
|
ok 51, exists $db{$k}; |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
# write random data |
1226
|
1
|
|
|
|
|
362
|
$db->warn('warnings expected on test 52'); |
1227
|
1
|
|
|
|
|
5
|
%db = ( ); |
1228
|
1
|
|
|
|
|
31
|
$db{$_} = 'x' x $_ for 1 .. 100; |
1229
|
1
|
|
|
|
|
8
|
$db->seek(1139, SEEK_SET); |
1230
|
1
|
|
|
|
|
110
|
$db->write(pack 'C*', map int(rand(256)), 1 .. 101); |
1231
|
|
|
|
|
|
|
|
1232
|
1
|
|
|
|
|
9
|
$db->repair; |
1233
|
1
|
|
|
|
|
8
|
$db->defrag; |
1234
|
|
|
|
|
|
|
|
1235
|
1
|
|
|
|
|
13
|
my $keys = keys %db; |
1236
|
1
|
|
|
|
|
28
|
ok 52, $keys > 0; |
1237
|
1
|
|
|
|
|
408
|
ok 53, $keys < 100; |
1238
|
|
|
|
|
|
|
|
1239
|
|
|
|
|
|
|
# no warnings |
1240
|
1
|
|
|
|
|
168
|
$db->repair; |
1241
|
1
|
|
|
|
|
6
|
$db->defrag; |
1242
|
1
|
|
|
|
|
9
|
ok 54, keys(%db) == $keys; |
1243
|
|
|
|
|
|
|
|
1244
|
|
|
|
|
|
|
# loop test |
1245
|
1
|
|
|
|
|
402
|
$db{$_} = 'x' x $_ for 1 .. 200; |
1246
|
1
|
|
|
|
|
5
|
my $keys = 200; |
1247
|
1
|
|
|
|
|
7
|
my ($k1_pos, $k1) = $db->next_pos; |
1248
|
1
|
|
|
|
|
6
|
ok 55, defined($k1_pos); |
1249
|
1
|
|
|
|
|
359
|
my ($k2_pos, $k2) = $db->next_pos; |
1250
|
1
|
|
|
|
|
5
|
ok 56, defined($k2_pos); |
1251
|
1
|
|
|
|
|
178
|
my ($k3_pos, $k3) = $db->next_pos; |
1252
|
1
|
|
|
|
|
5
|
ok 57, defined($k3_pos); |
1253
|
1
|
|
|
|
|
182
|
ok 58, $db->{cur_hash} == 0; |
1254
|
|
|
|
|
|
|
|
1255
|
1
|
|
|
|
|
175
|
$db->warn('warnings expected on test 59'); |
1256
|
1
|
|
|
|
|
6
|
$db->seek($k2_pos + 1, SEEK_SET); |
1257
|
1
|
|
|
|
|
6
|
$db->write_int($k1_pos); |
1258
|
1
|
|
|
|
|
6
|
$db->repair; |
1259
|
1
|
|
|
|
|
8
|
$db->defrag; |
1260
|
|
|
|
|
|
|
|
1261
|
1
|
|
|
|
|
13
|
ok 59, exists $db{$k1}; |
1262
|
1
|
|
|
|
|
359
|
ok 60, exists $db{$k2}; |
1263
|
1
|
|
|
|
|
312
|
ok 61, exists $db{$k3}; |
1264
|
1
|
|
|
|
|
455
|
ok 62, keys(%db) == $keys; |
1265
|
|
|
|
|
|
|
|
1266
|
|
|
|
|
|
|
# ultimate test |
1267
|
1
|
|
|
|
|
443
|
$db->warn('warnings expected on test 63'); |
1268
|
1
|
|
|
|
|
6
|
$db->seek(8, SEEK_SET); |
1269
|
1
|
|
|
|
|
10
|
$db->write_int(int rand(1 << 16)) for 1 .. 3000; |
1270
|
|
|
|
|
|
|
|
1271
|
1
|
|
|
|
|
9
|
$db->seek($db->key_hash_pos($db->key_hash('hello')), SEEK_SET); |
1272
|
1
|
|
|
|
|
5
|
$db->write_int(0); |
1273
|
1
|
|
|
|
|
14
|
$db{hello} = 'world'; |
1274
|
|
|
|
|
|
|
|
1275
|
1
|
|
|
|
|
12
|
$db->repair; |
1276
|
1
|
|
|
|
|
7
|
$db->defrag; |
1277
|
1
|
|
|
|
|
13
|
ok 63, $db{hello} eq 'world'; |
1278
|
|
|
|
|
|
|
|
1279
|
|
|
|
|
|
|
# no warnings or truncation |
1280
|
1
|
|
|
|
|
374
|
my $size = $db->seek(0, SEEK_END); |
1281
|
1
|
|
|
|
|
13
|
$db->repair; |
1282
|
1
|
|
|
|
|
5
|
$db->defrag; |
1283
|
1
|
|
|
|
|
4
|
ok 64, $db->seek(0, SEEK_END) == $size; |
1284
|
1
|
|
|
|
|
259
|
ok 65, keys(%db) == 1; |
1285
|
|
|
|
|
|
|
|
1286
|
1
|
|
|
|
|
237
|
1 |
1287
|
|
|
|
|
|
|
} |
1288
|
|
|
|
|
|
|
|
1289
|
|
|
|
|
|
|
1 |
1290
|
|
|
|
|
|
|
# the end |