fsdb 0.7.1 → 0.7.2

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.
data/History.txt CHANGED
@@ -1,3 +1,15 @@
1
+ fsdb 0.7.2
2
+
3
+ - rename lock_shared and lock_exclusive by appending _fsdb to avoid conflicts
4
+ - require thread for 1.8.7 compat
5
+ - added fixture example
6
+ - fetch clears cache entry to prevent increasing memory use
7
+ - fix possible race condition in insert
8
+ - use db cache_mutex instead of Thread.exclusive.
9
+ - minor api doc changes
10
+ - stop using literal character notation, for clarity
11
+ - updated README with recent benchmarks
12
+
1
13
  fsdb 0.7.1
2
14
 
3
15
  - added a require needed for ruby 1.8
data/README.markdown CHANGED
@@ -291,17 +291,17 @@ FSDB is not very fast. It's useful more for its safety, flexibility, and ease of
291
291
  or FSDB dump/load methods that use a faster format (e.g., plain text, rather
292
292
  than a marshalled String), this may not be so bad.
293
293
 
294
- - On an 850MHz PIII under linux, with debugging turned off (-b option),
294
+ - On an 1.3GHz Core 2 Duo under linux, with debugging turned off (-b option),
295
295
  test-concurrency.rb reports:
296
296
 
297
297
  processes | threads | objects | transactions per cpu second
298
298
  --------- | --------- | --------- | ---------------------------
299
- 1 | 1 | 10 | 965
300
- 1 | 10 | 10 | 165
301
- 10 | 1 | 10 | 684
302
- 10 | 10 | 10 | 122
303
- 10 | 10 | 100 | 100
304
- 10 | 10 | 10000 | 92
299
+ 1 | 1 | 10 | 4798
300
+ 1 | 10 | 10 | 3537
301
+ 10 | 1 | 10 | 4231
302
+ 10 | 10 | 10 | 4093
303
+ 10 | 10 | 100 | 4060
304
+ 10 | 10 | 10000 | 3700
305
305
 
306
306
  These results are not representative of typical applications, because the
307
307
  test was designed to stress the database and expose stability problems, not
@@ -521,22 +521,6 @@ is:
521
521
 
522
522
  ### Performance
523
523
 
524
- - Profiling says that Thread.exclusive consumes about 20% of cpu. Also,
525
- Thread.stop and Thread.run when there are multiple threads. Using
526
- Thread.critical in places where it is safe to do so (no exceptions raised)
527
- instead of Thread.exclusive would reduce this to an estimated 6%.
528
- ((See faster-modex .rb and faster-mutex.rb.))
529
-
530
- - Better way of waiting for file lock in the multithread case
531
-
532
- - this may be unfixable until ruby has native threads
533
-
534
- - Use shared memory for the cache, so write is not necessary after edit.
535
-
536
- - actually, this may not make much sense
537
-
538
- - Option for Database to ignore file locking and possibility of other writers.
539
-
540
524
  - fetch could use the cache better if the cache kept the file contents string
541
525
  as well as the loaded object. Then the #stale! call would only have to
542
526
  wipe the reference to the object, and could leave the contents string. But
Binary file
@@ -0,0 +1,45 @@
1
+ require 'fsdb'
2
+ require 'tmpdir'
3
+
4
+ data = FSDB::Database.new "data"
5
+
6
+ formats = [
7
+ FSDB::TEXT_FORMAT.when(/\.txt$|\.html$/),
8
+ FSDB::BINARY_FORMAT.when(/\.png$/)
9
+ ]
10
+
11
+ data.formats = formats
12
+
13
+ Dir.mktmpdir do |dir|
14
+ #dir = "tmp1" # for testing, use a dir that won't be deleted
15
+
16
+ tmp = FSDB::Database.new(dir)
17
+ tmp.formats = formats
18
+
19
+ tmp["hello.txt"] = "contents of hello.txt"
20
+ tmp["web/snippet.html"] = "<h1>Fix this!</h1>"
21
+
22
+ img = tmp.subdb("img")
23
+ img["hello.png"] = data["hello.png"]
24
+ img["hi.png"] = data["hello.png"]
25
+ end
26
+
27
+ # you can automatically serialize objects in yaml, json, or marshal:
28
+ require 'yaml'
29
+ require 'json'
30
+
31
+ Dir.mktmpdir do |dir|
32
+ #dir = "tmp2" # for testing, use a dir that won't be deleted
33
+
34
+ tmp = FSDB::Database.new(dir)
35
+ json_format = FSDB::Format.new(
36
+ /\.json$/, /\.js$/,
37
+ :name => "JSON_FORMAT",
38
+ :load => proc {|f| JSON.load(f)},
39
+ :dump => proc {|object, f| f.syswrite(JSON.dump(object))}
40
+ )
41
+ tmp.formats = formats + [FSDB::YAML_FORMAT, json_format]
42
+
43
+ tmp["a.yaml"] = {:some => ["complex", Object]}
44
+ tmp["b.json"] = ["foo", 2, 3]
45
+ end
data/lib/fsdb/database.rb CHANGED
@@ -85,12 +85,14 @@ class Database
85
85
  @object = object
