fsdb 0.5 → 0.6.0
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/{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
|