tsafe 0.0.11 → 0.0.12
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.
- 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
|