fsdb 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -117,7 +117,7 @@ end
117
117
  # this is like in test-concurrency.rb -- generalize?
118
118
  def cleanup dir
119
119
  @db.browse_dir dir do |child_path|
120
- if child_path[-1] == ?/ ## ugh!
120
+ if child_path =~ /\/$/ ## ugh!
121
121
  cleanup(child_path)
122
122
  else
123
123
  @db.delete(child_path)
@@ -0,0 +1,43 @@
1
+ require 'fsdb'
2
+
3
+ db = FSDB::Database.new '/tmp/fsdb-example'
4
+
5
+ db['foo.txt'] = "hello, world"
6
+ puts db['foo.txt']
7
+ puts
8
+
9
+ p1 = fork do
10
+ puts "*** starting fork #1 at #{Time.now}"
11
+ db.edit 'foo.txt' do |str|
12
+ str << "\n edited by fork #1 at #{Time.now}"
13
+ sleep 2 # give fork #2 a chance to try editing
14
+ str << "\n done editing in fork #1 at #{Time.now}"
15
+ end
16
+ end
17
+
18
+ sleep 1
19
+
20
+ p2 = fork do
21
+ puts "*** starting fork #2 at #{Time.now}"
22
+ db.edit 'foo.txt' do |str|
23
+ str << "\n edited by fork #2 at #{Time.now}"
24
+ str << "\n done editing in fork #2 at #{Time.now}"
25
+ end
26
+ end
27
+
28
+ Process.waitpid p1
29
+ Process.waitpid p2
30
+
31
+ puts db['foo.txt']
32
+
33
+ __END__
34
+
35
+ hello, world
36
+
37
+ *** starting fork #1 at Tue Nov 29 11:03:03 -0800 2011
38
+ *** starting fork #2 at Tue Nov 29 11:03:04 -0800 2011
39
+ hello, world
40
+ edited by fork #1 at Tue Nov 29 11:03:03 -0800 2011
41
+ done editing in fork #1 at Tue Nov 29 11:03:05 -0800 2011
42
+ edited by fork #2 at Tue Nov 29 11:03:05 -0800 2011
43
+ done editing in fork #2 at Tue Nov 29 11:03:05 -0800 2011
@@ -1,7 +1,5 @@
1
1
  require 'fileutils'
2
2
  require 'fsdb/platform'
3
- require 'fsdb/compat' if RUBY_VERSION.to_f < 1.7
4
- require 'fsdb/mutex'
5
3
  require 'fsdb/modex'
6
4
  require 'fsdb/file-lock'
7
5
  require 'fsdb/formats'
@@ -9,7 +7,7 @@ require 'fsdb/formats'
9
7
  module FSDB
10
8
  include Formats
11
9
 
12
- FSDB::VERSION = "0.6.1"
10
+ FSDB::VERSION = "0.7.0"
13
11
 
14
12
  # A thread-safe, process-safe object database class which uses the
15
13
  # native file system as its back end and allows multiple file formats.
@@ -108,14 +106,10 @@ class Database
108
106
 
109
107
  # Subclasses can change the defaults.
110
108
  DEFAULT_META_PREFIX = '..fsdb.meta.'
111
- ### if RUBY_PLATFORM =~ /darwin/
112
- ### DEFAULT_LOCK_TYPE = :fcntl_lock
113
- ### else
114
- DEFAULT_LOCK_TYPE = :flock
115
- ### end
109
+ DEFAULT_LOCK_TYPE = :flock
116
110
 
117
111
  # These must be methods of File.
118
- LOCK_TYPES = [:flock, :fcntl_lock]
112
+ LOCK_TYPES = [:flock] # obsolete: :fcntl_lock
119
113
 
120
114
  @cache = {} # maps <file id> to <CacheEntry>
121
115
  @cache_mutex = Mutex.new # protects access to @cache hash
@@ -130,15 +124,14 @@ class Database
130
124
  # The root directory of the db, to which paths are relative.
131
125
  attr_reader :dir
132
126
 
