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
data/lib/tsafe_mrswlock_jruby.rb
CHANGED
@@ -6,17 +6,17 @@ class Tsafe::Mrswlock
|
|
6
6
|
# Sets various variables.
|
7
7
|
def initialize
|
8
8
|
@lock = java.util.concurrent.locks.ReentrantReadWriteLock.new
|
9
|
-
|
10
|
-
#This hash holds thread-IDs for threads that are reading.
|
9
|
+
|
10
|
+
# This hash holds thread-IDs for threads that are reading.
|
11
11
|
@reading_threads = {}
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
# Runs the given block through the read-synchronization.
|
15
15
|
def rsync
|
16
16
|
@lock.read_lock.lock
|
17
17
|
tid = Thread.current.__id__
|
18
18
|
@reading_threads[tid] = true
|
19
|
-
|
19
|
+
|
20
20
|
begin
|
21
21
|
yield
|
22
22
|
ensure
|
@@ -24,8 +24,8 @@ class Tsafe::Mrswlock
|
|
24
24
|
@lock.read_lock.unlock
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
28
|
-
#Runs the given block through the write-synchronization (locks both reading and writing).
|
27
|
+
|
28
|
+
# Runs the given block through the write-synchronization (locks both reading and writing).
|
29
29
|
#===Examples
|
30
30
|
# lock.wsync do
|
31
31
|
# #do something within lock.
|
@@ -33,7 +33,7 @@ class Tsafe::Mrswlock
|
|
33
33
|
def wsync
|
34
34
|
tid = Thread.current.__id__
|
35
35
|
raise ThreadError, "Deadlock: Writing is not allowed while reading." if @reading_threads.key?(tid)
|
36
|
-
|
36
|
+
|
37
37
|
begin
|
38
38
|
@wlock_by = tid
|
39
39
|
@lock.write_lock.lock
|
@@ -42,4 +42,4 @@ class Tsafe::Mrswlock
|
|
42
42
|
@lock.write_lock.unlock
|
43
43
|
end
|
44
44
|
end
|
45
|
-
end
|
45
|
+
end
|
@@ -8,7 +8,7 @@
|
|
8
8
|
module Tsafe::Mrswlock_synmodule
|
9
9
|
def self.included(base)
|
10
10
|
base.to_s.split("::").inject(Object, :const_get).class_eval do
|
11
|
-
#Yields the given block within the read-lock.
|
11
|
+
# Yields the given block within the read-lock.
|
12
12
|
#===Examples
|
13
13
|
# obj._tsafe_rsync do
|
14
14
|
# #do something within read-lock.
|
@@ -16,8 +16,8 @@ module Tsafe::Mrswlock_synmodule
|
|
16
16
|
def _tsafe_rsync(&block)
|
17
17
|
@tsafe_mrswlock.rsync(&block)
|
18
18
|
end
|
19
|
-
|
20
|
-
#Yields the given block within the write-lock (and read-lock).
|
19
|
+
|
20
|
+
# Yields the given block within the write-lock (and read-lock).
|
21
21
|
#===Examples
|
22
22
|
# obj._tsafe_wsync do
|
23
23
|
# #do something within write-lock.
|
@@ -25,39 +25,39 @@ module Tsafe::Mrswlock_synmodule
|
|
25
25
|
def _tsafe_wsync(&block)
|
26
26
|
@tsafe_mrswlock.rsync(&block)
|
27
27
|
end
|
28
|
-
|
29
|
-
#Rename initialize.
|
28
|
+
|
29
|
+
# Rename initialize.
|
30
30
|
alias_method(:initialize_mrswlock, :initialize)
|
31
|
-
|
32
|
-
#Make another initialize-method that spawns the lock and then calls the original initialize.
|
31
|
+
|
32
|
+
# Make another initialize-method that spawns the lock and then calls the original initialize.
|
33
33
|
define_method(:initialize) do |*args, &block|
|
34
34
|
@tsafe_mrswlock = Tsafe::Mrswlock.new
|
35
35
|
return initialize_mrswlock(*args, &block)
|
36
36
|
end
|
37
|
-
|
38
|
-
#Makes reader methods go through reader-lock.
|
37
|
+
|
38
|
+
# Makes reader methods go through reader-lock.
|
39
39
|
base.class_variable_get(:@@tsafe_mrswlock_r_methods).each do |mname|
|
40
40
|
newmname = "tsafe_mrswlock_#{mname}".to_sym
|
41
41
|
alias_method(newmname, mname)
|
42
|
-
|
42
|
+
|
43
43
|
define_method(mname) do |*args, &block|
|
44
44
|
@tsafe_mrswlock.rsync do
|
45
|
-
return
|
45
|
+
return __send__(newmname, *args, &block)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
50
|
-
#Makes writer methods go through writer-lock.
|
49
|
+
|
50
|
+
# Makes writer methods go through writer-lock.
|
51
51
|
base.class_variable_get(:@@tsafe_mrswlock_w_methods).each do |mname|
|
52
52
|
newmname = "tsafe_mrswlock_#{mname}".to_sym
|
53
53
|
alias_method(newmname, mname)
|
54
|
-
|
54
|
+
|
55
55
|
define_method(mname) do |*args, &block|
|
56
56
|
@tsafe_mrswlock.wsync do
|
57
|
-
return
|
57
|
+
return __send__(newmname, *args, &block)
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
61
61
|
end
|
62
62
|
end
|
63
|
-
end
|
63
|
+
end
|
data/lib/tsafe_mutexed.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#This module can be included on a class to make all method-calls synchronized (by using mutex). Examples with array and hash are below.
|
1
|
+
# This module can be included on a class to make all method-calls synchronized (by using mutex). 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::Mutexed
|
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 = Mutex.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 = Mutex.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_proxy.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
-
#Instances of this class proxies calls to a given-object by using a mutex or monitor.
|
1
|
+
# Instances of this class proxies calls to a given-object by using a mutex or monitor.
|
2
2
|
#
|
3
3
|
#==== Examples
|
4
4
|
# threadsafe_array = Tsafe::Proxy.new(:obj => [])
|
5
5
|
# threadsafe_array << 5
|
6
6
|
# ret = threadsafe_array[0]
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# threadsafe_array = Tsafe::Proxy.new(:obj => [], :monitor => true)
|
9
9
|
class Tsafe::Proxy
|
10
|
-
#Spawns needed vars.
|
10
|
+
# Spawns needed vars.
|
11
11
|
def initialize(args)
|
12
12
|
if args[:monitor]
|
13
13
|
@mutex = Monitor.new
|
@@ -16,14 +16,14 @@ class Tsafe::Proxy
|
|
16
16
|
else
|
17
17
|
@mutex = Mutex.new
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
@obj = args[:obj]
|
21
21
|
end
|
22
|
-
|
23
|
-
#Proxies all calls to this object through the mutex.
|
22
|
+
|
23
|
+
# Proxies all calls to this object through the mutex.
|
24
24
|
def method_missing(method_name, *args, &block)
|
25
25
|
@mutex.synchronize do
|
26
26
|
@obj.__send__(method_name, *args, &block)
|
27
27
|
end
|
28
28
|
end
|
29
|
-
end
|
29
|
+
end
|
data/shippable.yml
ADDED
data/spec/mrswlock_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) +
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
|
2
2
|
|
3
3
|
require "timeout"
|
4
4
|
|
@@ -6,112 +6,112 @@ describe "Tsafe::Rwmutex" do
|
|
6
6
|
it "should work with the modified hash" do
|
7
7
|
Thread.abort_on_exception = true
|
8
8
|
debug = false
|
9
|
-
|
9
|
+
|
10
10
|
print "Setting initial values.\n" if debug
|
11
11
|
hash = Tsafe::MonHash.new
|
12
12
|
0.upto(15) do |count|
|
13
|
-
realcount =
|
13
|
+
realcount = 100_000_000 - count
|
14
14
|
hash[realcount] = realcount
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
called = false
|
18
18
|
hash._tsafe_rsync do
|
19
19
|
called = true
|
20
20
|
end
|
21
|
-
|
22
|
-
raise "Expected to be called."
|
23
|
-
|
21
|
+
|
22
|
+
raise "Expected to be called." unless called
|
23
|
+
|
24
24
|
called = false
|
25
25
|
hash._tsafe_wsync do
|
26
26
|
called = true
|
27
27
|
end
|
28
|
-
|
29
|
-
raise "Expected to be called."
|
30
|
-
|
28
|
+
|
29
|
+
raise "Expected to be called." unless called
|
30
|
+
|
31
31
|
ts = []
|
32
|
-
|
32
|
+
|
33
33
|
1.upto(10) do |tcount|
|
34
34
|
print "Starting thread #{tcount}\n" if debug
|
35
35
|
ts << Thread.new do
|
36
36
|
1.upto(5000) do |count|
|
37
37
|
hash[count] = count
|
38
|
-
|
38
|
+
|
39
39
|
hash[count]
|
40
40
|
hash.key?(count)
|
41
|
-
|
41
|
+
|
42
42
|
hash.delete(count)
|
43
|
-
|
43
|
+
|
44
44
|
hash.each do |key, val|
|
45
|
-
#nothing...
|
45
|
+
# nothing...
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
ts.each do |t|
|
52
52
|
print "Joining #{t.__id__}\n" if debug
|
53
53
|
t.join
|
54
54
|
end
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
it "should work with manual lock creation" do
|
58
58
|
debug = false
|
59
|
-
|
59
|
+
|
60
60
|
hash = {}
|
61
61
|
0.upto(15) do |count|
|
62
|
-
realcount =
|
62
|
+
realcount = 100_000_000 - count
|
63
63
|
hash[realcount] = realcount
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
rwm = Tsafe::Mrswlock.new
|
67
67
|
ts = []
|
68
|
-
|
68
|
+
|
69
69
|
1.upto(10) do
|
70
70
|
ts << Thread.new do
|
71
71
|
1.upto(5000) do |count|
|
72
72
|
rwm.wsync do
|
73
73
|
hash[count] = count
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
rwm.rsync do
|
77
77
|
hash[count]
|
78
78
|
hash.key?(count)
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
rwm.wsync do
|
82
82
|
hash.delete(count)
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
rwm.rsync do
|
86
86
|
hash.each do |key, val|
|
87
|
-
#nothing...
|
87
|
+
# nothing...
|
88
88
|
end
|
89
89
|
end
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
ts.each do |t|
|
95
95
|
print "Joining #{t.__id__}\n" if debug
|
96
96
|
t.join
|
97
97
|
end
|
98
98
|
end
|
99
|
-
|
99
|
+
|
100
100
|
it "should be able to read while writing from same thread while other threads are stressing" do
|
101
101
|
hash = Tsafe::MonHash.new
|
102
102
|
0.upto(1500) do |count|
|
103
103
|
hash[count] = count
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
Timeout.timeout(14) do
|
107
107
|
ts = []
|
108
108
|
1.upto(20) do
|
109
109
|
ts << Thread.new do
|
110
|
-
hash.keep_if do |key,
|
110
|
+
hash.keep_if do |key, _val|
|
111
111
|
hash.each do |key2, val2|
|
112
|
-
#ignore.
|
112
|
+
# ignore.
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
if key > 500
|
116
116
|
true
|
117
117
|
else
|
@@ -120,55 +120,51 @@ describe "Tsafe::Rwmutex" do
|
|
120
120
|
end
|
121
121
|
end
|
122
122
|
end
|
123
|
-
|
124
|
-
ts.each
|
125
|
-
t.join
|
126
|
-
end
|
123
|
+
|
124
|
+
ts.each(&:join)
|
127
125
|
end
|
128
126
|
end
|
129
|
-
|
127
|
+
|
130
128
|
it "should not be able to write while reading from same thread" do
|
131
129
|
hash = Tsafe::MonHash.new
|
132
130
|
0.upto(1000) do |count|
|
133
131
|
hash[count] = count
|
134
132
|
end
|
135
|
-
|
133
|
+
|
136
134
|
begin
|
137
|
-
hash.each do |key,
|
135
|
+
hash.each do |key, _val|
|
138
136
|
hash.delete(key)
|
139
137
|
end
|
140
|
-
|
138
|
+
|
141
139
|
raise "Expected ThreadError but didnt get raised."
|
142
|
-
rescue ThreadError
|
143
|
-
#
|
140
|
+
rescue ThreadError # rubocop:disable Lint/HandleExceptions
|
141
|
+
# Ignore - supposed to happen.
|
144
142
|
end
|
145
143
|
end
|
146
|
-
|
144
|
+
|
147
145
|
it "should include thread-safe array" do
|
148
146
|
arr = Tsafe::MonArray.new
|
149
147
|
0.upto(1000) do |count|
|
150
148
|
arr << count
|
151
149
|
end
|
152
|
-
|
150
|
+
|
153
151
|
ts = []
|
154
152
|
0.upto(20) do
|
155
153
|
ts << Thread.new do
|
156
154
|
arr.each do |i|
|
157
|
-
|
155
|
+
i + 100 / 5
|
158
156
|
end
|
159
|
-
|
157
|
+
|
160
158
|
0.upto(1000) do |count|
|
161
159
|
arr << count + 1000
|
162
160
|
end
|
163
|
-
|
161
|
+
|
164
162
|
arr.delete_if do |count|
|
165
163
|
count > 1000
|
166
164
|
end
|
167
165
|
end
|
168
166
|
end
|
169
|
-
|
170
|
-
ts.each
|
171
|
-
t.join
|
172
|
-
end
|
167
|
+
|
168
|
+
ts.each(&:join)
|
173
169
|
end
|
174
170
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__),
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
2
2
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "rspec"
|
4
|
+
require "tsafe"
|
5
5
|
|
6
6
|
# Requires supporting files with custom matchers and macros, etc,
|
7
7
|
# in ./support/ and its subdirectories.
|
8
|
-
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
9
9
|
|
10
10
|
RSpec.configure do |config|
|
11
|
-
|
12
11
|
end
|
data/spec/tsafe_spec.rb
CHANGED
@@ -1,58 +1,57 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) +
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/spec_helper")
|
2
2
|
|
3
3
|
describe "Tsafe" do
|
4
4
|
it "should be able to spawn threadsafe proxy-objects" do
|
5
|
-
arr = Tsafe::Proxy.new(:
|
6
|
-
|
5
|
+
arr = Tsafe::Proxy.new(obj: {})
|
6
|
+
|
7
7
|
0.upto(5) do |i|
|
8
8
|
arr[i] = i
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
Thread.new do
|
12
12
|
begin
|
13
|
-
arr.each do
|
14
|
-
res = key + val
|
13
|
+
arr.each do
|
15
14
|
sleep 0.1
|
16
15
|
end
|
17
|
-
rescue Exception => e
|
18
|
-
|
16
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
17
|
+
puts e.inspect
|
19
18
|
end
|
20
19
|
end
|
21
|
-
|
20
|
+
|
22
21
|
5.upto(10) do |i|
|
23
22
|
arr[i] = i
|
24
23
|
sleep 0.1
|
25
24
|
end
|
26
25
|
end
|
27
|
-
|
26
|
+
|
28
27
|
it "should be able to spawn special classes" do
|
29
|
-
#Create new synchronized hash.
|
28
|
+
# Create new synchronized hash.
|
30
29
|
arr = Tsafe::MonHash.new
|
31
|
-
|
32
|
-
#Make sure we get the right results.
|
30
|
+
|
31
|
+
# Make sure we get the right results.
|
33
32
|
arr[1] = 2
|
34
|
-
|
33
|
+
|
35
34
|
res = arr[1]
|
36
|
-
raise "Expected 2 but got '#{res}'."
|
37
|
-
|
38
|
-
#Set some values to test with.
|
35
|
+
raise "Expected 2 but got '#{res}'." unless res == 2
|
36
|
+
|
37
|
+
# Set some values to test with.
|
39
38
|
0.upto(5) do |i|
|
40
39
|
arr[i] = i
|
41
40
|
end
|
42
|
-
|
43
|
-
#Try to call through each through a thread and then also try to set new values, which normally would crash the hash.
|
41
|
+
|
42
|
+
# Try to call through each through a thread and then also try to set new values, which normally would crash the hash.
|
44
43
|
Thread.new do
|
45
44
|
begin
|
46
45
|
arr.each do |key, val|
|
47
46
|
res = key + val
|
48
47
|
sleep 0.1
|
49
48
|
end
|
50
|
-
rescue Exception => e
|
51
|
-
|
49
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
50
|
+
puts e.inspect
|
52
51
|
end
|
53
52
|
end
|
54
|
-
|
55
|
-
#This should not crash it, since they should wait for each other.
|
53
|
+
|
54
|
+
# This should not crash it, since they should wait for each other.
|
56
55
|
5.upto(10) do |i|
|
57
56
|
arr[i] = i
|
58
57
|
sleep 0.1
|