quack_concurrency 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/quack_concurrency.rb +6 -13
  4. data/lib/quack_concurrency/condition_variable.rb +91 -85
  5. data/lib/quack_concurrency/condition_variable/waitable.rb +108 -0
  6. data/lib/quack_concurrency/error.rb +1 -1
  7. data/lib/quack_concurrency/future.rb +31 -30
  8. data/lib/quack_concurrency/future/canceled.rb +1 -0
  9. data/lib/quack_concurrency/future/complete.rb +1 -0
  10. data/lib/quack_concurrency/mutex.rb +140 -38
  11. data/lib/quack_concurrency/queue.rb +32 -28
  12. data/lib/quack_concurrency/reentrant_mutex.rb +64 -76
  13. data/lib/quack_concurrency/safe_condition_variable.rb +23 -0
  14. data/lib/quack_concurrency/safe_condition_variable/waitable.rb +21 -0
  15. data/lib/quack_concurrency/safe_sleeper.rb +80 -0
  16. data/lib/quack_concurrency/sleeper.rb +100 -0
  17. data/lib/quack_concurrency/waiter.rb +32 -23
  18. data/spec/condition_variable_spec.rb +216 -0
  19. data/spec/future_spec.rb +145 -79
  20. data/spec/mutex_spec.rb +441 -0
  21. data/spec/queue_spec.rb +217 -77
  22. data/spec/reentrant_mutex_spec.rb +394 -99
  23. data/spec/safe_condition_variable_spec.rb +115 -0
  24. data/spec/safe_sleeper_spec.rb +197 -0
  25. data/spec/sleeper.rb +197 -0
  26. data/spec/waiter_spec.rb +181 -0
  27. metadata +16 -14
  28. data/lib/quack_concurrency/queue/error.rb +0 -6
  29. data/lib/quack_concurrency/reentrant_mutex/error.rb +0 -6
  30. data/lib/quack_concurrency/semaphore.rb +0 -139
  31. data/lib/quack_concurrency/semaphore/error.rb +0 -6
  32. data/lib/quack_concurrency/uninterruptible_condition_variable.rb +0 -94
  33. data/lib/quack_concurrency/uninterruptible_sleeper.rb +0 -81
  34. data/lib/quack_concurrency/yielder.rb +0 -35
  35. data/spec/semaphore_spec.rb +0 -244