133
- # The lock type of the db, by default <tt>:flock</tt>, optionally
134
- # <tt>:fcntl_lock</tt>.
127
+ # The lock type of the db, by default <tt>:flock</tt>.
135
128
  attr_reader :lock_type
136
129
 
137
130
  # Create a new database object that accesses +dir+. Makes sure that the
138
131
  # directory exists on disk, but doesn't create or open any other files.
139
132
  # The +opts+ hash can include:
140
133
  #
141
- # <tt>:lock_type</tt>:: <tt>:flock</tt> by default, or <tt>:fcntl_lock</tt>
134
+ # <tt>:lock_type</tt>:: <tt>:flock</tt> by default
142
135
  #
143
136
  # <tt>:meta_prefix</tt>:: <tt>'..fsdb.meta.'</tt> by default
144
137
  #
@@ -152,10 +145,6 @@ class Database
152
145
  raise "Unknown lock type: #{lock_type}"
153
146
  end
154
147
 
155
- if @lock_type == :fcntl_lock
156
- require 'fcntl_lock' ## hack.
157
- end
158
-
159
148
  @meta_prefix = opts[:meta_prefix] || DEFAULT_META_PREFIX
160
149
 
161
150
  @formats = opts[:formats]
@@ -7,10 +7,6 @@ class File
7
7
  CAN_DELETE_OPEN_FILE = !FSDB::PLATFORM_IS_WINDOWS
8
8
  CAN_OPEN_DIR = !FSDB::PLATFORM_IS_WINDOWS
9
9
 
10
- LOCK_BLOCK_FIXED_VER = "1.8.2" # Hurray!
11
- LOCK_DOESNT_BLOCK = [RUBY_VERSION, LOCK_BLOCK_FIXED_VER].
12
- map {|s| s.split('.')}.sort[0].join('.') == LOCK_BLOCK_FIXED_VER
13
-
14
10
  if FSDB::PLATFORM_IS_WINDOWS_ME
15
11
  # no flock() on WinME
16
12
 
@@ -21,61 +17,22 @@ class File
21
17
  end
22
18
 
23
19
  else
24
- if LOCK_DOESNT_BLOCK
25
- # Get an exclusive (i.e., write) lock on the file.
26
- # If the lock is not available, wait for it without blocking other ruby
27
- # threads.
28
- def lock_exclusive lock_type
29
- send(lock_type, LOCK_EX)
30
- rescue Errno::EINTR
31
- retry
32
- end
33
-
34
- # Get a shared (i.e., read) lock on the file.
35
- # If the lock is not available, wait for it without blocking other ruby
36
- # threads.
37
- def lock_shared lock_type
38
- send(lock_type, LOCK_SH)
39
- rescue Errno::EINTR
40
- retry
41
- end
42
-
43
- else
44
- def lock_exclusive lock_type
45
- if Thread.list.size == 1
46
- begin
47
- send(lock_type, LOCK_EX)
48
- rescue Errno::EINTR
49
- retry
50
- end
51
- else
52
- # ugly hack because waiting for a lock in a Ruby thread blocks the
53
- # entire process
54
- period = 0.001
55
- until send(lock_type, LOCK_EX|LOCK_NB)
56
- sleep period
57
- period *= 2 if period < 1
58
- end
59
- end
60
- end
20
+ # Get an exclusive (i.e., write) lock on the file.
21
+ # If the lock is not available, wait for it without blocking other ruby
22
+ # threads.
23
+ def lock_exclusive lock_type
24
+ send(lock_type, LOCK_EX)
25
+ rescue Errno::EINTR
26
+ retry
27
+ end
61
28
 
62
- def lock_shared lock_type
63
- if Thread.list.size == 1
64
- begin
65
- send(lock_type, LOCK_SH)
66
- rescue Errno::EINTR
67
- retry
68
- end
69
- else
70
- # ugly hack because waiting for a lock in a Ruby thread blocks the
71
- # entire process
72
- period = 0.001
73
- until send(lock_type, LOCK_SH|LOCK_NB)
74
- sleep period
75
- period *= 2 if period < 1
76
- end
77
- end
78
- end
29
+ # Get a shared (i.e., read) lock on the file.
30
+ # If the lock is not available, wait for it without blocking other ruby
31
+ # threads.
32
+ def lock_shared lock_type
33
+ send(lock_type, LOCK_SH)
34
+ rescue Errno::EINTR
35
+ retry
79
36
  end
