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 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