fsdb 0.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{RELEASE-NOTES → History.txt} +6 -0
- data/{README → README.txt} +26 -17
- data/examples/flat.rb +146 -0
- data/examples/fsdb-example.rb +28 -0
- data/examples/rbformat.rb +17 -0
- data/examples/yaml2.rb +29 -0
- data/junk/OLDRakefile +98 -0
- data/junk/OLDRakefile2 +55 -0
- data/junk/check-cache.rb +18 -0
- data/junk/create-lock.rb +25 -0
- data/junk/doc/old-api/classes/FSDB.html +139 -0
- data/junk/doc/old-api/classes/FSDB/Database.html +953 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000029.html +16 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000030.html +16 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000031.html +16 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000032.html +16 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000033.html +33 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000034.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000035.html +22 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000036.html +16 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000037.html +22 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000038.html +43 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000039.html +25 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000040.html +43 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000041.html +23 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000042.html +22 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000043.html +16 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000044.html +16 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000045.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000046.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000047.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000048.html +16 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000049.html +71 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000050.html +43 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000051.html +53 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000052.html +44 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000053.html +39 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000054.html +72 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000055.html +39 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000056.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000057.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000058.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000059.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000060.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000061.html +23 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000062.html +23 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000063.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database.src/M000064.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Database/AbortedTransaction.html +118 -0
- data/junk/doc/old-api/classes/FSDB/Database/CreateFileError.html +120 -0
- data/junk/doc/old-api/classes/FSDB/Database/DirIsImmutableError.html +117 -0
- data/junk/doc/old-api/classes/FSDB/Database/DirNotEmptyError.html +117 -0
- data/junk/doc/old-api/classes/FSDB/Database/FormatError.html +117 -0
- data/junk/doc/old-api/classes/FSDB/Database/MissingFileError.html +117 -0
- data/junk/doc/old-api/classes/FSDB/Database/MissingObjectError.html +117 -0
- data/junk/doc/old-api/classes/FSDB/Database/NotDirError.html +118 -0
- data/junk/doc/old-api/classes/FSDB/Database/PathComponentError.html +120 -0
- data/junk/doc/old-api/classes/FSDB/DatabaseDebuggable.html +148 -0
- data/junk/doc/old-api/classes/FSDB/DatabaseDebuggable.src/M000005.html +21 -0
- data/junk/doc/old-api/classes/FSDB/DatabaseDebuggable.src/M000007.html +21 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.html +210 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000006.html +22 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000007.html +22 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000008.html +22 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000009.html +22 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000010.html +22 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000011.html +22 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000012.html +22 -0
- data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000013.html +22 -0
- data/junk/doc/old-api/classes/FSDB/ForkSafely.html +126 -0
- data/junk/doc/old-api/classes/FSDB/Modex.html +237 -0
- data/junk/doc/old-api/classes/FSDB/Modex.src/M000024.html +21 -0
- data/junk/doc/old-api/classes/FSDB/Modex.src/M000025.html +30 -0
- data/junk/doc/old-api/classes/FSDB/Modex.src/M000026.html +21 -0
- data/junk/doc/old-api/classes/FSDB/Modex.src/M000027.html +30 -0
- data/junk/doc/old-api/classes/FSDB/Modex.src/M000028.html +44 -0
- data/junk/doc/old-api/classes/FSDB/Modex.src/M000029.html +26 -0
- data/junk/doc/old-api/classes/FSDB/Modex.src/M000030.html +48 -0
- data/junk/doc/old-api/classes/FSDB/Modex/ForkSafely.html +105 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.html +244 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.src/M000018.html +19 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.src/M000019.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.src/M000020.html +19 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.src/M000021.html +18 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.src/M000022.html +23 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.src/M000023.html +30 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.src/M000024.html +26 -0
- data/junk/doc/old-api/classes/FSDB/Mutex.src/M000025.html +21 -0
- data/junk/doc/old-api/classes/FSDB/Mutex/ForkSafely.html +105 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.html +257 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000012.html +23 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000013.html +18 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000014.html +23 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000015.html +18 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000016.html +18 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000017.html +22 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000018.html +23 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000019.html +18 -0
- data/junk/doc/old-api/classes/FSDB/PathUtilities/InvalidPathError.html +111 -0
- data/junk/doc/old-api/classes/File.html +272 -0
- data/junk/doc/old-api/classes/File.src/M000001.html +17 -0
- data/junk/doc/old-api/classes/File.src/M000002.html +17 -0
- data/junk/doc/old-api/classes/File.src/M000003.html +20 -0
- data/junk/doc/old-api/classes/File.src/M000004.html +20 -0
- data/junk/doc/old-api/classes/File.src/M000005.html +32 -0
- data/junk/doc/old-api/classes/File.src/M000006.html +32 -0
- data/junk/doc/old-api/created.rid +1 -0
- data/junk/doc/old-api/files/README.html +112 -0
- data/junk/doc/old-api/files/RELEASE-NOTES.html +233 -0
- data/junk/doc/old-api/files/fsdb_txt.html +888 -0
- data/junk/doc/old-api/files/lib/fsdb/database_rb.html +115 -0
- data/junk/doc/old-api/files/lib/fsdb/file-lock_rb.html +109 -0
- data/junk/doc/old-api/files/lib/fsdb/modex_rb.html +121 -0
- data/junk/doc/old-api/files/lib/fsdb/mutex_rb.html +108 -0
- data/junk/doc/old-api/files/lib/fsdb/util_rb.html +108 -0
- data/junk/doc/old-api/fr_class_index.html +47 -0
- data/junk/doc/old-api/fr_file_index.html +34 -0
- data/junk/doc/old-api/fr_method_index.html +90 -0
- data/junk/doc/old-api/index.html +24 -0
- data/junk/doc/old-api/rdoc-style.css +208 -0
- data/junk/file-lock-nb.rb +15 -0
- data/junk/fl.rb +144 -0
- data/junk/flock-test.rb +39 -0
- data/junk/fsdb.kateproject +47 -0
- data/junk/fsdb.prj +196 -0
- data/junk/fsdb.sf +46 -0
- data/junk/insert-dir.rb +48 -0
- data/junk/lock-test-bug.rb +150 -0
- data/junk/lock-test-too-simple.rb +136 -0
- data/junk/lock-test.rb +151 -0
- data/{script → junk}/mkrdoc +0 -0
- data/junk/restore-fsdb.rb +37 -0
- data/junk/rf.txt +5 -0
- data/junk/solaris-bug-fixed.rb +184 -0
- data/junk/solaris-bug.rb +182 -0
- data/junk/solaris-bug.txt +43 -0
- data/junk/sync.rb +327 -0
- data/junk/test-file-lock.html +86 -0
- data/junk/test-file-lock.rb +84 -0
- data/junk/test-processes.rb +131 -0
- data/junk/test-threads.rb +113 -0
- data/junk/wiki-mutex.rb +108 -0
- data/lib/fsdb/database.rb +5 -3
- data/lib/fsdb/delegatable.rb +21 -0
- data/lib/fsdb/faster-modex.rb +223 -0
- data/lib/fsdb/faster-mutex.rb +138 -0
- data/lib/fsdb/mutex.rb +4 -1
- data/lib/fsdb/persistent.rb +91 -0
- data/lib/fsdb/read-write-object.rb +36 -0
- data/lib/fsdb/server.rb +44 -0
- data/misc/fsdb-blorubu.txt +47 -0
- data/misc/mtime-and-file-id.txt +23 -0
- data/misc/posixlock.txt +148 -0
- data/rakefile +39 -0
- data/tasks/ann.rake +80 -0
- data/tasks/bones.rake +20 -0
- data/tasks/gem.rake +201 -0
- data/tasks/git.rake +40 -0
- data/tasks/notes.rake +27 -0
- data/tasks/post_load.rake +34 -0
- data/tasks/rdoc.rake +51 -0
- data/tasks/rubyforge.rake +55 -0
- data/tasks/setup.rb +292 -0
- data/tasks/spec.rake +54 -0
- data/tasks/svn.rake +47 -0
- data/tasks/test.rake +40 -0
- data/tasks/zentest.rake +36 -0
- data/test/err.txt +31 -0
- data/test/runs.rb +8 -0
- data/test/test-file-lock.rb +78 -0
- data/test/test-util.rb +1 -0
- data/test/trap.rb +31 -0
- metadata +198 -35
- data/Manifest +0 -36
- data/Rakefile +0 -10
- data/fsdb.gemspec +0 -113
@@ -0,0 +1,131 @@
|
|
1
|
+
require './test.rb'
|
2
|
+
|
3
|
+
## should also test nested transactions, replace, insert, delete, etc...
|
4
|
+
module TestStressProcesses
|
5
|
+
|
6
|
+
class ProcessTester
|
7
|
+
attr_accessor :x
|
8
|
+
def initialize
|
9
|
+
@x = 0
|
10
|
+
end
|
11
|
+
def inspect
|
12
|
+
"<ProcessTester:@x=#{@x}>"
|
13
|
+
end
|
14
|
+
def to_s
|
15
|
+
inspect
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.go
|
20
|
+
|
21
|
+
db = $db
|
22
|
+
|
23
|
+
object_count = 5
|
24
|
+
process_count = 10
|
25
|
+
rep_count = 1000
|
26
|
+
max_sleep = 0.01
|
27
|
+
|
28
|
+
paths = (0...object_count).map {|i| "thread-test/t#{i}"}
|
29
|
+
|
30
|
+
paths.each do |path|
|
31
|
+
db[path] = ProcessTester.new
|
32
|
+
end
|
33
|
+
|
34
|
+
processes = (0...process_count).map do
|
35
|
+
fork do
|
36
|
+
srand(Process.pid)
|
37
|
+
|
38
|
+
rep_count.times do |iter|
|
39
|
+
path = paths[rand(paths.size)]
|
40
|
+
|
41
|
+
case rand(100)
|
42
|
+
|
43
|
+
when 0..49
|
44
|
+
db.browse path do |tester|
|
45
|
+
__x__ = tester.x
|
46
|
+
|
47
|
+
data = File.read(File.join(db.dir, path))
|
48
|
+
uncached_x = Marshal.load(data).x
|
49
|
+
unless uncached_x == __x__
|
50
|
+
fail "browse test: cache is stale:\n" +
|
51
|
+
" #{uncached_x} on disk, #{__x__} in cache"
|
52
|
+
end
|
53
|
+
|
54
|
+
sleep(rand*max_sleep)
|
55
|
+
|
56
|
+
unless __x__ == tester.x
|
57
|
+
fail "browse test: x == #{__x__} but tester.x == #{tester.x}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
when 50..69
|
62
|
+
db.edit path do |tester|
|
63
|
+
__x__ = tester.x
|
64
|
+
|
65
|
+
data = File.read(File.join(db.dir, path))
|
66
|
+
uncached_x = Marshal.load(data).x
|
67
|
+
unless uncached_x == __x__
|
68
|
+
fail "edit test: cache is stale:\n" +
|
69
|
+
" #{uncached_x} on disk, #{__x__} in cache"
|
70
|
+
end
|
71
|
+
|
72
|
+
tester.x += 1
|
73
|
+
__x__ = tester.x
|
74
|
+
|
75
|
+
sleep(rand*max_sleep)
|
76
|
+
|
77
|
+
unless __x__ == tester.x
|
78
|
+
fail "edit test: x == #{__x__} but tester.x == #{tester.x}"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
when 70..90
|
83
|
+
db.replace path do |tester|
|
84
|
+
__x__ = tester.x
|
85
|
+
|
86
|
+
data = File.read(File.join(db.dir, path))
|
87
|
+
uncached_x = Marshal.load(data).x
|
88
|
+
unless uncached_x == __x__
|
89
|
+
fail "replace test: cache is stale:\n" +
|
90
|
+
" #{uncached_x} on disk, #{__x__} in cache"
|
91
|
+
end
|
92
|
+
|
93
|
+
tester.x += 1
|
94
|
+
__x__ = tester.x
|
95
|
+
|
96
|
+
sleep(rand*max_sleep)
|
97
|
+
|
98
|
+
unless __x__ == tester.x
|
99
|
+
fail "replace test: x == #{__x__} but tester.x == #{tester.x}"
|
100
|
+
end
|
101
|
+
|
102
|
+
tester
|
103
|
+
end
|
104
|
+
|
105
|
+
else
|
106
|
+
sleep(rand*max_sleep)
|
107
|
+
db.clear_cache
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
thread = Thread.new do
|
116
|
+
i = 0
|
117
|
+
loop do
|
118
|
+
puts i
|
119
|
+
sleep 1
|
120
|
+
i += 1
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
process_count.times {Process.wait}
|
125
|
+
thread.kill
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
TestStressProcesses.go if __FILE__ == $0
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require './test.rb'
|
2
|
+
|
3
|
+
## should also test nested transactions, replace, insert, delete, etc...
|
4
|
+
module TestStressThreads
|
5
|
+
|
6
|
+
class ThreadTester
|
7
|
+
attr_accessor :x
|
8
|
+
def initialize
|
9
|
+
@x = 0
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.go
|
14
|
+
|
15
|
+
db = $db
|
16
|
+
|
17
|
+
srand(3765)
|
18
|
+
|
19
|
+
object_count = 100
|
20
|
+
thread_count = 100
|
21
|
+
rep_count = 1000
|
22
|
+
max_sleep = nil
|
23
|
+
|
24
|
+
paths = (0...object_count).map {|i| "thread-test/t#{i}"}
|
25
|
+
|
26
|
+
paths.each do |path|
|
27
|
+
db[path] = ThreadTester.new
|
28
|
+
end
|
29
|
+
|
30
|
+
threads = (0...thread_count).map do |thread_index|
|
31
|
+
Thread.new(thread_index) do |ti|
|
32
|
+
Thread.current[:number] = ti
|
33
|
+
Thread.current[:transactions] = 0
|
34
|
+
rep_count.times do |iter|
|
35
|
+
Thread.current[:iter] = iter
|
36
|
+
path = paths[rand(paths.size)]
|
37
|
+
|
38
|
+
case rand(3)
|
39
|
+
|
40
|
+
when 0
|
41
|
+
db.browse path do |tester|
|
42
|
+
__x__ = tester.x
|
43
|
+
sleep(rand*max_sleep) if max_sleep
|
44
|
+
unless __x__ == tester.x
|
45
|
+
fail "browse: x == #{__x__} but tester.x == #{tester.x}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
Thread.current[:transactions] += 1
|
49
|
+
|
50
|
+
when 1
|
51
|
+
db.edit path do |tester|
|
52
|
+
tester.x += 1
|
53
|
+
__x__ = tester.x
|
54
|
+
sleep(rand*max_sleep) if max_sleep
|
55
|
+
unless __x__ == tester.x
|
56
|
+
fail "edit: x == #{__x__} but tester.x == #{tester.x}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
Thread.current[:transactions] += 1
|
60
|
+
|
61
|
+
else
|
62
|
+
sleep(rand*max_sleep) if max_sleep
|
63
|
+
if rand(20) == 0
|
64
|
+
db.clear_cache
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
start_time = Process.times
|
74
|
+
|
75
|
+
# threads.each{|t|printf "%6d", t[:number]}
|
76
|
+
# puts
|
77
|
+
# puts "-----|" * threads.size
|
78
|
+
sleep 1
|
79
|
+
|
80
|
+
# stats = old_stats = nil
|
81
|
+
loop do
|
82
|
+
# old_stats = stats
|
83
|
+
# stats = threads.map{|t|t[:iter]}
|
84
|
+
# stats.each{|s|printf "%6d", s}
|
85
|
+
puts "."
|
86
|
+
break unless threads.any?{|t|t.alive?}
|
87
|
+
# if old_stats == stats
|
88
|
+
# fail "Deadlock!"
|
89
|
+
# end
|
90
|
+
sleep 1
|
91
|
+
end
|
92
|
+
|
93
|
+
end_time = Process.times
|
94
|
+
|
95
|
+
utime = end_time.utime - start_time.utime
|
96
|
+
stime = end_time.stime - start_time.stime
|
97
|
+
rate = threads.inject(0) {|sum, t| sum += t[:transactions]} /
|
98
|
+
(utime + stime)
|
99
|
+
|
100
|
+
puts "All db worker threads dead."
|
101
|
+
puts "time: %12s%12s" % %w{user system}
|
102
|
+
puts " %12.2f%12.2f" % [utime, stime]
|
103
|
+
puts "\nExecuted %d transactions per second" % rate
|
104
|
+
|
105
|
+
threads.each do |thread|
|
106
|
+
thread.join
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
TestStressThreads.go
|
data/junk/wiki-mutex.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
# Mutex class based on standard thread.rb Mutex, which has some problems:
|
6
|
+
# - waiters are not a strict queue (try_lock can jump the queue, after
|
7
|
+
# which the queue gets *rotated*). Race condition.
|
8
|
+
# - doesn't use Thread.exclusive in enough places
|
9
|
+
# - no way to make dead threads give up the mutex, which is crucial in a fork
|
10
|
+
class ForkableMutex
|
11
|
+
def initialize
|
12
|
+
@waiting = [] # queue of waiting threads
|
13
|
+
@locked = nil; # the thread that has the lock
|
14
|
+
@waiting.taint # enable tainted comunication
|
15
|
+
self.taint
|
16
|
+
end
|
17
|
+
|
18
|
+
def locked?
|
19
|
+
@locked
|
20
|
+
end
|
21
|
+
|
22
|
+
def try_lock
|
23
|
+
Thread.exclusive do
|
24
|
+
if not @locked and @waiting.empty?
|
25
|
+
@locked = Thread.current
|
26
|
+
true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def lock
|
32
|
+
thread = Thread.current
|
33
|
+
Thread.exclusive do
|
34
|
+
if @locked
|
35
|
+
@waiting.push thread
|
36
|
+
Thread.stop
|
37
|
+
unless @locked == thread
|
38
|
+
raise ThreadError, "queue was jumped"
|
39
|
+
end
|
40
|
+
else
|
41
|
+
@locked = thread
|
42
|
+
end
|
43
|
+
self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def unlock
|
48
|
+
return unless @locked
|
49
|
+
|
50
|
+
t = Thread.exclusive { wake_next_waiter }
|
51
|
+
|
52
|
+
begin
|
53
|
+
t.run if t
|
54
|
+
rescue ThreadError
|
55
|
+
end
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def synchronize
|
60
|
+
lock
|
61
|
+
yield
|
62
|
+
ensure
|
63
|
+
unlock
|
64
|
+
end
|
65
|
+
|
66
|
+
def remove_dead
|
67
|
+
Thread.exclusive do
|
68
|
+
@waiting = @waiting.select {|t| t.alive?}
|
69
|
+
wake_next_waiter if @locked and not @locked.alive?
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def wake_next_waiter
|
75
|
+
t = @waiting.shift
|
76
|
+
t.wakeup if t
|
77
|
+
@locked = t
|
78
|
+
rescue ThreadError
|
79
|
+
retry
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
module ForkSafely
|
84
|
+
def fork
|
85
|
+
super do
|
86
|
+
ObjectSpace.each_object(ForkableMutex) { |m| m.remove_dead }
|
87
|
+
yield
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
include ForkSafely
|
92
|
+
|
93
|
+
|
94
|
+
if __FILE__ == $0
|
95
|
+
include ForkSafely
|
96
|
+
|
97
|
+
m = ForkableMutex.new
|
98
|
+
|
99
|
+
Thread.new { m.synchronize { sleep 2 } }
|
100
|
+
|
101
|
+
fork do
|
102
|
+
m.synchronize { puts "Didn't get here if you used standard mutex or fork." }
|
103
|
+
end
|
104
|
+
|
105
|
+
m.synchronize { puts "Got here." }
|
106
|
+
|
107
|
+
Process.wait
|
108
|
+
end
|
data/lib/fsdb/database.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'fileutils'
|
2
2
|
require 'fsdb/platform'
|
3
3
|
require 'fsdb/compat' if RUBY_VERSION.to_f < 1.7
|
4
4
|
require 'fsdb/mutex'
|
@@ -9,6 +9,8 @@ require 'fsdb/formats'
|
|
9
9
|
module FSDB
|
10
10
|
include Formats
|
11
11
|
|
12
|
+
FSDB::VERSION = "0.6.0"
|
13
|
+
|
12
14
|
# A thread-safe, process-safe object database class which uses the
|
13
15
|
# native file system as its back end and allows multiple file formats.
|
14
16
|
|
@@ -158,7 +160,7 @@ class Database
|
|
158
160
|
|
159
161
|
@formats = opts[:formats]
|
160
162
|
|
161
|
-
|
163
|
+
FileUtils.makedirs(@dir)
|
162
164
|
end
|
163
165
|
|
164
166
|
# Shortcut to create a new database at +path+.
|
@@ -236,7 +238,7 @@ class Database
|
|
236
238
|
def make_file_id(abs_path)
|
237
239
|
dirname = File.dirname(abs_path)
|
238
240
|
begin
|
239
|
-
|
241
|
+
FileUtils.makedirs(dirname)
|
240
242
|
rescue Errno::EEXIST
|
241
243
|
raise PathComponentError
|
242
244
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Delegatable
|
2
|
+
def delegate h
|
3
|
+
h.each do |meth, reader|
|
4
|
+
module_eval %{
|
5
|
+
def #{meth}(*args, &block); #{reader}.#{meth}(*args, &block); end
|
6
|
+
}
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def browse(&block); @db.browse(path, &block); end
|
12
|
+
def edit(&block); @db.edit(path, &block); end
|
13
|
+
def replace(&block); @db.replace(path, &block); end
|
14
|
+
def insert(&block); @db.insert(path, &block); end
|
15
|
+
def delete(&block); @db.delete(path, &block); end
|
16
|
+
def fetch(&block); @db.fetch(path, &block); end
|
17
|
+
|
18
|
+
extend Delegatable
|
19
|
+
delegate :browse => "@db", :edit => "@db", :replace => "@db",
|
20
|
+
:insert => "@db", :delegate => "@db", :fetch => "@db"
|
21
|
+
|
@@ -0,0 +1,223 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
### only use this if you can ensure Thread.critical is not already set!
|
4
|
+
|
5
|
+
# Make sure we use the fast definition, not the thread.rb one!
|
6
|
+
class Thread # :nodoc:
|
7
|
+
def self.exclusive
|
8
|
+
old = critical
|
9
|
+
self.critical = true
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
self.critical = old
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Thread
|
17
|
+
def self.nonexclusive
|
18
|
+
old = critical
|
19
|
+
self.critical = false
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
self.critical = old
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
module FSDB
|
27
|
+
|
28
|
+
# Modex is a modal exclusion semaphore, like in syncronizer.rb.
|
29
|
+
# The two modes are shared (SH) and exclusive (EX).
|
30
|
+
# Modex is not nestable.
|
31
|
+
#
|
32
|
+
class Modex
|
33
|
+
SH = :SH
|
34
|
+
EX = :EX
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@waiting = []
|
38
|
+
@locked = []
|
39
|
+
@mode = nil
|
40
|
+
@first = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def try_lock mode
|
44
|
+
Thread.critical = true
|
45
|
+
thread = Thread.current
|
46
|
+
if @locked.include?(thread)
|
47
|
+
Thread.critical = false
|
48
|
+
raise ThreadError
|
49
|
+
end
|
50
|
+
|
51
|
+
if @mode == mode and mode == SH and @waiting.empty? # strict queue
|
52
|
+
@locked << thread
|
53
|
+
rslt = true
|
54
|
+
elsif not @mode
|
55
|
+
@mode = mode
|
56
|
+
@locked << thread
|
57
|
+
rslt = true
|
58
|
+
end
|
59
|
+
Thread.critical = false
|
60
|
+
rslt
|
61
|
+
end
|
62
|
+
|
63
|
+
# the block is executed in the exclusive context
|
64
|
+
def lock mode
|
65
|
+
Thread.critical = true
|
66
|
+
thread = Thread.current
|
67
|
+
if @locked.include?(thread)
|
68
|
+
Thread.critical = false
|
69
|
+
raise ThreadError
|
70
|
+
end
|
71
|
+
|
72
|
+
if @mode == mode and mode == SH and @waiting.empty? # strict queue
|
73
|
+
@locked << thread
|
74
|
+
elsif not @mode
|
75
|
+
@mode = mode
|
76
|
+
@locked << thread
|
77
|
+
else
|
78
|
+
@waiting << thread << mode
|
79
|
+
Thread.stop
|
80
|
+
Thread.critical = true
|
81
|
+
end
|
82
|
+
|
83
|
+
yield if block_given?
|
84
|
+
|
85
|
+
# if @mode != mode
|
86
|
+
# raise "@mode == #{@mode} but mode == #{mode}"
|
87
|
+
# end
|
88
|
+
#
|
89
|
+
# if @mode == EX and @locked.size > 1
|
90
|
+
# raise "@mode == EX but @locked.size == #{@locked.size}"
|
91
|
+
# end
|
92
|
+
|
93
|
+
Thread.critical = false
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
# the block is executed in the exclusive context
|
98
|
+
def unlock
|
99
|
+
raise ThreadError unless @mode
|
100
|
+
|
101
|
+
Thread.critical = true
|
102
|
+
yield if block_given?
|
103
|
+
@locked.delete Thread.current
|
104
|
+
wake_next_waiter if @locked.empty?
|
105
|
+
Thread.critical = false
|
106
|
+
|
107
|
+
self
|
108
|
+
end
|
109
|
+
|
110
|
+
def synchronize mode, do_when_first = nil, do_when_last = nil, arg = nil
|
111
|
+
lock mode do
|
112
|
+
if @first
|
113
|
+
@first = false
|
114
|
+
|
115
|
+
if do_when_first
|
116
|
+
if mode == SH
|
117
|
+
@mode = EX
|
118
|
+
end
|
119
|
+
|
120
|
+
Thread.critical = false; do_when_first[arg]; Thread.critical = true
|
121
|
+
|
122
|
+
if mode == SH
|
123
|
+
@mode = SH
|
124
|
+
wake_waiting_sharers
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
yield
|
131
|
+
|
132
|
+
ensure
|
133
|
+
unlock do
|
134
|
+
if @locked.size == 1
|
135
|
+
if do_when_last
|
136
|
+
@mode = EX
|
137
|
+
Thread.critical = false; do_when_last[arg]; Thread.critical = true
|
138
|
+
end
|
139
|
+
@first = true
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def remove_dead # :nodoc:
|
145
|
+
Thread.exclusive do
|
146
|
+
waiting = @waiting; @waiting = []
|
147
|
+
until waiting.empty?
|
148
|
+
|
149
|
+
t = waiting.shift; m = waiting.shift
|
150
|
+
@waiting << t << m if t.alive?
|
151
|
+
end
|
152
|
+
|
153
|
+
@locked = @locked.select {|t| t.alive?}
|
154
|
+
wake_next_waiter if @locked.empty?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
def wake_next_waiter
|
160
|
+
first = @waiting.shift; @mode = @waiting.shift && EX
|
161
|
+
if first
|
162
|
+
first.wakeup
|
163
|
+
@locked << first
|
164
|
+
end
|
165
|
+
first
|
166
|
+
rescue ThreadError
|
167
|
+
retry
|
168
|
+
end
|
169
|
+
|
170
|
+
def wake_waiting_sharers
|
171
|
+
while @waiting[1] == SH # note strict queue order
|
172
|
+
t = @waiting.shift; @waiting.shift
|
173
|
+
@locked << t
|
174
|
+
t.wakeup
|
175
|
+
end
|
176
|
+
rescue ThreadError
|
177
|
+
retry
|
178
|
+
end
|
179
|
+
|
180
|
+
module ForkSafely
|
181
|
+
def fork # :nodoc:
|
182
|
+
super do
|
183
|
+
ObjectSpace.each_object(Modex) { |m| m.remove_dead }
|
184
|
+
yield
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
# FSDB users who fork should include ForkSafely or FSDB itself. (The reason for
|
191
|
+
# this is that the fork may inherit some dead threads from the parent, and if
|
192
|
+
# they hold any locks, you may get a deadlock. ForkSafely modifies fork so that
|
193
|
+
# these dead threads are cleared. If you use modexes (outside of those in FSDB),
|
194
|
+
# they should be FSDB::Modexes.
|
195
|
+
module ForkSafely
|
196
|
+
include Modex::ForkSafely
|
197
|
+
end
|
198
|
+
include ForkSafely
|
199
|
+
|
200
|
+
end # module FSDB
|
201
|
+
|
202
|
+
|
203
|
+
if __FILE__ == $0
|
204
|
+
# Stress test is in fsdb/test/test-modex.rb. This is just to show fork usage.
|
205
|
+
|
206
|
+
include FSDB::ForkSafely
|
207
|
+
|
208
|
+
m = FSDB::Modex.new
|
209
|
+
|
210
|
+
SH = FSDB::Modex::SH
|
211
|
+
|
212
|
+
Thread.new { m.synchronize(SH) { sleep 1 } }
|
213
|
+
|
214
|
+
fork do
|
215
|
+
m.synchronize(SH) do
|
216
|
+
puts "Didn't get here if you used standard mutex or fork."
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
m.synchronize(SH) { puts "Got here." }
|
221
|
+
|
222
|
+
Process.wait
|
223
|
+
end
|