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.
Files changed (176) hide show
  1. data/{RELEASE-NOTES → History.txt} +6 -0
  2. data/{README → README.txt} +26 -17
  3. data/examples/flat.rb +146 -0
  4. data/examples/fsdb-example.rb +28 -0
  5. data/examples/rbformat.rb +17 -0
  6. data/examples/yaml2.rb +29 -0
  7. data/junk/OLDRakefile +98 -0
  8. data/junk/OLDRakefile2 +55 -0
  9. data/junk/check-cache.rb +18 -0
  10. data/junk/create-lock.rb +25 -0
  11. data/junk/doc/old-api/classes/FSDB.html +139 -0
  12. data/junk/doc/old-api/classes/FSDB/Database.html +953 -0
  13. data/junk/doc/old-api/classes/FSDB/Database.src/M000029.html +16 -0
  14. data/junk/doc/old-api/classes/FSDB/Database.src/M000030.html +16 -0
  15. data/junk/doc/old-api/classes/FSDB/Database.src/M000031.html +16 -0
  16. data/junk/doc/old-api/classes/FSDB/Database.src/M000032.html +16 -0
  17. data/junk/doc/old-api/classes/FSDB/Database.src/M000033.html +33 -0
  18. data/junk/doc/old-api/classes/FSDB/Database.src/M000034.html +18 -0
  19. data/junk/doc/old-api/classes/FSDB/Database.src/M000035.html +22 -0
  20. data/junk/doc/old-api/classes/FSDB/Database.src/M000036.html +16 -0
  21. data/junk/doc/old-api/classes/FSDB/Database.src/M000037.html +22 -0
  22. data/junk/doc/old-api/classes/FSDB/Database.src/M000038.html +43 -0
  23. data/junk/doc/old-api/classes/FSDB/Database.src/M000039.html +25 -0
  24. data/junk/doc/old-api/classes/FSDB/Database.src/M000040.html +43 -0
  25. data/junk/doc/old-api/classes/FSDB/Database.src/M000041.html +23 -0
  26. data/junk/doc/old-api/classes/FSDB/Database.src/M000042.html +22 -0
  27. data/junk/doc/old-api/classes/FSDB/Database.src/M000043.html +16 -0
  28. data/junk/doc/old-api/classes/FSDB/Database.src/M000044.html +16 -0
  29. data/junk/doc/old-api/classes/FSDB/Database.src/M000045.html +18 -0
  30. data/junk/doc/old-api/classes/FSDB/Database.src/M000046.html +18 -0
  31. data/junk/doc/old-api/classes/FSDB/Database.src/M000047.html +18 -0
  32. data/junk/doc/old-api/classes/FSDB/Database.src/M000048.html +16 -0
  33. data/junk/doc/old-api/classes/FSDB/Database.src/M000049.html +71 -0
  34. data/junk/doc/old-api/classes/FSDB/Database.src/M000050.html +43 -0
  35. data/junk/doc/old-api/classes/FSDB/Database.src/M000051.html +53 -0
  36. data/junk/doc/old-api/classes/FSDB/Database.src/M000052.html +44 -0
  37. data/junk/doc/old-api/classes/FSDB/Database.src/M000053.html +39 -0
  38. data/junk/doc/old-api/classes/FSDB/Database.src/M000054.html +72 -0
  39. data/junk/doc/old-api/classes/FSDB/Database.src/M000055.html +39 -0
  40. data/junk/doc/old-api/classes/FSDB/Database.src/M000056.html +18 -0
  41. data/junk/doc/old-api/classes/FSDB/Database.src/M000057.html +18 -0
  42. data/junk/doc/old-api/classes/FSDB/Database.src/M000058.html +18 -0
  43. data/junk/doc/old-api/classes/FSDB/Database.src/M000059.html +18 -0
  44. data/junk/doc/old-api/classes/FSDB/Database.src/M000060.html +18 -0
  45. data/junk/doc/old-api/classes/FSDB/Database.src/M000061.html +23 -0
  46. data/junk/doc/old-api/classes/FSDB/Database.src/M000062.html +23 -0
  47. data/junk/doc/old-api/classes/FSDB/Database.src/M000063.html +18 -0
  48. data/junk/doc/old-api/classes/FSDB/Database.src/M000064.html +18 -0
  49. data/junk/doc/old-api/classes/FSDB/Database/AbortedTransaction.html +118 -0
  50. data/junk/doc/old-api/classes/FSDB/Database/CreateFileError.html +120 -0
  51. data/junk/doc/old-api/classes/FSDB/Database/DirIsImmutableError.html +117 -0
  52. data/junk/doc/old-api/classes/FSDB/Database/DirNotEmptyError.html +117 -0
  53. data/junk/doc/old-api/classes/FSDB/Database/FormatError.html +117 -0
  54. data/junk/doc/old-api/classes/FSDB/Database/MissingFileError.html +117 -0
  55. data/junk/doc/old-api/classes/FSDB/Database/MissingObjectError.html +117 -0
  56. data/junk/doc/old-api/classes/FSDB/Database/NotDirError.html +118 -0
  57. data/junk/doc/old-api/classes/FSDB/Database/PathComponentError.html +120 -0
  58. data/junk/doc/old-api/classes/FSDB/DatabaseDebuggable.html +148 -0
  59. data/junk/doc/old-api/classes/FSDB/DatabaseDebuggable.src/M000005.html +21 -0
  60. data/junk/doc/old-api/classes/FSDB/DatabaseDebuggable.src/M000007.html +21 -0
  61. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.html +210 -0
  62. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000006.html +22 -0
  63. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000007.html +22 -0
  64. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000008.html +22 -0
  65. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000009.html +22 -0
  66. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000010.html +22 -0
  67. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000011.html +22 -0
  68. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000012.html +22 -0
  69. data/junk/doc/old-api/classes/FSDB/DirectoryIterators.src/M000013.html +22 -0
  70. data/junk/doc/old-api/classes/FSDB/ForkSafely.html +126 -0
  71. data/junk/doc/old-api/classes/FSDB/Modex.html +237 -0
  72. data/junk/doc/old-api/classes/FSDB/Modex.src/M000024.html +21 -0
  73. data/junk/doc/old-api/classes/FSDB/Modex.src/M000025.html +30 -0
  74. data/junk/doc/old-api/classes/FSDB/Modex.src/M000026.html +21 -0
  75. data/junk/doc/old-api/classes/FSDB/Modex.src/M000027.html +30 -0
  76. data/junk/doc/old-api/classes/FSDB/Modex.src/M000028.html +44 -0
  77. data/junk/doc/old-api/classes/FSDB/Modex.src/M000029.html +26 -0
  78. data/junk/doc/old-api/classes/FSDB/Modex.src/M000030.html +48 -0
  79. data/junk/doc/old-api/classes/FSDB/Modex/ForkSafely.html +105 -0
  80. data/junk/doc/old-api/classes/FSDB/Mutex.html +244 -0
  81. data/junk/doc/old-api/classes/FSDB/Mutex.src/M000018.html +19 -0
  82. data/junk/doc/old-api/classes/FSDB/Mutex.src/M000019.html +18 -0
  83. data/junk/doc/old-api/classes/FSDB/Mutex.src/M000020.html +19 -0
  84. data/junk/doc/old-api/classes/FSDB/Mutex.src/M000021.html +18 -0
  85. data/junk/doc/old-api/classes/FSDB/Mutex.src/M000022.html +23 -0
  86. data/junk/doc/old-api/classes/FSDB/Mutex.src/M000023.html +30 -0
  87. data/junk/doc/old-api/classes/FSDB/Mutex.src/M000024.html +26 -0
  88. data/junk/doc/old-api/classes/FSDB/Mutex.src/M000025.html +21 -0
  89. data/junk/doc/old-api/classes/FSDB/Mutex/ForkSafely.html +105 -0
  90. data/junk/doc/old-api/classes/FSDB/PathUtilities.html +257 -0
  91. data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000012.html +23 -0
  92. data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000013.html +18 -0
  93. data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000014.html +23 -0
  94. data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000015.html +18 -0
  95. data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000016.html +18 -0
  96. data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000017.html +22 -0
  97. data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000018.html +23 -0
  98. data/junk/doc/old-api/classes/FSDB/PathUtilities.src/M000019.html +18 -0
  99. data/junk/doc/old-api/classes/FSDB/PathUtilities/InvalidPathError.html +111 -0
  100. data/junk/doc/old-api/classes/File.html +272 -0
  101. data/junk/doc/old-api/classes/File.src/M000001.html +17 -0
  102. data/junk/doc/old-api/classes/File.src/M000002.html +17 -0
  103. data/junk/doc/old-api/classes/File.src/M000003.html +20 -0
  104. data/junk/doc/old-api/classes/File.src/M000004.html +20 -0
  105. data/junk/doc/old-api/classes/File.src/M000005.html +32 -0
  106. data/junk/doc/old-api/classes/File.src/M000006.html +32 -0
  107. data/junk/doc/old-api/created.rid +1 -0
  108. data/junk/doc/old-api/files/README.html +112 -0
  109. data/junk/doc/old-api/files/RELEASE-NOTES.html +233 -0
  110. data/junk/doc/old-api/files/fsdb_txt.html +888 -0
  111. data/junk/doc/old-api/files/lib/fsdb/database_rb.html +115 -0
  112. data/junk/doc/old-api/files/lib/fsdb/file-lock_rb.html +109 -0
  113. data/junk/doc/old-api/files/lib/fsdb/modex_rb.html +121 -0
  114. data/junk/doc/old-api/files/lib/fsdb/mutex_rb.html +108 -0
  115. data/junk/doc/old-api/files/lib/fsdb/util_rb.html +108 -0
  116. data/junk/doc/old-api/fr_class_index.html +47 -0
  117. data/junk/doc/old-api/fr_file_index.html +34 -0
  118. data/junk/doc/old-api/fr_method_index.html +90 -0
  119. data/junk/doc/old-api/index.html +24 -0
  120. data/junk/doc/old-api/rdoc-style.css +208 -0
  121. data/junk/file-lock-nb.rb +15 -0
  122. data/junk/fl.rb +144 -0
  123. data/junk/flock-test.rb +39 -0
  124. data/junk/fsdb.kateproject +47 -0
  125. data/junk/fsdb.prj +196 -0
  126. data/junk/fsdb.sf +46 -0
  127. data/junk/insert-dir.rb +48 -0
  128. data/junk/lock-test-bug.rb +150 -0
  129. data/junk/lock-test-too-simple.rb +136 -0
  130. data/junk/lock-test.rb +151 -0
  131. data/{script → junk}/mkrdoc +0 -0
  132. data/junk/restore-fsdb.rb +37 -0
  133. data/junk/rf.txt +5 -0
  134. data/junk/solaris-bug-fixed.rb +184 -0
  135. data/junk/solaris-bug.rb +182 -0
  136. data/junk/solaris-bug.txt +43 -0
  137. data/junk/sync.rb +327 -0
  138. data/junk/test-file-lock.html +86 -0
  139. data/junk/test-file-lock.rb +84 -0
  140. data/junk/test-processes.rb +131 -0
  141. data/junk/test-threads.rb +113 -0
  142. data/junk/wiki-mutex.rb +108 -0
  143. data/lib/fsdb/database.rb +5 -3
  144. data/lib/fsdb/delegatable.rb +21 -0
  145. data/lib/fsdb/faster-modex.rb +223 -0
  146. data/lib/fsdb/faster-mutex.rb +138 -0
  147. data/lib/fsdb/mutex.rb +4 -1
  148. data/lib/fsdb/persistent.rb +91 -0
  149. data/lib/fsdb/read-write-object.rb +36 -0
  150. data/lib/fsdb/server.rb +44 -0
  151. data/misc/fsdb-blorubu.txt +47 -0
  152. data/misc/mtime-and-file-id.txt +23 -0
  153. data/misc/posixlock.txt +148 -0
  154. data/rakefile +39 -0
  155. data/tasks/ann.rake +80 -0
  156. data/tasks/bones.rake +20 -0
  157. data/tasks/gem.rake +201 -0
  158. data/tasks/git.rake +40 -0
  159. data/tasks/notes.rake +27 -0
  160. data/tasks/post_load.rake +34 -0
  161. data/tasks/rdoc.rake +51 -0
  162. data/tasks/rubyforge.rake +55 -0
  163. data/tasks/setup.rb +292 -0
  164. data/tasks/spec.rake +54 -0
  165. data/tasks/svn.rake +47 -0
  166. data/tasks/test.rake +40 -0
  167. data/tasks/zentest.rake +36 -0
  168. data/test/err.txt +31 -0
  169. data/test/runs.rb +8 -0
  170. data/test/test-file-lock.rb +78 -0
  171. data/test/test-util.rb +1 -0
  172. data/test/trap.rb +31 -0
  173. metadata +198 -35
  174. data/Manifest +0 -36
  175. data/Rakefile +0 -10
  176. 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
@@ -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 'ftools'
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
- File.makedirs(@dir)
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
- File.makedirs(dirname)
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