diff --git a/spec/std/crystal/lock_spec.cr b/spec/std/crystal/lock_spec.cr new file mode 100644 index 000000000000..ea6801d93a71 --- /dev/null +++ b/spec/std/crystal/lock_spec.cr @@ -0,0 +1,66 @@ +require "crystal/lock" +require "../sync/spec_helper" + +describe Crystal::Lock do + it "#lock raises on deadlock" do + lock = Crystal::Lock.new + called = 0 + + lock.lock do + called = 1 + + expect_raises(Sync::Error::Deadlock) do + lock.lock { called = 2 } + end + end + + called.should eq(1) + end + + it "synchronizes" do + lock = Crystal::Lock.new + wg = WaitGroup.new + + ary = [] of Int32 + counter = Atomic(Int64).new(0) + + # readers can run concurrently, but are mutually exclusive to writers (the + # array can be safely read from): + + 10.times do + spawn(name: "reader") do + 100.times do + lock.rlock do + ary.each { counter.add(1) } + end + Fiber.yield + end + end + end + + # writers are mutually exclusive: they can safely mutate the array + + 5.times do + wg.spawn(name: "writer:increment") do + 100.times do + lock.lock { 100.times { ary << ary.size } } + Fiber.yield + end + end + end + + 4.times do + wg.spawn(name: "writer:decrement") do + 100.times do + lock.lock { 100.times { ary.pop? } } + Fiber.yield + end + end + end + + wg.wait + + ary.should eq((0..(ary.size - 1)).to_a) + counter.lazy_get.should be > 0 + end +end diff --git a/src/crystal/lock.cr b/src/crystal/lock.cr new file mode 100644 index 000000000000..5b7a65acac5b --- /dev/null +++ b/src/crystal/lock.cr @@ -0,0 +1,41 @@ +require "sync/mu" + +module Crystal + # :nodoc: + # + # Always checked alternative to Sync::Mutex and Sync::RWLock in a single and + # significantly smaller type (no crystal type id, no lock type, no reentrancy + # counter). + struct Lock + @mu = Sync::MU.new + @locked_by : Fiber? + + def lock(&) + unless @mu.try_lock? + raise Sync::Error::Deadlock.new if deadlock? + @mu.lock_slow + end + + begin + @locked_by = Fiber.current + yield + ensure + @locked_by = nil + @mu.unlock + end + end + + def rlock(&) + @mu.rlock + begin + yield + ensure + @mu.runlock + end + end + + private def deadlock? + @locked_by == Fiber.current + end + end +end