80
37
  end
81
38
 
@@ -1,30 +1,6 @@
1
- #!/usr/bin/env ruby
2
-
3
- unless defined? Thread.exclusive
4
- class Thread # :nodoc:
5
- def self.exclusive
6
- old = critical
7
- self.critical = true
8
- yield
9
- ensure
10
- self.critical = old
11
- end
12
- end
13
- end
14
-
15
- class Thread
16
- def self.nonexclusive
17
- old = critical
18
- self.critical = false
19
- yield
20
- ensure
21
- self.critical = old
22
- end
23
- end
24
-
25
1
  module FSDB
26
2
 
27
- # Modex is a modal exclusion semaphore, like in syncronizer.rb.
3
+ # Modex is a modal exclusion semaphore.
28
4
  # The two modes are shared (SH) and exclusive (EX).
29
5
  # Modex is not nestable.
30
6
  #
@@ -37,12 +13,13 @@ class Modex
37
13
  @locked = []
38
14
  @mode = nil
39
15
  @first = true
16
+ @m = Mutex.new
40
17
  end
41
18
 
42
19
  def try_lock mode
43
- Thread.exclusive do
20
+ @m.synchronize do
44
21
  thread = Thread.current
45
- raise ThreadError if @locked.include?(thread)
22
+ raise ThreadError, "nesting not allowed" if @locked.include?(thread)
46
23
 
47
24
  if @mode == mode and mode == SH and @waiting.empty? # strict queue
48
25
  @locked << thread
@@ -51,15 +28,17 @@ class Modex
51
28
  @mode = mode
52
29
  @locked << thread
53
30
  true
31
+ else
32
+ false
54
33
  end
55
34
  end
56
35
  end
57
36
 
58
37
  # the block is executed in the exclusive context
59
38
  def lock mode
60
- Thread.exclusive do
39
+ @m.synchronize do
61
40
  thread = Thread.current
62
- raise ThreadError if @locked.include?(thread)
41
+ raise ThreadError, "nesting not allowed" if @locked.include?(thread)
63
42
 
64
43
  if @mode == mode and mode == SH and @waiting.empty? # strict queue
65
44
  @locked << thread
@@ -68,8 +47,9 @@ class Modex
68
47
  @locked << thread
69
48
  else
70
49
  @waiting << thread << mode
50
+ @m.unlock
71
51
  Thread.stop
72
- Thread.critical = true
52
+ @m.lock
73
53
  end
74
54
 
75
55
  yield if block_given?
@@ -88,11 +68,20 @@ class Modex
88
68
 
89
69
  # the block is executed in the exclusive context
90
70
  def unlock
91
- raise ThreadError unless @mode
71
+ raise ThreadError, "already unlocked" unless @mode
92
72
 
93
- Thread.exclusive do
94
- yield if block_given?
95
- @locked.delete Thread.current
73
+ @m.synchronize do
74
+ if block_given?
75
+ begin
76
+ yield
77
+ ensure
78
+ @locked.delete Thread.current
79
+ end
80
+
81
+ else
82
+ @locked.delete Thread.current
83
+ end
84
+
96
85
  wake_next_waiter if @locked.empty?
97
86
  end
98
87
 
@@ -109,7 +98,7 @@ class Modex
109
98
  @mode = EX
110
99
  end
111
100
 
112
- Thread.nonexclusive { do_when_first[arg] }
101
+ nonexclusive { do_when_first[arg] }
113
102
 
114
103
  if mode == SH
115
104
  @mode = SH
@@ -126,7 +115,7 @@ class Modex
126
115
  if @locked.size == 1
127
116
  if do_when_last
128
117
  @mode = EX
129
- Thread.nonexclusive { do_when_last[arg] }
118
+ nonexclusive { do_when_last[arg] }
130
119
  end
131
120
  @first = true
