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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2cb73944d6f6f8968d868d89e19e229dfecd5fc8
4
- data.tar.gz: d9c7598cd06bd8b88678eaec6f71f494eb1a0c1e
3
+ metadata.gz: 3580a4c1778350e195656c42ca100c651ec18386
4
+ data.tar.gz: e3c964bb3f135372dd07ffc7b36c108f27990569
5
5
  SHA512:
6
- metadata.gz: befb35a3bc54b7f261889bd7b6495aaaa51c75f50cbb91fef4a657fda323bacb8080f91934062d2ae28e751ab23bc6130ec033fd802d54ae4c67a86b092442a0
7
- data.tar.gz: 013e98cd4fe0fce2063ac8a204a33152f1e96c8e22786870046a66a6aa5d6d941444c3b7caaff319404d5d59a0ad2449fec5638266b883e833833a76131b90b2
6
+ metadata.gz: 6c12c89af79a909c65346fe8ea35e2de608dbba0ce4eb85be35b56bd07a5c50858cd5773ab94ee8e6dd3c8b75c1a872ac7400a0bbc78927c715954c337a7288e
7
+ data.tar.gz: f93ee4dfb7552724b78f14e682a0750e53c7984910c588050e9dc92b1d2571c35ea8ff7a5fb095ef6b537a238d15da4bb7d31a6126d1ec7aeea1d89d19138dda
@@ -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
@@ -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
- File.write(file_name, hash.to_json)
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
@@ -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
- cross_check_entries
373
- return unless repair
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
@@ -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
- File.write(file_name, hash.to_json)
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
- t = Time.now
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
- File.write(version_file, "#{VERSION}")
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
@@ -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
- # We have an address and have found the node in the node cache.
99
- return node
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
- @node_cache[address & mask] = node if nibble <= MAX_CACHED_LEVEL
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
- @node_cache.delete(address & mask)
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.
@@ -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
- @root_objects.delete_if { |name, id| !@db.check(id, false) }
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 && repair
519
- PEROBS.log.warn "Fixing broken reference to object #{id} in " +
520
- "object #{ref_obj._id}:\n" + ref_obj.inspect
521
- ref_obj._delete_reference_to_id(id)
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
- PEROBS.log.fatal "The following object references a " +
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
- File.write(file_name, hash.to_json)
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
@@ -1,4 +1,4 @@
1
1
  module PEROBS
2
2
  # The version number
3
- VERSION = "2.4.0"
3
+ VERSION = "2.4.1"
4
4
  end
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.0
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-08 00:00:00.000000000 Z
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