1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
|
require 'timeout'
module Concurrent
RSpec.describe Synchronization do
RSpec.shared_examples :attr_volatile do
specify 'older writes are always visible' do
store = store()
store.not_volatile = 0
store.volatile = 0
@stop = false
in_thread do
Thread.abort_on_exception = true
1000000000.times do |i|
store.not_volatile = i
store.volatile = i
break if @stop # on JRuby this is not kill-able loop
end
end
t2 = in_thread do
Thread.abort_on_exception = true
10.times.map do
Thread.pass
volatile = store.volatile
not_volatile = store.not_volatile
not_volatile >= volatile
end
end
expect(t2.value.all?).to eq true
@stop = true
end
end
describe Synchronization::Object do
class AAClass < Synchronization::Object
end
class ABClass < AAClass
safe_initialization!
end
class ACClass < ABClass
end
class ADClass < ACClass
safe_initialization!
end
it 'does not ensure visibility when not needed' do
expect_any_instance_of(AAClass).not_to receive(:full_memory_barrier)
AAClass.new
end
it "does ensure visibility when specified" do
expect_any_instance_of(ABClass).to receive(:full_memory_barrier)
ABClass.new
end
it "does ensure visibility when specified in a parent" do
expect_any_instance_of(ACClass).to receive(:full_memory_barrier)
ACClass.new
end
it "does ensure visibility once when specified in child again" do
expect_any_instance_of(ADClass).to receive(:full_memory_barrier)
ADClass.new
end
# TODO (pitr 12-Sep-2015): give a whole gem a pass to find classes with final fields without using the convention and migrate
Synchronization::Object.ensure_safe_initialization_when_final_fields_are_present
class VolatileFieldClass < Synchronization::Object
attr_volatile :volatile
attr_accessor :not_volatile
end
let(:store) { VolatileFieldClass.new }
it_should_behave_like :attr_volatile
end
describe Synchronization::LockableObject do
class BClass < Synchronization::LockableObject
safe_initialization!
attr_volatile :volatile
attr_accessor :not_volatile
def initialize(value = nil)
super()
@Final = value
ns_initialize
end
def final
@Final
end
def count
synchronize { @count += 1 }
end
def wait(timeout = nil)
synchronize { ns_wait(timeout) }
end
public :synchronize
private
def ns_initialize
@count = 0
end
end
subject { BClass.new }
describe '#wait' do
it 'puts the current thread to sleep' do
t1 = in_thread do
Thread.abort_on_exception = true
subject.wait
end
t2 = in_thread { Thread.pass until t1.status == 'sleep' }
join_with t2
end
it 'allows the sleeping thread to be killed' do
t = in_thread do
Thread.abort_on_exception = true
subject.wait rescue nil
end
sleep 0.1
t.kill
sleep 0.1
expect(t.join).not_to eq nil
expect(t.alive?).to eq false
end
it 'releases the lock on the current object' do
t1 = in_thread do
# #wait should release lock, even if it was already held on entry
t2 = in_thread { subject.wait }
Thread.pass until t2.status == 'sleep'
subject.synchronize {} # it will deadlock here if #wait doesn't release lock
t2
end
join_with t1
repeat_until_success { expect(t1.value.status).to eq 'sleep' }
end
it 'can be called from within a #synchronize block' do
t1 = in_thread do
t2 = in_thread { subject.synchronize { subject.wait } }
Thread.pass until t2.status == 'sleep'
subject.synchronize {} # it will deadlock here if #wait doesn't release lock
t2
end
join_with t1
repeat_until_success { expect(t1.value.status).to eq 'sleep' }
end
end
describe '#synchronize' do
it 'allows only one thread to execute count' do
threads = 10.times.map { in_thread(subject) { 100.times { subject.count } } }
threads.each(&:join)
expect(subject.count).to eq 1001
end
end
describe 'signaling' do
pending 'for now pending, tested pretty well by Event'
end
specify 'final field always visible' do
store = BClass.new 'asd'
done = CountDownLatch.new
in_thread do
1000000000.times do |i|
store = BClass.new i.to_s
break if done.count == 0
end
end
in_thread do
10.times do
expect(store.final).not_to be_nil
Thread.pass
end
done.count_down
end
end
let(:store) { BClass.new }
it_should_behave_like :attr_volatile
end
describe 'Concurrent::Synchronization::Volatile module' do
class BareClass
include Synchronization::Volatile
attr_volatile :volatile
attr_accessor :not_volatile
end
let(:store) { BareClass.new }
it_should_behave_like :attr_volatile
end
describe 'attr_atomic' do
specify do
a = Class.new(Synchronization::Object) do
attr_atomic :a
def initialize(*rest)
super
self.a = :a
end
end
b = Class.new(a) do
attr_atomic :b
def initialize
super
self.b = :b
end
end
instance = b.new
expect(instance.a).to be == :a
expect(instance.b).to be == :b
end
end
end
end
|