86
86
  end
87
87
 
88
- def start_using; Thread.exclusive {@users += 1}; end
89
- def stop_using; Thread.exclusive {@users -= 1}; end
88
+ # called in context of lock on db's cache_mutex.
89
+ def start_using; @users += 1; end
90
90
 
91
- # called in context of lock on db's cache_mutex, which is also
92
- # required for start_using.
93
- def unused?; @users == 0; end
91
+ # called in context of lock on db's cache_mutex.
92
+ def stop_using; @users -= 1; end
93
+
94
+ # called in context of lock on db's cache_mutex.
95
+ def unused?; @users == 0; end
94
96
 
95
97
  # Protects object during #browse, #edit, and so on. Should be locked
96
98
  # as long as the object is being used. It's ok to lock the @mutex
@@ -160,6 +162,8 @@ class Database
160
162
 
161
163
  # Create a new database object that accesses +path+ relative to the database
162
164
  # directory. A process can have any number of dbs accessing overlapping dirs.
165
+ # The FSDB concurrency protections apply to a file regardless of which db
166
+ # is used to access it.
163
167
  # The cost of creating an additional db is very low; its state is just the
164
168
  # dir and some options. Caching is done in structures owned by the Database
165
169
  # class itself.
@@ -174,10 +178,11 @@ class Database
174
178
  def inspect; "#<#{self.class}:#{dir}>"; end
175
179
 
176
180
  # Convert a relative path (relative to the db dir) to an absolute path.
181
+ # A directory path will have '/' appended to it.
177
182
  def absolute(path)
178
183
  abs_path = File.expand_path(File.join(@dir, path))
179
184
  if File.directory?(abs_path)
180
- abs_path << ?/ # prevent Errno::EINVAL on UFS
185
+ abs_path << "/" # prevent Errno::EINVAL on UFS
181
186
  end
182
187
  abs_path
183
188
  end
@@ -346,7 +351,11 @@ private
346
351
  end
347
352
  yield cache_entry
348
353
  ensure
349
- cache_entry.stop_using if cache_entry
354
+ if cache_entry
355
+ cache_mutex.synchronize do
356
+ cache_entry.stop_using
357
+ end
358
+ end
350
359
  end
351
360
 
352
361
  # Lock path for shared (read) use. Other threads will wait to modify it.
@@ -376,7 +385,7 @@ private
376
385
  rescue Errno::EINTR
377
386
  retry
378
387
  else
379
- f.lock_shared(@lock_type)
388
+ f.lock_shared_fsdb(@lock_type)
380
389
  identify_file_type(f, path, abs_path)
381
390
  yield f
382
391
  ensure
@@ -398,7 +407,7 @@ private
398
407
  rescue Errno::EINTR
399
408
  retry
400
409
  else
401
- f.lock_exclusive(@lock_type)
410
+ f.lock_exclusive_fsdb(@lock_type)
402
411
  identify_file_type(f, path, abs_path)
403
412
  yield f
404
413
  ensure
@@ -493,7 +502,7 @@ public
493
502
  end
494
503
 
495
504
  cache_entry.file_handle = f
496
- f.lock_shared(@lock_type)
505
+ f.lock_shared_fsdb(@lock_type)
497
506
  identify_file_type(f, path, abs_path)
498
507
  ## could avoid if cache_object says so
499
508
  object = cache_object(f, cache_entry)
@@ -615,18 +624,14 @@ public
615
624
  # Insert the object, replacing anything at the path. Returns the object.
616
625
  # (The object remains a <i>local copy</i>, distinct from the one which will be
617
626
  # returned when accessing the path through database transactions.)
618
- #
619
- # If +path+ ends in "/", then object is treated as a collection of key-value
620
- # pairs, and each value is inserted at the corresponding key under +path+.
621
- # (You can omit the "/" if the dir already exists.)
622
- ### is this still true?
623
627
  def insert(path, object)
