perobs 2.4.0 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|