quack_concurrency 0.5.4 → 0.6.0

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.
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