@@ -0,0 +1,115 @@
1
+ require 'quack_concurrency'
2
+
3
+ describe QuackConcurrency::SafeConditionVariable do
4
+
5
+ it "should inherit ConditionVariable" do
6
+ expect(described_class).to be < QuackConcurrency::ConditionVariable
7
+ end
8
+
9
+ describe "#signal" do
10
+
11
+ context "when called with waiting threads" do
12
+ it "should resume the next thread currently waiting" do
13
+ condition_variable = QuackConcurrency::SafeConditionVariable.new
14
+ mutex = Mutex.new
15
+ values = []
16
+ Thread.new { mutex.synchronize { condition_variable.wait(mutex); values << 1 } }
17
+ Thread.new { sleep 1; mutex.synchronize { condition_variable.wait(mutex); values << 2 } }
18
+ Thread.new { sleep 2; mutex.synchronize { condition_variable.wait(mutex); values << 3 } }
19
+ sleep 3
20
+ condition_variable.signal
21
+ condition_variable.signal
22
+ condition_variable.signal
23
+ sleep 1
24
+ expect(values).to eq [1, 2, 3]
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ describe "#wait" do
31
+
32
+ context "when called without a timeout" do
33
+ it "should block until #broadcast or #signal are called" do
34
+ condition_variable = QuackConcurrency::SafeConditionVariable.new
35
+ mutex = Mutex.new
36
+ thread1 = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
37
+ thread2 = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
38
+ sleep 1
39
+ expect(thread1.alive?).to be true
40
+ expect(thread2.alive?).to be true
41
+ condition_variable.broadcast
42
+ sleep 1
43
+ expect(thread1.alive?).to be false
44
+ expect(thread2.alive?).to be false
45
+ end
46
+ context "and before Thread#run" do
47
+ it "should return only after #broadcast or #signal are called" do
48
+ condition_variable = QuackConcurrency::SafeConditionVariable.new
49
+ mutex = Mutex.new
50
+ thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
51
+ sleep 1
52
+ thread.run
53
+ sleep 1
54
+ expect(thread.alive?).to be true
55
+ condition_variable.broadcast
56
+ sleep 1
57
+ expect(thread.alive?).to be false
58
+ end
59
+ end
60
+ end
61
+
62
+ context "when called with a timeout" do
63
+ context "of nil" do
64
+ it "should return only after #broadcast or #signal are called" do
65
+ condition_variable = QuackConcurrency::SafeConditionVariable.new
66
+ mutex = Mutex.new
67
+ thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
68
+ sleep 1
69
+ expect(thread.alive?).to be true
70
+ condition_variable.broadcast
71
+ sleep 1
72
+ expect(thread.alive?).to be false
73
+ end
74
+ end
75
+ context "of Float::INFINITY" do
76
+ it "should return only after #broadcast or #signal are called" do
77
+ condition_variable = QuackConcurrency::SafeConditionVariable.new
78
+ mutex = Mutex.new
79
+ thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex) } }
80
+ sleep 1
81
+ expect(thread.alive?).to be true
82
+ condition_variable.broadcast
83
+ sleep 1
84
+ expect(thread.alive?).to be false
85
+ end
86
+ end
87
+ context "of positive Integer" do
88
+ it "should block until timeout reached" do
89
+ condition_variable = QuackConcurrency::SafeConditionVariable.new
90
+ mutex = Mutex.new
91
+ thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex, 2) } }
92
+ sleep 1
93
+ expect(thread.alive?).to be true
94
+ sleep 2
95
+ expect(thread.alive?).to be false
96
+ end
97
+ end
98
+ context "and Thread#run is called before timeout is reached" do
99
+ it "should return only after timeout is reached" do
100
+ condition_variable = QuackConcurrency::SafeConditionVariable.new
101
+ mutex = Mutex.new
102
+ thread = Thread.new { mutex.synchronize { condition_variable.wait(mutex, 3) } }
103
+ sleep 1
104
+ thread.run
105
+ sleep 1
106
+ expect(thread.alive?).to be true
107
+ sleep 2
108
+ expect(thread.alive?).to be false
109
+ end
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ end
@@ -0,0 +1,197 @@
1
+ require 'quack_concurrency'
2
+
3
+ describe QuackConcurrency::SafeSleeper do
4
+
5
+ def timer(&block)
6
+ start_time = Time.now
7
+ yield(start_time)
8
+ time_elapsed = Time.now - start_time
9
+ end
10
+
11
+ def delay(units = 1)
12
+ sleep units
13
+ end
14
+
15
+ def expect_time(actual_time, expected_time)
16
+ expect(actual_time).to be_between(expected_time - 0.5, expected_time + 0.5)
17
+ end
18
+
19
+ def sleep_test(sleep_duration: , timeout: false, wake_at: nil, sleep_at: 0)
20
+ sleeper = QuackConcurrency::SafeSleeper.new
21
+ elapsed_time = nil
22
+ sleep_thread = Thread.new do
23
+ delay(sleep_at)
24
+ if timeout == false
25
+ elapsed_time = timer { sleeper.sleep }
26
+ else
27
+ elapsed_time = timer { sleeper.sleep(timeout) }
28
+ end
29
+ end
30
+ wake_thread = Thread.new do
31
+ if wake_at
32
+ delay(wake_at)
33
+ sleeper.wake
34
+ end
35
+ end
36
+ sleep_thread.join
37
+ wake_thread.join
38
+ expect_time(elapsed_time, sleep_duration) # test duration of sleep
39
+ end
40
+
41
+ describe "::new" do
42
+
43
+ context "when called with no arguments" do
44
+ sleeper = nil
45
+ it "should not raise error" do
46
+ expect{ sleeper = QuackConcurrency::SafeSleeper.new }.not_to raise_error
47
+ end
48
+ it "should return a SafeSleeper" do
49
+ expect(sleeper).to be_a(QuackConcurrency::SafeSleeper)
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ describe "#wake" do
56
+
57
+ context "when called" do
58
+ it "should not raise error" do
59
+ sleeper = QuackConcurrency::SafeSleeper.new
60
+ expect{ sleeper.wake }.not_to raise_error
61
+ end
62
+ it "should wake a sleeping thread immediately if #sleep has already been called" do
63
+ sleep_test(sleep_duration: 1, wake_at: 1, sleep_at: 0)
64
+ end
65
+ end
66
+
67
+ context "when called a second time" do
68
+ it "should raise RuntimeError" do
69
+ sleeper = QuackConcurrency::SafeSleeper.new
70
+ sleeper.wake
71
+ expect{ sleeper.wake }.to raise_error(RuntimeError)
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ describe "#sleep" do
78
+
79
+ context "when called" do
80
+ it "should return sleep duration as a Float" do
81
+ sleeper = QuackConcurrency::SafeSleeper.new
82
+ return_value = sleeper.sleep(1)
83
+ expect(return_value).to be_a(Float)
84
+ expect_time(return_value, 1)
85
+ end
86
+ end
87
+
88
+ context "when called after #wake" do
89
+ it "should return immediately" do
90
+ sleep_test(sleep_duration: 0, wake_at: 0, sleep_at: 1)
91
+ end
92
+ end
93
+
94
+ context "when called before #wake" do
95
+ it "should return after #wake is called" do
96
+ sleep_test(sleep_duration: 1, wake_at: 1, sleep_at: 0)
97
+ end
98
+ context "and #wake gets called before timeout is reached" do
99
+ it "should return when #wake is called" do
100
+ sleep_test(sleep_duration: 1, timeout: 2, wake_at: 1, sleep_at: 0)
101
+ end
102
+ end
103
+ context "and #wake gets called after timeout is reached" do
104
+ it "should return when timeout is reached" do
105
+ sleep_test(sleep_duration: 1, timeout: 1, wake_at: 2, sleep_at: 0)
106
+ end
107
+ end
108
+ end
109
+
110
+ context "when called with timeout" do
111
+ context "of nil" do
112
+ it "should return only after #wake is called" do
113
+ sleep_test(sleep_duration: 1, timeout: nil, wake_at: 1, sleep_at: 0)
114
+ end
115
+ end
116
+ context "of Float::INFINITY" do
117
+ it "should return only after #wake is called" do
118
+ sleep_test(sleep_duration: 1, timeout: Float::INFINITY, wake_at: 1, sleep_at: 0)
119
+ end
120
+ end
121
+ context "of non Numeric value" do
122
+ it "should raise TypeError" do
123
+ sleeper = QuackConcurrency::SafeSleeper.new
124
+ expect{ sleeper.sleep('1') }.to raise_error(TypeError)
125
+ end
126
+ end
127
+ context "of negative Numeric value" do
128
+ it "should raise ArgumentError" do
129
+ sleeper = QuackConcurrency::SafeSleeper.new
130
+ expect{ sleeper.sleep(-1) }.to raise_error(ArgumentError)
131
+ end
132
+ end
133
+ context "of Numeric value" do
134
+ it "should return after given timeout" do
135
+ sleep_test(sleep_duration: 1, timeout: 1, sleep_at: 0)
136
+ end
137
+ end
138
+ context "and Thread#run is called before timeout is reached" do
139
+ it "should return only after timeout is reached" do
140
+ sleeper = QuackConcurrency::SafeSleeper.new
141
+ elapsed_time = nil
142
+ sleep_thread = Thread.new do
143
+ elapsed_time = timer { sleeper.sleep(2) }
144
+ end
145
+ run_thread = Thread.new do
146
+ delay 1
147
+ sleep_thread.run
148
+ end
149
+ sleep_thread.join
150
+ run_thread.join
151
+ expect_time(elapsed_time, 2)
152
+ end
153
+ end
154
+ end
155
+
156
+ context "when called before Thread#run" do
157
+ it "should return only after #wake is called" do
158
+ sleeper = QuackConcurrency::SafeSleeper.new
159
+ elapsed_time = nil
160
+ sleep_thread = Thread.new do
161
+ elapsed_time = timer { sleeper.sleep }
162
+ end
163
+ wake_thread = Thread.new do
164
+ delay 2
165
+ sleeper.wake
166
+ end
167
+ run_thread = Thread.new do
168
+ delay 1
169
+ sleep_thread.run
170
+ end
171
+ sleep_thread.join
172
+ wake_thread.join
173
+ run_thread.join
174
+ expect_time(elapsed_time, 2)
175
+ end
176
+ end
177
+
178
+ context "when called a second time" do
179
+ it "should raise RuntimeError" do
180
+ sleeper = QuackConcurrency::SafeSleeper.new
181
+ sleeper.wake
182
+ sleeper.sleep
183
+ expect{ sleeper.sleep }.to raise_error(RuntimeError)
184
+ end
185
+ end
186
+
187
+ context "when called with no way to wake up" do
188
+ it "should raise fatal" do
189
+ sleeper = QuackConcurrency::SafeSleeper.new
190
+ FatalError = ObjectSpace.each_object(Class).find { |klass| klass < Exception && klass.inspect == 'fatal' }
191
+ expect{ sleeper.sleep }.to raise_error(FatalError)
192
+ end
193
+ end
194
+
195
+ end
196
+
197
+ end
@@ -0,0 +1,197 @@
1
+ require 'quack_concurrency'
2
+
3
+ describe QuackConcurrency::Sleeper do
4
+
5
+ def timer(&block)
6
+ start_time = Time.now
7
+ yield(start_time)
8
+ time_elapsed = Time.now - start_time
9
+ end
10
+
11
+ def delay(units = 1)
12
+ sleep units
13
+ end
14
+
15
+ def expect_time(actual_time, expected_time)
16
+ expect(actual_time).to be_between(expected_time - 0.5, expected_time + 0.5)
17
+ end
18
+
19
+ def sleep_test(sleep_duration: , timeout: false, wake_at: nil, sleep_at: 0)
20
+ sleeper = QuackConcurrency::Sleeper.new
21
+ elapsed_time = nil
22
+ sleep_thread = Thread.new do
23
+ delay(sleep_at)
24
+ if timeout == false
25
+ elapsed_time = timer { sleeper.sleep }
26
+ else
27
+ elapsed_time = timer { sleeper.sleep(timeout) }
28
+ end
29
+ end
30
+ wake_thread = Thread.new do
31
+ if wake_at
32
+ delay(wake_at)
33
+ sleeper.wake
34
+ end
35
+ end
36
+ sleep_thread.join
37
+ wake_thread.join
38
+ expect_time(elapsed_time, sleep_duration) # test duration of sleep
39
+ end
40
+
41
+ describe "::new" do
42
+
43
+ context "when called with no arguments" do
44
+ sleeper = nil
45
+ it "should not raise error" do
46
+ expect{ sleeper = QuackConcurrency::Sleeper.new }.not_to raise_error
47
+ end
48
+ it "should return a Sleeper" do
49
+ expect(sleeper).to be_a(QuackConcurrency::Sleeper)
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ describe "#wake" do
56
+
57
+ context "when called" do
58
+ it "should not raise error" do
59
+ sleeper = QuackConcurrency::Sleeper.new
60
+ expect{ sleeper.wake }.not_to raise_error
61
+ end
62
+ it "should wake a sleeping thread immediately if #sleep has already been called" do
63
+ sleep_test(sleep_duration: 1, wake_at: 1, sleep_at: 0)
64
+ end
65
+ end
66
+
67
+ context "when called a second time" do
68
+ it "should raise RuntimeError" do
69
+ sleeper = QuackConcurrency::Sleeper.new
70
+ sleeper.wake
71
+ expect{ sleeper.wake }.to raise_error(RuntimeError)
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ describe "#sleep" do
78
+
79
+ context "when called" do
80
+ it "should return sleep duration as a Float" do
81
+ sleeper = QuackConcurrency::Sleeper.new
82
+ return_value = sleeper.sleep(1)
83
+ expect(return_value).to be_a(Float)
84
+ expect_time(return_value, 1)
85
+ end
86
+ end
87
+
88
+ context "when called after #wake" do
89
+ it "should return immediately" do
90
+ sleep_test(sleep_duration: 0, wake_at: 0, sleep_at: 1)
91
+ end
92
+ end
93
+
94
+ context "when called before #wake" do
95
+ it "should return after #wake is called" do
96
+ sleep_test(sleep_duration: 1, wake_at: 1, sleep_at: 0)
97
+ end
98
+ context "and #wake gets called before timeout is reached" do
99
+ it "should return when #wake is called" do
100
+ sleep_test(sleep_duration: 1, timeout: 2, wake_at: 1, sleep_at: 0)
101
+ end
102
+ end
103
+ context "and #wake gets called after timeout is reached" do
104
+ it "should return when timeout is reached" do
105
+ sleep_test(sleep_duration: 1, timeout: 1, wake_at: 2, sleep_at: 0)
106
+ end
107
+ end
108
+ end
109
+
110
+ context "when called with timeout" do
111
+ context "of nil" do
112
+ it "should return only after #wake is called" do
113
+ sleep_test(sleep_duration: 1, timeout: nil, wake_at: 1, sleep_at: 0)
114
+ end
115
+ end
116
+ context "of Float::INFINITY" do
117
+ it "should return only after #wake is called" do
118
+ sleep_test(sleep_duration: 1, timeout: Float::INFINITY, wake_at: 1, sleep_at: 0)
119
+ end
120
+ end
121
+ context "of non Numeric value" do
122
+ it "should raise TypeError" do
123
+ sleeper = QuackConcurrency::Sleeper.new
124
+ expect{ sleeper.sleep('1') }.to raise_error(TypeError)
125
+ end
126
+ end
127
+ context "of negative Numeric value" do
128
+ it "should raise ArgumentError" do
129
+ sleeper = QuackConcurrency::Sleeper.new
130
+ expect{ sleeper.sleep(-1) }.to raise_error(ArgumentError)
131
+ end
132
+ end
133
+ context "of Numeric value" do
134
+ it "should return after given timeout" do
135
+ sleep_test(sleep_duration: 1, timeout: 1, sleep_at: 0)
136
+ end
137
+ end
138
+ context "and Thread#run is called before timeout is reached" do
139
+ it "should return when after Thread#run is called" do
140
+ sleeper = QuackConcurrency::Sleeper.new
141
+ elapsed_time = nil
142
+ sleep_thread = Thread.new do
143
+ elapsed_time = timer { sleeper.sleep(2) }
144
+ end
145
+ run_thread = Thread.new do
146
+ delay 1
147
+ sleep_thread.run
148
+ end
149
+ sleep_thread.join
150
+ run_thread.join
151
+ expect_time(elapsed_time, 1)
152
+ end
153
+ end
154
+ end
155
+
156
+ context "when called before Thread#run" do
157
+ it "should return when after Thread#run is called" do
158
+ sleeper = QuackConcurrency::Sleeper.new
159
+ elapsed_time = nil
160
+ sleep_thread = Thread.new do
161
+ elapsed_time = timer { sleeper.sleep }
162
+ end
163
+ wake_thread = Thread.new do
164
+ delay 2
165
+ sleeper.wake
166
+ end
167
+ run_thread = Thread.new do
168
+ delay 1
169
+ sleep_thread.run
170
+ end
171
+ sleep_thread.join
172
+ wake_thread.join
173
+ run_thread.join
174
+ expect_time(elapsed_time, 1)
175
+ end
176
+ end
177
+
178
+ context "when called a second time" do
179
+ it "should raise RuntimeError" do
180
+ sleeper = QuackConcurrency::Sleeper.new
181
+ sleeper.wake
182
+ sleeper.sleep
183
+ expect{ sleeper.sleep }.to raise_error(RuntimeError)
184
+ end
185
+ end
186
+
187
+ context "when called with no way to wake up" do
188
+ it "should raise fatal" do
189
+ sleeper = QuackConcurrency::Sleeper.new
190
+ FatalError = ObjectSpace.each_object(Class).find { |klass| klass < Exception && klass.inspect == 'fatal' }
191
+ expect{ sleeper.sleep }.to raise_error(FatalError)
192
+ end
193
+ end
194
+
195
+ end
196
+
197
+ end