tsafe 0.0.11 → 0.0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +73 -0
- data/.rubocop_todo.yml +18 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +64 -6
- data/{README.rdoc → README.md} +3 -3
- data/Rakefile +19 -16
- data/VERSION +1 -1
- data/lib/tsafe.rb +14 -14
- data/lib/tsafe_monarray.rb +5 -3
- data/lib/tsafe_monhash.rb +5 -3
- data/lib/tsafe_monitored.rb +10 -10
- data/lib/tsafe_mrswlock.rb +44 -48
- data/lib/tsafe_mrswlock_jruby.rb +8 -8
- data/lib/tsafe_mrswlock_synmodule.rb +16 -16
- data/lib/tsafe_mutexed.rb +10 -10
- data/lib/tsafe_proxy.rb +7 -7
- data/shippable.yml +11 -0
- data/spec/mrswlock_spec.rb +46 -50
- data/spec/spec_helper.rb +4 -5
- data/spec/tsafe_spec.rb +22 -23
- data/tsafe.gemspec +22 -12
- metadata +100 -67
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f4f86e8634d75137b3cc4e0028ea91ecd817b3bb
|
4
|
+
data.tar.gz: a808258388e5f91e46968714eb244f9441d32923
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5e1cd00910e7af20774471c76ba8458ff24534cfa466b31ad94bccfc6c5396cbada12771423f9fa0469e89f0450ecae6ecca8c2c392af87998261fa9185031c7
|
7
|
+
data.tar.gz: 02b4accddafaf35f3aceb98bf309bdccd58c67361ed41ebc0be2e146e98971c8e03965ede29d70785165aabc22f5cfd002ef70cc8701190024b61abb3b32bf8c
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
AllCops:
|
4
|
+
DisplayCopNames: true
|
5
|
+
DisplayStyleGuide: true
|
6
|
+
Exclude:
|
7
|
+
- tsafe.gemspec
|
8
|
+
|
9
|
+
# https://github.com/AtomLinter/linter-rubocop/issues/2
|
10
|
+
Style/FileName:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
# Metrics/LineLength:
|
14
|
+
# Max: 160
|
15
|
+
|
16
|
+
Metrics/MethodLength:
|
17
|
+
Max: 50
|
18
|
+
|
19
|
+
Metrics/AbcSize:
|
20
|
+
Max: 25
|
21
|
+
|
22
|
+
Metrics/ClassLength:
|
23
|
+
Max: 250
|
24
|
+
|
25
|
+
Style/AccessModifierIndentation:
|
26
|
+
EnforcedStyle: outdent
|
27
|
+
|
28
|
+
Style/AlignParameters:
|
29
|
+
EnforcedStyle: with_fixed_indentation
|
30
|
+
|
31
|
+
Style/ClassAndModuleChildren:
|
32
|
+
EnforcedStyle: compact
|
33
|
+
|
34
|
+
Style/ConditionalAssignment:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
Style/Documentation:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
Style/EmptyLines:
|
41
|
+
Enabled: false
|
42
|
+
|
43
|
+
Style/MultilineMethodCallIndentation:
|
44
|
+
EnforcedStyle: indented
|
45
|
+
|
46
|
+
Style/MultilineOperationIndentation:
|
47
|
+
EnforcedStyle: indented
|
48
|
+
|
49
|
+
Style/StringLiterals:
|
50
|
+
EnforcedStyle: double_quotes
|
51
|
+
|
52
|
+
Style/StringLiteralsInInterpolation:
|
53
|
+
Enabled: false
|
54
|
+
|
55
|
+
Style/NilComparison:
|
56
|
+
Enabled: false
|
57
|
+
|
58
|
+
Style/SignalException:
|
59
|
+
EnforcedStyle: only_raise
|
60
|
+
|
61
|
+
Style/MultilineOperationIndentation:
|
62
|
+
EnforcedStyle: indented
|
63
|
+
|
64
|
+
Style/SpaceInsideHashLiteralBraces:
|
65
|
+
EnforcedStyle: no_space
|
66
|
+
|
67
|
+
Style/TrivialAccessors:
|
68
|
+
ExactNameMatch: true
|
69
|
+
Enabled: true
|
70
|
+
|
71
|
+
# Disabled on purpose: https://github.com/bbatsov/rubocop/issues/1758
|
72
|
+
Style/ClosingParenthesisIndentation:
|
73
|
+
Enabled: false
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2016-02-08 11:42:03 +0100 using RuboCop version 0.37.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 7
|
10
|
+
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes.
|
11
|
+
# URISchemes: http, https
|
12
|
+
Metrics/LineLength:
|
13
|
+
Max: 298
|
14
|
+
|
15
|
+
# Offense count: 1
|
16
|
+
Style/ClassAndModuleCamelCase:
|
17
|
+
Exclude:
|
18
|
+
- 'lib/tsafe_mrswlock_synmodule.rb'
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,17 +1,61 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git://github.com/kaspernj/best_practice_project.git
|
3
|
+
revision: 9c747dd3b66ee18010c6f5c839e05c36452d44ea
|
4
|
+
specs:
|
5
|
+
best_practice_project (0.0.9)
|
6
|
+
auto_autoloader
|
7
|
+
|
1
8
|
GEM
|
2
9
|
remote: http://rubygems.org/
|
3
10
|
specs:
|
11
|
+
addressable (2.4.0)
|
12
|
+
ast (2.2.0)
|
13
|
+
auto_autoloader (0.0.4)
|
14
|
+
string-cases
|
15
|
+
builder (3.2.2)
|
4
16
|
diff-lcs (1.1.3)
|
5
|
-
|
6
|
-
|
17
|
+
faraday (0.8.11)
|
18
|
+
multipart-post (~> 1.2.0)
|
19
|
+
git (1.2.9.1)
|
20
|
+
github_api (0.10.1)
|
21
|
+
addressable
|
22
|
+
faraday (~> 0.8.1)
|
23
|
+
hashie (>= 1.2)
|
24
|
+
multi_json (~> 1.4)
|
25
|
+
nokogiri (~> 1.5.2)
|
26
|
+
oauth2
|
27
|
+
hashie (3.4.3)
|
28
|
+
highline (1.7.8)
|
29
|
+
jeweler (1.8.8)
|
30
|
+
builder
|
7
31
|
bundler (~> 1.0)
|
8
32
|
git (>= 1.2.5)
|
33
|
+
github_api (= 0.10.1)
|
34
|
+
highline (>= 1.6.15)
|
35
|
+
nokogiri (= 1.5.10)
|
9
36
|
rake
|
10
37
|
rdoc
|
11
|
-
json (1.
|
12
|
-
json (1.
|
13
|
-
|
14
|
-
|
38
|
+
json (1.8.3)
|
39
|
+
json (1.8.3-java)
|
40
|
+
jwt (1.5.1)
|
41
|
+
multi_json (1.11.2)
|
42
|
+
multi_xml (0.5.5)
|
43
|
+
multipart-post (1.2.0)
|
44
|
+
nokogiri (1.5.10)
|
45
|
+
nokogiri (1.5.10-java)
|
46
|
+
oauth2 (1.1.0)
|
47
|
+
faraday (>= 0.8, < 0.10)
|
48
|
+
jwt (~> 1.0, < 1.5.2)
|
49
|
+
multi_json (~> 1.3)
|
50
|
+
multi_xml (~> 0.5)
|
51
|
+
rack (>= 1.2, < 3)
|
52
|
+
parser (2.3.0.3)
|
53
|
+
ast (~> 2.2)
|
54
|
+
powerpack (0.1.1)
|
55
|
+
rack (1.6.4)
|
56
|
+
rainbow (2.1.0)
|
57
|
+
rake (10.5.0)
|
58
|
+
rdoc (3.12.2)
|
15
59
|
json (~> 1.4)
|
16
60
|
rspec (2.8.0)
|
17
61
|
rspec-core (~> 2.8.0)
|
@@ -21,13 +65,27 @@ GEM
|
|
21
65
|
rspec-expectations (2.8.0)
|
22
66
|
diff-lcs (~> 1.1.2)
|
23
67
|
rspec-mocks (2.8.0)
|
68
|
+
rubocop (0.37.0)
|
69
|
+
parser (>= 2.3.0.2, < 3.0)
|
70
|
+
powerpack (~> 0.1)
|
71
|
+
rainbow (>= 1.99.1, < 3.0)
|
72
|
+
ruby-progressbar (~> 1.7)
|
73
|
+
unicode-display_width (~> 0.3)
|
74
|
+
ruby-progressbar (1.7.5)
|
75
|
+
string-cases (0.0.4)
|
76
|
+
unicode-display_width (0.3.1)
|
24
77
|
|
25
78
|
PLATFORMS
|
26
79
|
java
|
27
80
|
ruby
|
28
81
|
|
29
82
|
DEPENDENCIES
|
83
|
+
best_practice_project!
|
30
84
|
bundler (>= 1.0.0)
|
31
85
|
jeweler (~> 1.8.3)
|
32
86
|
rdoc (~> 3.12)
|
33
87
|
rspec (~> 2.8.0)
|
88
|
+
rubocop (= 0.37.0)
|
89
|
+
|
90
|
+
BUNDLED WITH
|
91
|
+
1.11.2
|
data/{README.rdoc → README.md}
RENAMED
@@ -1,9 +1,9 @@
|
|
1
|
-
=
|
1
|
+
= Tsafe
|
2
2
|
|
3
|
-
|
3
|
+
Various classes for thread safe operations in Ruby and JRuby.
|
4
4
|
|
5
5
|
== Contributing to tsafe
|
6
|
-
|
6
|
+
|
7
7
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
8
8
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
9
9
|
* Fork the project.
|
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "rubygems"
|
4
|
+
require "bundler"
|
5
5
|
begin
|
6
6
|
Bundler.setup(:default, :development)
|
7
7
|
rescue Bundler::BundlerError => e
|
@@ -9,41 +9,44 @@ rescue Bundler::BundlerError => e
|
|
9
9
|
$stderr.puts "Run `bundle install` to install missing gems"
|
10
10
|
exit e.status_code
|
11
11
|
end
|
12
|
-
require
|
12
|
+
require "rake"
|
13
13
|
|
14
|
-
require
|
14
|
+
require "jeweler"
|
15
15
|
Jeweler::Tasks.new do |gem|
|
16
16
|
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
17
|
gem.name = "tsafe"
|
18
18
|
gem.homepage = "http://github.com/kaspernj/tsafe"
|
19
19
|
gem.license = "MIT"
|
20
|
-
gem.summary = %
|
21
|
-
gem.description = %
|
20
|
+
gem.summary = %(Threadsafe proxy, array, hash and framework for making other classes threadsafe.)
|
21
|
+
gem.description = %(Proxy-objects for making another object threadsafe by proxying calls through mutex and method_missing. Monitored array and hash where all methods are going through monitor. Threadsafe class for including into a class that extends another class in order to make it threadsafe.)
|
22
22
|
gem.email = "k@spernj.org"
|
23
23
|
gem.authors = ["Kasper Johansen"]
|
24
24
|
# dependencies defined in Gemfile
|
25
25
|
end
|
26
26
|
Jeweler::RubygemsDotOrgTasks.new
|
27
27
|
|
28
|
-
require
|
29
|
-
require
|
28
|
+
require "rspec/core"
|
29
|
+
require "rspec/core/rake_task"
|
30
30
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
-
spec.pattern = FileList[
|
31
|
+
spec.pattern = FileList["spec/**/*_spec.rb"]
|
32
32
|
end
|
33
33
|
|
34
34
|
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
-
spec.pattern =
|
35
|
+
spec.pattern = "spec/**/*_spec.rb"
|
36
36
|
spec.rcov = true
|
37
37
|
end
|
38
38
|
|
39
|
-
task :
|
39
|
+
task default: :spec
|
40
40
|
|
41
|
-
require
|
41
|
+
require "rdoc/task"
|
42
42
|
Rake::RDocTask.new do |rdoc|
|
43
|
-
version = File.exist?(
|
43
|
+
version = File.exist?("VERSION") ? File.read("VERSION") : ""
|
44
44
|
|
45
|
-
rdoc.rdoc_dir =
|
45
|
+
rdoc.rdoc_dir = "rdoc"
|
46
46
|
rdoc.title = "tsafe #{version}"
|
47
|
-
rdoc.rdoc_files.include(
|
48
|
-
rdoc.rdoc_files.include(
|
47
|
+
rdoc.rdoc_files.include("README*")
|
48
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
49
49
|
end
|
50
|
+
|
51
|
+
require "best_practice_project"
|
52
|
+
BestPracticeProject.load_tasks
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.12
|
data/lib/tsafe.rb
CHANGED
@@ -1,24 +1,24 @@
|
|
1
1
|
require "monitor"
|
2
2
|
|
3
|
-
#This module contains various tools to handle thread-safety easily and pretty.
|
3
|
+
# This module contains various tools to handle thread-safety easily and pretty.
|
4
4
|
module Tsafe
|
5
|
-
#Autoloader for subclasses.
|
5
|
+
# Autoloader for subclasses.
|
6
6
|
def self.const_missing(name)
|
7
|
-
|
8
|
-
|
9
|
-
if RUBY_ENGINE == "jruby"
|
10
|
-
require "#{
|
7
|
+
file_path = "#{File.dirname(__FILE__)}/tsafe_#{name.to_s.downcase}"
|
8
|
+
|
9
|
+
if RUBY_ENGINE == "jruby" && File.exist?("#{file_path}_jruby.rb")
|
10
|
+
require "#{file_path}_jruby.rb"
|
11
11
|
else
|
12
|
-
require
|
12
|
+
require file_path
|
13
13
|
end
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
|
15
|
+
return Tsafe.const_get(name) if Tsafe.const_defined?(name)
|
16
|
+
super
|
17
17
|
end
|
18
|
-
|
19
|
-
#JRuby can corrupt an array in a threadded env. Use this method to only get a synchronized array when running JRuby in order to not having to write "if RUBY_ENGINE"-stuff.
|
18
|
+
|
19
|
+
# JRuby can corrupt an array in a threadded env. Use this method to only get a synchronized array when running JRuby in order to not having to write "if RUBY_ENGINE"-stuff.
|
20
20
|
def self.std_array
|
21
21
|
return Tsafe::MonArray.new if RUBY_ENGINE == "jruby"
|
22
|
-
|
22
|
+
[]
|
23
23
|
end
|
24
|
-
end
|
24
|
+
end
|
data/lib/tsafe_monarray.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
#Predefined synchronized array.
|
1
|
+
# Predefined synchronized array.
|
2
2
|
#
|
3
3
|
#===Examples
|
4
4
|
# arr = Tsafe::MonArray.new
|
5
5
|
# arr << 5
|
6
6
|
# ret = arr[0]
|
7
7
|
class Tsafe::MonArray < ::Array
|
8
|
+
# rubocop:disable Style/ClassVars
|
8
9
|
@@tsafe_mrswlock_w_methods = [:<<, :collect, :collect!, :compact!, :delete, :delete_at, :delete_if, :drop, :drop_while, :fill, :flatten!, :insert, :keep_if, :map, :map!, :replace, :shuffle!, :slice!, :shift, :sort!, :sort_by!, :unshift]
|
9
10
|
@@tsafe_mrswlock_r_methods = [:each, :each_index, :take_while]
|
10
|
-
|
11
|
+
# rubocop:enable Style/ClassVars
|
12
|
+
|
11
13
|
include Tsafe::Mrswlock_synmodule
|
12
|
-
end
|
14
|
+
end
|
data/lib/tsafe_monhash.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
#Predefined synchronized hash.
|
1
|
+
# Predefined synchronized hash.
|
2
2
|
#
|
3
3
|
#===Examples
|
4
4
|
# h = Tsafe::MonHash.new
|
5
5
|
# h['test'] = 'trala'
|
6
6
|
# ret = h['test']
|
7
7
|
class Tsafe::MonHash < ::Hash
|
8
|
+
# rubocop:disable Style/ClassVars
|
8
9
|
@@tsafe_mrswlock_w_methods = [:[]=, :clear, :delete, :delete_if, :keep_if, :merge!, :rehash, :reject!, :replace, :select!, :shift, :store, :update, :values_at]
|
9
10
|
@@tsafe_mrswlock_r_methods = [:each, :each_key, :each_pair, :each_value]
|
10
|
-
|
11
|
+
# rubocop:enable Style/ClassVars
|
12
|
+
|
11
13
|
include Tsafe::Mrswlock_synmodule
|
12
|
-
end
|
14
|
+
end
|
data/lib/tsafe_monitored.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#This module can be included on a class to make all method-calls synchronized (by using monitor). Examples with array and hash are below.
|
1
|
+
# This module can be included on a class to make all method-calls synchronized (by using monitor). Examples with array and hash are below.
|
2
2
|
#
|
3
3
|
#===Examples
|
4
4
|
# class MySyncedClass < SomeOtherClassThatNeedsToBeSynchronized
|
@@ -7,23 +7,23 @@
|
|
7
7
|
module Tsafe::Monitored
|
8
8
|
def self.included(base)
|
9
9
|
base.to_s.split("::").inject(Object, :const_get).class_eval do
|
10
|
-
|
11
|
-
#These two methods create warnings under JRuby.
|
10
|
+
instance_methods.each do |method_name|
|
11
|
+
# These two methods create warnings under JRuby.
|
12
12
|
if RUBY_ENGINE == "jruby"
|
13
|
-
next if method_name == :instance_exec
|
13
|
+
next if method_name == :instance_exec || method_name == :instance_eval
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
new_method_name = "_ts_#{method_name}"
|
17
17
|
alias_method(new_method_name, method_name)
|
18
|
-
|
18
|
+
|
19
19
|
define_method method_name do |*args, &block|
|
20
|
-
#Need to use monitor, since the internal calls might have to run not-synchronized, and we have just overwritten the internal methods.
|
21
|
-
@_ts_mutex = Monitor.new
|
20
|
+
# Need to use monitor, since the internal calls might have to run not-synchronized, and we have just overwritten the internal methods.
|
21
|
+
@_ts_mutex = Monitor.new unless @_ts_mutex
|
22
22
|
@_ts_mutex.synchronize do
|
23
|
-
return
|
23
|
+
return _ts___send__(new_method_name, *args, &block)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
28
28
|
end
|
29
|
-
end
|
29
|
+
end
|
data/lib/tsafe_mrswlock.rb
CHANGED
@@ -7,70 +7,66 @@ class Tsafe::Mrswlock
|
|
7
7
|
@reads = 0
|
8
8
|
@w_mutex = Mutex.new
|
9
9
|
@threads_read_stopped = []
|
10
|
-
|
11
|
-
#This variable is used to allow reads from the writing thread (monitor-behavior).
|
10
|
+
|
11
|
+
# This variable is used to allow reads from the writing thread (monitor-behavior).
|
12
12
|
@locked_by = nil
|
13
|
-
|
14
|
-
#This hash holds thread-IDs for threads that are reading.
|
13
|
+
|
14
|
+
# This hash holds thread-IDs for threads that are reading.
|
15
15
|
@reading_threads = {}
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
# Runs the given block through the read-synchronization.
|
19
19
|
def rsync
|
20
20
|
begin
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
Thread.stop
|
27
|
-
end
|
28
|
-
|
29
|
-
@threads_read_stopped.delete(Thread.current)
|
30
|
-
@reading_threads[tid] = true
|
31
|
-
@reads += 1
|
32
|
-
yield
|
33
|
-
ensure
|
34
|
-
@reading_threads.delete(tid)
|
35
|
-
@reads -= 1
|
21
|
+
tid = Thread.current.__id__
|
22
|
+
|
23
|
+
while @w_mutex.locked? && @locked_by != tid
|
24
|
+
@threads_read_stopped << Thread.current
|
25
|
+
Thread.stop
|
36
26
|
end
|
27
|
+
|
28
|
+
@threads_read_stopped.delete(Thread.current)
|
29
|
+
@reading_threads[tid] = true
|
30
|
+
@reads += 1
|
31
|
+
yield
|
37
32
|
ensure
|
38
|
-
|
39
|
-
@
|
33
|
+
@reading_threads.delete(tid)
|
34
|
+
@reads -= 1
|
40
35
|
end
|
36
|
+
ensure
|
37
|
+
# Restart stopped writing-thread.
|
38
|
+
@threads_write_stopped.run if @threads_write_stopped && @reads <= 0
|
41
39
|
end
|
42
|
-
|
43
|
-
#Runs the given block through the write-synchronization (locks both reading and writing).
|
40
|
+
|
41
|
+
# Runs the given block through the write-synchronization (locks both reading and writing).
|
44
42
|
#===Examples
|
45
43
|
# lock.wsync do
|
46
44
|
# #do something within lock.
|
47
45
|
# end
|
48
46
|
def wsync
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
while
|
58
|
-
|
59
|
-
|
60
|
-
Thread.stop
|
61
|
-
end
|
62
|
-
|
63
|
-
yield
|
64
|
-
ensure
|
65
|
-
@locked_by = nil
|
66
|
-
@threads_write_stopped = nil
|
47
|
+
@w_mutex.synchronize do
|
48
|
+
begin
|
49
|
+
tid = Thread.current.__id__
|
50
|
+
@locked_by = tid
|
51
|
+
|
52
|
+
# Wait for any reads to finish that might have started while we were getting the lock.
|
53
|
+
# Also allow write if there is only one reading thread and that reading thread is the current thread.
|
54
|
+
while @reads > 0
|
55
|
+
raise ThreadError, "Deadlock: Writing is not allowed while reading." if @reading_threads.key?(tid)
|
56
|
+
@threads_write_stopped = Thread.current
|
57
|
+
Thread.stop
|
67
58
|
end
|
59
|
+
|
60
|
+
yield
|
61
|
+
ensure
|
62
|
+
@locked_by = nil
|
63
|
+
@threads_write_stopped = nil
|
68
64
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
65
|
+
end
|
66
|
+
ensure
|
67
|
+
# Restart any stopped reading-threads.
|
68
|
+
while (thread = @threads_read_stopped.shift)
|
69
|
+
thread.run
|
74
70
|
end
|
75
71
|
end
|
76
|
-
end
|
72
|
+
end
|