132
121
  end
@@ -134,7 +123,7 @@ class Modex
134
123
  end
135
124
 
136
125
  def remove_dead # :nodoc:
137
- Thread.exclusive do
126
+ @m.synchronize do
138
127
  waiting = @waiting; @waiting = []
139
128
  until waiting.empty?
140
129
 
@@ -148,13 +137,21 @@ class Modex
148
137
  end
149
138
 
150
139
  private
140
+ def nonexclusive
141
+ raise ThreadError unless @m.locked?
142
+ @m.unlock
143
+ yield
144
+ ensure
145
+ @m.lock
146
+ end
147
+
151
148
  def wake_next_waiter
152
- first = @waiting.shift; @mode = @waiting.shift && EX
153
- if first
154
- first.wakeup
155
- @locked << first
149
+ first_waiter = @waiting.shift; @mode = @waiting.shift && EX
150
+ if first_waiter
151
+ first_waiter.wakeup
152
+ @locked << first_waiter
156
153
  end
157
- first
154
+ first_waiter
158
155
  rescue ThreadError
159
156
  retry
160
157
  end
@@ -295,7 +295,7 @@ class ConcurrencyTest
295
295
 
296
296
  @total_iters = process_count * thread_count * rep_count
297
297
 
298
- unless quiet or not $defout.isatty
298
+ unless quiet or not $stdout.isatty
299
299
  monitor_thread = Thread.new do
300
300
  loop do
301
301
  print "\r#{status_meter}"
@@ -356,7 +356,7 @@ class ConcurrencyTest
356
356
 
357
357
  def cleanup dir
358
358
  @db.browse_dir dir do |child_path|
359
- if child_path[-1] == ?/ ## ugh!
359
+ if child_path =~ /\/$/ ## ugh!
360
360
  cleanup(child_path)
361
361
  else
362
362
  @db.delete(child_path)
@@ -68,10 +68,6 @@ class ConcurrencyTest
68
68
  require 'profile'
69
69
  end
70
70
 
71
- opts.on("--fcntl-lock", "use fcntl version of flock") do
72
- # actually, this arg is already picked out by test.rb
73
- end
74
-
75
71
  opts.on("--nofork", "do not use fork, even if available") do
76
72
  params["nofork"] = true
77
73
  end
@@ -32,7 +32,7 @@ class Test_Formats < Test::Unit::TestCase
32
32
  # this is like in test-concurrency.rb -- generalize?
33
33
  def cleanup dir
34
34
  @db.browse_dir dir do |child_path|
35
- if child_path[-1] == ?/ ## ugh!
35
+ if child_path =~ /\/$/ ## ugh!
36
36
  cleanup(child_path)
37
37
  else
38
38
  @db.delete(child_path)
@@ -23,13 +23,15 @@ class Test_FSDB < Test::Unit::TestCase
23
23
  end
24
24
 
25
25
  def test_zzz_cleanup # Argh! Test::Unit is missing a few features....
26
+ @db['foo.txt'] = "abc" # something to clean up
27
+ ## really, cleanup should work when the dir is not there
26
28
  cleanup '/'
27
29
  end
28
30
 
29
31
  # this is like in test-concurrency.rb -- generalize?
30
32
  def cleanup dir
31
33
  @db.browse_dir dir do |child_path|
32
- if child_path[-1] == ?/ ## ugh!
34
+ if child_path =~ /\/$/ ## ugh!
33
35
  cleanup(child_path)
34
36
  else
35
37
  @db.delete(child_path)
@@ -4,28 +4,70 @@ require 'fsdb/modex'
4
4
 
5
5
  include FSDB
6
6
 
7
- modex = Modex.new
8
- counter = 0
7
+ DEBUG_MODEX = ARGV.delete '-d'
8
+ VERBOSE_MODEX = ARGV.delete '-v'
9
+
9
10
  thread_count = (ARGV.shift || 10).to_i
10
11
  rep_count = (ARGV.shift || 1000).to_i
11
12
 
