fsdb 0.7.1 → 0.7.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +12 -0
- data/README.markdown +7 -23
- data/examples/data/hello.png +0 -0
- data/examples/fixture.rb +45 -0
- data/lib/fsdb/database.rb +24 -17
- data/lib/fsdb/file-lock.rb +4 -4
- data/lib/fsdb/modex.rb +2 -0
- data/lib/fsdb/persistent.rb +2 -2
- metadata +6 -3
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
|
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 |
|
300
|
-
1 | 10 | 10 |
|
301
|
-
10 | 1 | 10 |
|
302
|
-
10 | 10 | 10 |
|
303
|
-
10 | 10 | 100 |
|
304
|
-
10 | 10 | 10000 |
|
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
|
data/examples/fixture.rb
ADDED
@@ -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
|
-
|
89
|
-
def
|
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
|
92
|
-
|
93
|
-
|
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 <<
|
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
|
-
|
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.
|
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.
|
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.
|
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
|
-
|
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) #
|
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
|
|
data/lib/fsdb/file-lock.rb
CHANGED
@@ -10,17 +10,17 @@ class File
|
|
10
10
|
if FSDB::PLATFORM_IS_WINDOWS_ME
|
11
11
|
# no flock() on WinME
|
12
12
|
|
13
|
-
def
|
13
|
+
def lock_exclusive_fsdb lock_type # :nodoc
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
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
|
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
|
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
data/lib/fsdb/persistent.rb
CHANGED
@@ -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.
|
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.
|
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.
|
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:
|
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.
|
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:
|