624
628
  abs_path = absolute(path)
625
629
  file_id = make_file_id(abs_path)
626
630
  object_exclusive file_id do |cache_entry|
627
631
  open_write_lock(path) do |f|
628
632
  dump(object, f)
629
- cache_entry.update(f.mtime, inc_version_of(f, cache_entry), object)
633
+ inc_version_of(f, cache_entry)
634
+ cache_entry.stale!
630
635
  object
631
636
  end
632
637
  end
@@ -647,7 +652,7 @@ public
647
652
  rescue MissingFileError
648
653
  raise DirIsImmutableError if PLATFORM_IS_WINDOWS_ME
649
654
  ensure
650
- clear_entry(file_id) # no one else can get this copy of object
655
+ clear_entry(file_id) # the entry was recently marked stale anyway
651
656
  end
652
657
  alias []= insert
653
658
 
@@ -750,6 +755,8 @@ public
750
755
  raise unless File.directory?(abs_path)
751
756
  # on some platforms, opening a dir raises EACCESS
752
757
  return Formats::DIR_LOAD_FROM_PATH[abs_path]
758
+ ensure
759
+ clear_entry(file_id) # the entry was recently marked stale anyway
753
760
  end
754
761
  alias [] fetch
755
762
 
@@ -10,17 +10,17 @@ class File
10
10
  if FSDB::PLATFORM_IS_WINDOWS_ME
11
11
  # no flock() on WinME
12
12
 
13
- def lock_exclusive lock_type # :nodoc
13
+ def lock_exclusive_fsdb lock_type # :nodoc
14
14
  end
15
15
 
16
- def lock_shared lock_type # :nodoc
16
+ def lock_shared_fsdb lock_type # :nodoc
17
17
  end
18
18
 
19
19
  else
20
20
  # Get an exclusive (i.e., write) lock on the file.
21
21
  # If the lock is not available, wait for it without blocking other ruby
22
22
  # threads.
23
- def lock_exclusive lock_type
23
+ def lock_exclusive_fsdb lock_type
24
24
  send(lock_type, LOCK_EX)
25
25
  rescue Errno::EINTR
26
26
  retry
@@ -29,7 +29,7 @@ class File
29
29
  # Get a shared (i.e., read) lock on the file.
30
30
  # If the lock is not available, wait for it without blocking other ruby
31
31
  # threads.
32
- def lock_shared lock_type
32
+ def lock_shared_fsdb lock_type
33
33
  send(lock_type, LOCK_SH)
34
34
  rescue Errno::EINTR
35
35
  retry
data/lib/fsdb/modex.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'thread'
2
+
1
3
  module FSDB
2
4
 
3
5
  # Modex is a modal exclusion semaphore.
@@ -27,7 +27,7 @@ module Persistent
27
27
  persistent_mutex.synchronize do
28
28
  File.makedirs(File.dirname(persistent_file))
29
29
  File.open(persistent_file, "wb") do |f|
30
- f.lock_exclusive do
30
+ f.lock_exclusive_fsdb do
31
31
  dump(f)
32
32
  yield self if block_given?
33
33
  end
@@ -50,7 +50,7 @@ module Persistent
50
50
  # a particular object, or there will be multiple copies.
51
51
  def restore file
52
52
  object = File.open(file, "rb") do |f|
53
- f.lock_shared do
53
+ f.lock_shared_fsdb do
54
54
  load(f)
55
55
  end
56
56
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fsdb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-12-06 00:00:00.000000000 Z
12
+ date: 2012-05-20 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: ! 'A file system data base. Provides a thread-safe, process-safe Database
15
15
  class.
@@ -30,6 +30,8 @@ files:
30
30
  - README.markdown
31
31
  - bench/bench.rb
32
32
  - examples/indexes.rb
33
+ - examples/fixture.rb
34
+ - examples/data/hello.png
33
35
  - examples/rbformat.rb
34
36
  - examples/formats.rb
35
37
  - examples/yaml.rb
@@ -88,8 +90,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
88
90
  version: '0'
89
91
  requirements: []
90
92
  rubyforge_project: fsdb
91
- rubygems_version: 1.8.11
93
+ rubygems_version: 1.8.24
92
94
  signing_key:
93
95
  specification_version: 3
94
96
  summary: File System Database
95
97
  test_files: []
98
+ has_rdoc: