perobs 2.4.0 → 2.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/perobs/BTreeBlob.rb +2 -1
- data/lib/perobs/BTreeDB.rb +3 -2
- data/lib/perobs/FlatFile.rb +24 -6
- data/lib/perobs/FlatFileDB.rb +7 -18
- data/lib/perobs/IndexTree.rb +17 -7
- data/lib/perobs/IndexTreeNode.rb +8 -2
- data/lib/perobs/RobustFile.rb +88 -0
- data/lib/perobs/Store.rb +12 -7
- data/lib/perobs/TreeDB.rb +3 -2
- data/lib/perobs/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3580a4c1778350e195656c42ca100c651ec18386
|
4
|
+
data.tar.gz: e3c964bb3f135372dd07ffc7b36c108f27990569
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c12c89af79a909c65346fe8ea35e2de608dbba0ce4eb85be35b56bd07a5c50858cd5773ab94ee8e6dd3c8b75c1a872ac7400a0bbc78927c715954c337a7288e
|
7
|
+
data.tar.gz: f93ee4dfb7552724b78f14e682a0750e53c7984910c588050e9dc92b1d2571c35ea8ff7a5fb095ef6b537a238d15da4bb7d31a6126d1ec7aeea1d89d19138dda
|
data/lib/perobs/BTreeBlob.rb
CHANGED
@@ -28,6 +28,7 @@
|
|
28
28
|
require 'zlib'
|
29
29
|
|
30
30
|
require 'perobs/Log'
|
31
|
+
require 'perobs/RobustFile'
|
31
32
|
|
32
33
|
module PEROBS
|
33
34
|
|
@@ -209,7 +210,7 @@ module PEROBS
|
|
209
210
|
def write_to_blobs_file(raw, address)
|
210
211
|
begin
|
211
212
|
File.write(@blobs_file_name, raw, address)
|
212
|
-
rescue => e
|
213
|
+
rescue IOError => e
|
213
214
|
PEROBS.log.fatal "Cannot write blobs file #{@blobs_file_name}: " +
|
214
215
|
e.message
|
215
216
|
end
|
data/lib/perobs/BTreeDB.rb
CHANGED
@@ -28,6 +28,7 @@
|
|
28
28
|
require 'fileutils'
|
29
29
|
|
30
30
|
require 'perobs/Log'
|
31
|
+
require 'perobs/RobustFile'
|
31
32
|
require 'perobs/DataBase'
|
32
33
|
require 'perobs/BTreeBlob'
|
33
34
|
|
@@ -114,8 +115,8 @@ module PEROBS
|
|
114
115
|
def put_hash(name, hash)
|
115
116
|
file_name = File.join(@db_dir, name + '.json')
|
116
117
|
begin
|
117
|
-
|
118
|
-
rescue => e
|
118
|
+
RobustFile.write(file_name, hash.to_json)
|
119
|
+
rescue IOError => e
|
119
120
|
PEROBS.log.fatal "Cannot write hash file '#{file_name}': #{e.message}"
|
120
121
|
end
|
121
122
|
end
|
data/lib/perobs/FlatFile.rb
CHANGED
@@ -134,6 +134,9 @@ module PEROBS
|
|
134
134
|
|
135
135
|
# Delete all unmarked objects.
|
136
136
|
def delete_unmarked_objects
|
137
|
+
PEROBS.log.info "Deleting unmarked objects..."
|
138
|
+
t = Time.now
|
139
|
+
|
137
140
|
deleted_ids = []
|
138
141
|
each_blob_header do |pos, mark, length, blob_id, crc|
|
139
142
|
if (mark & 3 == 1)
|
@@ -143,6 +146,8 @@ module PEROBS
|
|
143
146
|
end
|
144
147
|
defragmentize
|
145
148
|
|
149
|
+
PEROBS.log.info "#{deleted_ids.length} unmarked objects deleted " +
|
150
|
+
"in #{Time.now - t} seconds"
|
146
151
|
deleted_ids
|
147
152
|
end
|
148
153
|
|
@@ -274,8 +279,16 @@ module PEROBS
|
|
274
279
|
|
275
280
|
# Clear alls marks.
|
276
281
|
def clear_all_marks
|
282
|
+
t = Time.now
|
283
|
+
PEROBS.log.info "Clearing all marks..."
|
284
|
+
|
285
|
+
total_blob_count = 0
|
286
|
+
marked_blob_count = 0
|
287
|
+
|
277
288
|
each_blob_header do |pos, mark, length, blob_id, crc|
|
289
|
+
total_blob_count += 1
|
278
290
|
if (mark & 1 == 1)
|
291
|
+
marked_blob_count += 1
|
279
292
|
begin
|
280
293
|
@f.seek(pos)
|
281
294
|
@f.write([ mark & 0b11111101 ].pack('C'))
|
@@ -286,6 +299,8 @@ module PEROBS
|
|
286
299
|
end
|
287
300
|
end
|
288
301
|
end
|
302
|
+
PEROBS.log.info "#{marked_blob_count} marks in #{total_blob_count} " +
|
303
|
+
"objects cleared in #{Time.now - t} seconds"
|
289
304
|
end
|
290
305
|
|
291
306
|
# Eliminate all the holes in the file. This is an in-place
|
@@ -339,6 +354,10 @@ module PEROBS
|
|
339
354
|
def check(repair = false)
|
340
355
|
return unless @f
|
341
356
|
|
357
|
+
t = Time.now
|
358
|
+
PEROBS.log.info "Checking FlatFile database" +
|
359
|
+
"#{repair ? ' in repair mode' : ''}..."
|
360
|
+
|
342
361
|
# First check the database blob file. Each entry should be readable and
|
343
362
|
# correct.
|
344
363
|
each_blob_header do |pos, mark, length, blob_id, crc|
|
@@ -369,16 +388,15 @@ module PEROBS
|
|
369
388
|
# and vise versa.
|
370
389
|
begin
|
371
390
|
unless @index.check(self) && @space_list.check(self) &&
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
regenerate_index_and_spaces
|
391
|
+
cross_check_entries
|
392
|
+
regenerate_index_and_spaces if repair
|
376
393
|
end
|
377
394
|
rescue PEROBS::FatalError
|
378
|
-
regenerate_index_and_spaces
|
395
|
+
regenerate_index_and_spaces if repair
|
379
396
|
end
|
380
397
|
|
381
|
-
sync
|
398
|
+
sync if repair
|
399
|
+
PEROBS.log.info "check_db completed in #{Time.now - t} seconds"
|
382
400
|
end
|
383
401
|
|
384
402
|
# This method clears the index tree and the free space list and
|
data/lib/perobs/FlatFileDB.rb
CHANGED
@@ -29,6 +29,7 @@ require 'fileutils'
|
|
29
29
|
require 'zlib'
|
30
30
|
|
31
31
|
require 'perobs/Log'
|
32
|
+
require 'perobs/RobustFile'
|
32
33
|
require 'perobs/DataBase'
|
33
34
|
require 'perobs/FlatFile'
|
34
35
|
|
@@ -102,8 +103,8 @@ module PEROBS
|
|
102
103
|
def put_hash(name, hash)
|
103
104
|
file_name = File.join(@db_dir, name + '.json')
|
104
105
|
begin
|
105
|
-
|
106
|
-
rescue => e
|
106
|
+
RobustFile.write(file_name, hash.to_json)
|
107
|
+
rescue IOError => e
|
107
108
|
PEROBS.log.fatal "Cannot write hash file '#{file_name}': #{e.message}"
|
108
109
|
end
|
109
110
|
end
|
@@ -144,21 +145,14 @@ module PEROBS
|
|
144
145
|
|
145
146
|
# This method must be called to initiate the marking process.
|
146
147
|
def clear_marks
|
147
|
-
t = Time.now
|
148
|
-
PEROBS.log.info "Clearing all marks"
|
149
148
|
@flat_file.clear_all_marks
|
150
|
-
PEROBS.log.info "All marks cleared in #{Time.now - t} seconds"
|
151
149
|
end
|
152
150
|
|
153
151
|
# Permanently delete all objects that have not been marked. Those are
|
154
152
|
# orphaned and are no longer referenced by any actively used object.
|
155
153
|
# @return [Array] List of IDs that have been removed from the DB.
|
156
154
|
def delete_unmarked_objects
|
157
|
-
|
158
|
-
PEROBS.log.info "Deleting unmarked objects"
|
159
|
-
retval = @flat_file.delete_unmarked_objects
|
160
|
-
PEROBS.log.info "Unmarked objects deleted in #{Time.now - t} seconds"
|
161
|
-
retval
|
155
|
+
@flat_file.delete_unmarked_objects
|
162
156
|
end
|
163
157
|
|
164
158
|
# Mark an object.
|
@@ -179,10 +173,7 @@ module PEROBS
|
|
179
173
|
# @param repair [TrueClass/FalseClass] True if found errors should be
|
180
174
|
# repaired.
|
181
175
|
def check_db(repair = false)
|
182
|
-
t = Time.now
|
183
|
-
PEROBS.log.info "check_db started"
|
184
176
|
@flat_file.check(repair)
|
185
|
-
PEROBS.log.info "check_db completed in #{Time.now - t} seconds"
|
186
177
|
end
|
187
178
|
|
188
179
|
# Check if the stored object is syntactically correct.
|
@@ -193,13 +184,11 @@ module PEROBS
|
|
193
184
|
# false.
|
194
185
|
def check(id, repair)
|
195
186
|
begin
|
196
|
-
get_object(id)
|
187
|
+
return get_object(id) != nil
|
197
188
|
rescue => e
|
198
189
|
PEROBS.log.warn "Cannot read object with ID #{id}: #{e.message}"
|
199
190
|
return false
|
200
191
|
end
|
201
|
-
|
202
|
-
true
|
203
192
|
end
|
204
193
|
|
205
194
|
# Store the given serialized object into the cluster files. This method is
|
@@ -236,8 +225,8 @@ module PEROBS
|
|
236
225
|
|
237
226
|
def write_version_file(version_file)
|
238
227
|
begin
|
239
|
-
|
240
|
-
rescue => e
|
228
|
+
RobustFile.write(version_file, VERSION)
|
229
|
+
rescue IOError => e
|
241
230
|
PEROBS.log.fatal "Cannot write version number file " +
|
242
231
|
"'#{version_file}': " + e.message
|
243
232
|
end
|
data/lib/perobs/IndexTree.rb
CHANGED
@@ -25,6 +25,7 @@
|
|
25
25
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
26
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
27
|
|
28
|
+
require 'perobs/Log'
|
28
29
|
require 'perobs/FixedSizeBlobFile'
|
29
30
|
require 'perobs/IndexTreeNode'
|
30
31
|
|
@@ -91,19 +92,24 @@ module PEROBS
|
|
91
92
|
# @param nibble [Fixnum] Index of the nibble the node should correspond to
|
92
93
|
# @param address [Integer] Address of the node in @nodes or nil
|
93
94
|
def get_node(nibble, address = nil)
|
95
|
+
if nibble >= 16
|
96
|
+
# We only support 64 bit keys, so nibble cannot be larger than 15.
|
97
|
+
PEROBS.log.fatal "Nibble must be within 0 - 15 but is #{nibble}"
|
98
|
+
end
|
94
99
|
# Generate a mask for the least significant bits up to and including the
|
95
100
|
# nibble.
|
96
101
|
mask = (2 ** ((1 + nibble) * 4)) - 1
|
97
|
-
if address && (node = @node_cache[address & mask])
|
98
|
-
|
99
|
-
|
100
|
-
else
|
102
|
+
#if address && (node = @node_cache[address & mask])
|
103
|
+
# # We have an address and have found the node in the node cache.
|
104
|
+
# return node
|
105
|
+
#else
|
106
|
+
begin
|
101
107
|
# We don't have a IndexTreeNode object yet for this node. Create it
|
102
108
|
# with the data from the 'database_index' file.
|
103
109
|
node = IndexTreeNode.new(self, nibble, address)
|
104
110
|
# Add the node to the node cache if it's up to MAX_CACHED_LEVEL levels
|
105
111
|
# down from the root.
|
106
|
-
|
112
|
+
#@node_cache[address & mask] = node if nibble <= MAX_CACHED_LEVEL
|
107
113
|
return node
|
108
114
|
end
|
109
115
|
end
|
@@ -112,9 +118,13 @@ module PEROBS
|
|
112
118
|
# @param nibble [Fixnum] The corresponding nibble for the node
|
113
119
|
# @param address [Integer] The address of the node in @nodes
|
114
120
|
def delete_node(nibble, address)
|
121
|
+
if nibble >= 16
|
122
|
+
# We only support 64 bit keys, so nibble cannot be larger than 15.
|
123
|
+
PEROBS.log.fatal "Nibble must be within 0 - 15 but is #{nibble}"
|
124
|
+
end
|
115
125
|
# First delete the node from the node cache.
|
116
126
|
mask = (2 ** ((1 + nibble) * 4)) - 1
|
117
|
-
|
127
|
+
#@node_cache.delete(address & mask)
|
118
128
|
# Then delete it from the 'database_index' file.
|
119
129
|
@nodes.delete_blob(address)
|
120
130
|
end
|
@@ -149,7 +159,7 @@ module PEROBS
|
|
149
159
|
|
150
160
|
# Check if the index is OK and matches the flat_file data.
|
151
161
|
def check(flat_file)
|
152
|
-
@root.check(flat_file)
|
162
|
+
@root.check(flat_file, 0)
|
153
163
|
end
|
154
164
|
|
155
165
|
# Convert the tree into a human readable form.
|
data/lib/perobs/IndexTreeNode.rb
CHANGED
@@ -185,8 +185,14 @@ module PEROBS
|
|
185
185
|
# Recursively check this node and all sub nodes. Compare the found
|
186
186
|
# ID/address pairs with the corresponding entry in the given FlatFile.
|
187
187
|
# @param flat_file [FlatFile]
|
188
|
+
# @tree_level [Fixnum] Assumed level in the tree. Must correspond with
|
189
|
+
# @nibble_idx
|
188
190
|
# @return [Boolean] true if no errors were found, false otherwise
|
189
|
-
def check(flat_file)
|
191
|
+
def check(flat_file, tree_level)
|
192
|
+
if tree_level >= 16
|
193
|
+
PEROBS.log.error "IndexTreeNode level (#{tree_level}) too large"
|
194
|
+
return false
|
195
|
+
end
|
190
196
|
ENTRIES.times do |index|
|
191
197
|
case get_entry_type(index)
|
192
198
|
when 0
|
@@ -205,7 +211,7 @@ module PEROBS
|
|
205
211
|
# The entry is a reference to another node. Just follow it and look
|
206
212
|
# at the next nibble.
|
207
213
|
unless @tree.get_node(@nibble_idx + 1, @entries[index]).
|
208
|
-
check(flat_file)
|
214
|
+
check(flat_file, tree_level + 1)
|
209
215
|
return false
|
210
216
|
end
|
211
217
|
else
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# = RobustFile.rb -- Persistent Ruby Object Store
|
4
|
+
#
|
5
|
+
# Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
|
6
|
+
#
|
7
|
+
# MIT License
|
8
|
+
#
|
9
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
10
|
+
# a copy of this software and associated documentation files (the
|
11
|
+
# "Software"), to deal in the Software without restriction, including
|
12
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
13
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
14
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
15
|
+
# the following conditions:
|
16
|
+
#
|
17
|
+
# The above copyright notice and this permission notice shall be
|
18
|
+
# included in all copies or substantial portions of the Software.
|
19
|
+
#
|
20
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
21
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
22
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
23
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
24
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
25
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
26
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
27
|
+
|
28
|
+
require 'perobs/Log'
|
29
|
+
|
30
|
+
module PEROBS
|
31
|
+
|
32
|
+
class RobustFile
|
33
|
+
|
34
|
+
# This is a more robust version of File::write. It first writes to a
|
35
|
+
# temporary file and them atomically renames the new file to the
|
36
|
+
# potentially existing old file. This should significantly increase our
|
37
|
+
# chances that we never end up with a corrupted file due to problems
|
38
|
+
# during the file write.
|
39
|
+
def RobustFile::write(name, string)
|
40
|
+
tmp_name = name + '.atomic'
|
41
|
+
|
42
|
+
# Ensure that no file with the temporary name exists.
|
43
|
+
if File.exist?(tmp_name)
|
44
|
+
PEROBS.log.warn "Found old temporary file #{tmp_name}"
|
45
|
+
unless File.exist?(name)
|
46
|
+
# If we have a file with the temporary name but no file with the
|
47
|
+
# actual name, rename the temporary file to the actual name. We have
|
48
|
+
# no idea if that temporary file is good enough, but it's better to
|
49
|
+
# have a file with the actual name as backup in case this file write
|
50
|
+
# fails.
|
51
|
+
begin
|
52
|
+
File.rename(tmp_name, name)
|
53
|
+
rescue IOError => e
|
54
|
+
raise IOError "Could not name old temporary file to #{name}: " +
|
55
|
+
e.message
|
56
|
+
end
|
57
|
+
Log.warn "Found old temporary file but no corresponding original file"
|
58
|
+
else
|
59
|
+
# Delete the old temporary file.
|
60
|
+
begin
|
61
|
+
File.delete(tmp_name)
|
62
|
+
rescue IOError => e
|
63
|
+
PEROBS.log.warn "Could not delete old temporary file " +
|
64
|
+
"#{tmp_name}: #{e.message}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Write the temporary file.
|
70
|
+
begin
|
71
|
+
File.write(tmp_name, string)
|
72
|
+
rescue IOError => e
|
73
|
+
raise IOError "Could not write file #{tmp_name}: #{e.message}"
|
74
|
+
end
|
75
|
+
|
76
|
+
# If the temporary file was written successfully we can rename it to the
|
77
|
+
# actual file name and atomically replace the old file if it exists.
|
78
|
+
begin
|
79
|
+
File.rename(tmp_name, name)
|
80
|
+
rescue IOError => e
|
81
|
+
raise IOError "Could not rename #{tmp_name} to #{name}: #{e.message}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
data/lib/perobs/Store.rb
CHANGED
@@ -365,7 +365,8 @@ module PEROBS
|
|
365
365
|
else
|
366
366
|
PEROBS.log.debug "No errors found"
|
367
367
|
end
|
368
|
-
|
368
|
+
# Delete all broken root objects.
|
369
|
+
@root_objects.delete_if { |name, id| !@db.check(id, repair) }
|
369
370
|
|
370
371
|
# Ensure that any fixes are written into the DB.
|
371
372
|
sync
|
@@ -515,13 +516,17 @@ module PEROBS
|
|
515
516
|
end
|
516
517
|
else
|
517
518
|
# Remove references to bad objects.
|
518
|
-
if ref_obj
|
519
|
-
|
520
|
-
"object #{
|
521
|
-
|
519
|
+
if ref_obj
|
520
|
+
if repair
|
521
|
+
PEROBS.log.warn "Eliminating broken reference to object #{id} " +
|
522
|
+
"in object #{ref_obj._id}:\n" + ref_obj.inspect
|
523
|
+
ref_obj._delete_reference_to_id(id)
|
524
|
+
else
|
525
|
+
PEROBS.log.fatal "The following object references a " +
|
526
|
+
"non-existing object #{id}:\n" + ref_obj.inspect
|
527
|
+
end
|
522
528
|
else
|
523
|
-
|
524
|
-
"non-existing object #{id}:\n" + ref_obj.inspect
|
529
|
+
PEROBS.log.warn "Eliminating root object #{id}"
|
525
530
|
end
|
526
531
|
errors += 1
|
527
532
|
end
|
data/lib/perobs/TreeDB.rb
CHANGED
@@ -28,6 +28,7 @@
|
|
28
28
|
require 'fileutils'
|
29
29
|
|
30
30
|
require 'perobs/Log'
|
31
|
+
require 'perobs/RobustFile'
|
31
32
|
require 'perobs/DataBase'
|
32
33
|
require 'perobs/BTreeBlob'
|
33
34
|
|
@@ -114,8 +115,8 @@ module PEROBS
|
|
114
115
|
def put_hash(name, hash)
|
115
116
|
file_name = File.join(@db_dir, name + '.json')
|
116
117
|
begin
|
117
|
-
|
118
|
-
rescue => e
|
118
|
+
RobustFile.write(file_name, hash.to_json)
|
119
|
+
rescue IOError => e
|
119
120
|
PEROBS.log.fatal "Cannot write hash file '#{file_name}': #{e.message}"
|
120
121
|
end
|
121
122
|
end
|
data/lib/perobs/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perobs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.4.
|
4
|
+
version: 2.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Schlaeger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-02-
|
11
|
+
date: 2017-02-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -83,6 +83,7 @@ files:
|
|
83
83
|
- lib/perobs/Log.rb
|
84
84
|
- lib/perobs/Object.rb
|
85
85
|
- lib/perobs/ObjectBase.rb
|
86
|
+
- lib/perobs/RobustFile.rb
|
86
87
|
- lib/perobs/StackFile.rb
|
87
88
|
- lib/perobs/Store.rb
|
88
89
|
- lib/perobs/TreeDB.rb
|