13
+ def tabbed a
14
+ a.map {|s| s.sub(/^(\d+)/) {|n| " " * n.to_i * 20 + n}}
15
+ end
16
+
17
+ def thread_pass
18
+ if rand < 0.1
19
+ sleep rand/1000
20
+ else
21
+ Thread.pass
22
+ end
23
+ end
24
+
25
+ modex = Modex.new
26
+ counter = 0
27
+
12
28
  out = []
13
29
  sharers = 0
14
30
  excluders = 0
15
31
  dumper = proc do |str|
16
32
  puts "*** #{str} called when there were #{sharers} threads sharing modex ***"
17
- puts out[-20..-1].join("\n")
33
+ puts tabbed(out[-20..-1])
18
34
  exit!
19
35
  end
20
36
 
37
+ class TestThread < Thread
38
+ def initialize n, outdev
39
+ self[:id] = n
40
+ self[:writes] = 0
41
+ @outdev = outdev
42
+
43
+ super do
44
+ begin
45
+ yield
46
+ rescue => ex
47
+ puts "#{ex}\n #{ex.backtrace.join("\n ")}"
48
+ # let thread stop, but let process continue
49
+ end
50
+ end
51
+ end
52
+
53
+ def inspect
54
+ "#<test thread #{self[:id]}>"
55
+ end
56
+
57
+ if DEBUG_MODEX
58
+ def out s
59
+ @outdev << "#{self[:id]}: #{s}"
60
+ end
61
+ else
62
+ def out(*); end
63
+ end
64
+ end
65
+
21
66
  threads = (0...thread_count).map do |n|
22
- Thread.new do
67
+ TestThread.new(n, out) do
23
68
  thread = Thread.current
24
- thread[:id] = n
25
- thread[:writes] = 0
26
-
69
+
27
70
  do_when_first = proc do
28
- out << "#{thread[:id]}: do_when_first"
29
71
  sharers += 1
30
72
  if sharers > 1
31
73
  dumper["do_when_first"]
@@ -33,7 +75,6 @@ threads = (0...thread_count).map do |n|
33
75
  end
34
76
 
35
77
  do_when_last = proc do
36
- out << "#{thread[:id]}: do_when_last"
37
78
  if sharers > 1
38
79
  dumper["do_when_last"]
39
80
  end
@@ -44,37 +85,40 @@ threads = (0...thread_count).map do |n|
44
85
  x = rand(100)
45
86
  case
46
87
  when x < 50
47
- out << "#{thread[:id]}: trying SH"
88
+ thread.out "trying SH"
48
89
  modex.synchronize(Modex::SH, do_when_first, do_when_last) do
49
- out << "#{thread[:id]}: locked SH"
90
+ thread.out "begin SH"
50
91
  c_old = counter
51
- Thread.pass
92
+ thread_pass
52
93
  raise if excluders > 0
53
94
  raise unless counter == c_old
54
- out << "#{thread[:id]}: unlocked SH"
95
+ thread.out "end SH"
55
96
  end
97
+ thread_pass
98
+
56
99
  else
57
- out << "#{thread[:id]}: trying EX"
100
+ thread.out "trying EX"
58
101
  modex.synchronize(Modex::EX) do
59
- out << "#{thread[:id]}: locked EX"
102
+ thread.out "begin EX"
60
103
  excluders += 1
61
104
  counter += 1
62
- Thread.pass
105
+ thread_pass
63
106
  raise unless excluders == 1
107
+ raise unless sharers == 0
64
108
  excluders -= 1
65
- out << "#{thread[:id]}: unlocked EX"
109
+ thread.out "end EX"
66
110
  end
111
+ thread_pass
67
112
  thread[:writes] += 1
68
113
  end
69
114
  end
70
115
  end
71
116
  end
72
117
 
73
- expected = 0
74
- threads.each {|t| t.join; expected += t[:writes]}
118
+ expected = threads.inject(0) {|s, t| t.join; s + t[:writes]}
119
+ puts tabbed(out) if VERBOSE_MODEX
75
120
 
76
121
  actual = counter
77
-
78
122
  if expected == actual
79
123
  puts "Test passed: #{actual} write operations"
80
124
  else