class ShareLockTest
Public Instance Methods
setup()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 8 def setup @lock = ActiveSupport::Concurrency::ShareLock.new end
test_compatible_exclusives_cooperate_to_both_proceed()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 275 def test_compatible_exclusives_cooperate_to_both_proceed ready = Concurrent::CyclicBarrier.new(2) done = Concurrent::CyclicBarrier.new(2) threads = 2.times.map do Thread.new do @lock.sharing do ready.wait @lock.exclusive(purpose: :x, compatible: [:x], after_compatible: [:x]) {} done.wait end end end assert_threads_not_stuck threads end
test_exclusive_blocks_sharing()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 34 def test_exclusive_blocks_sharing with_thread_waiting_in_lock_section(:exclusive) do |exclusive_thread_release_latch| sharing_thread = Thread.new { @lock.sharing {} } assert_threads_stuck_but_releasable_by_latch sharing_thread, exclusive_thread_release_latch end end
test_exclusive_conflicting_purpose()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 115 def test_exclusive_conflicting_purpose [true, false].each do |use_upgrading| with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| begin together = Concurrent::CyclicBarrier.new(2) conflicting_exclusive_threads = [ Thread.new do @lock.send(use_upgrading ? :sharing : :tap) do together.wait @lock.exclusive(purpose: :red, compatible: [:green, :purple]) {} end end, Thread.new do @lock.send(use_upgrading ? :sharing : :tap) do together.wait @lock.exclusive(purpose: :blue, compatible: [:green]) {} end end ] assert_threads_stuck conflicting_exclusive_threads # wait for threads to get into their respective `exclusive {}` blocks # This thread will be stuck as long as any other thread is in # a sharing block. While it's blocked, it holds no lock, so it # doesn't interfere with any other attempts. no_purpose_thread = Thread.new do @lock.exclusive {} end assert_threads_stuck no_purpose_thread # This thread is compatible with both of the "primary" # attempts above. It's initially stuck on the outer share # lock, but as soon as that's released, it can run -- # regardless of whether those threads hold share locks. compatible_thread = Thread.new do @lock.exclusive(purpose: :green, compatible: []) {} end assert_threads_stuck compatible_thread assert_threads_stuck conflicting_exclusive_threads sharing_thread_release_latch.count_down assert_threads_not_stuck compatible_thread # compatible thread is now able to squeak through if use_upgrading # The "primary" threads both each hold a share lock, and are # mutually incompatible; they're still stuck. assert_threads_stuck conflicting_exclusive_threads # The thread without a specified purpose is also stuck; it's # not compatible with anything. assert_threads_stuck no_purpose_thread else # As the primaries didn't hold a share lock, as soon as the # outer one was released, all the exclusive locks are free # to be acquired in turn. assert_threads_not_stuck conflicting_exclusive_threads assert_threads_not_stuck no_purpose_thread end ensure conflicting_exclusive_threads.each(&:kill) no_purpose_thread.kill end end end end
test_exclusive_matching_purpose()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 78 def test_exclusive_matching_purpose [true, false].each do |use_upgrading| with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| exclusive_threads = (1..2).map do Thread.new do @lock.send(use_upgrading ? :sharing : :tap) do @lock.exclusive(purpose: :load, compatible: [:load, :unload]) {} end end end assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch end end end
test_exclusive_ordering()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 184 def test_exclusive_ordering scratch_pad = [] scratch_pad_mutex = Mutex.new load_params = [:load, [:load]] unload_params = [:unload, [:unload, :load]] all_sharing = Concurrent::CyclicBarrier.new(4) [load_params, load_params, unload_params, unload_params].permutation do |thread_params| with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| threads = thread_params.map do |purpose, compatible| Thread.new do @lock.sharing do all_sharing.wait @lock.exclusive(purpose: purpose, compatible: compatible) do scratch_pad_mutex.synchronize { scratch_pad << purpose } end end end end sleep(0.01) scratch_pad_mutex.synchronize { assert_empty scratch_pad } sharing_thread_release_latch.count_down assert_threads_not_stuck threads scratch_pad_mutex.synchronize do assert_equal [:load, :load, :unload, :unload], scratch_pad scratch_pad.clear end end end end
test_killed_thread_loses_lock()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 94 def test_killed_thread_loses_lock with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| thread = Thread.new do @lock.sharing do @lock.exclusive {} end end assert_threads_stuck thread thread.kill sharing_thread_release_latch.count_down thread = Thread.new do @lock.exclusive {} end assert_threads_not_stuck thread end end
test_manual_incompatible_yield()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 318 def test_manual_incompatible_yield ready = Concurrent::CyclicBarrier.new(2) done = Concurrent::CyclicBarrier.new(2) threads = [ Thread.new do @lock.sharing do ready.wait @lock.exclusive(purpose: :x) {} done.wait end end, Thread.new do @lock.sharing do ready.wait @lock.yield_shares(compatible: [:y]) do done.wait end end end, ] assert_threads_stuck threads ensure threads.each(&:kill) if threads end
test_manual_recursive_yield()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 346 def test_manual_recursive_yield ready = Concurrent::CyclicBarrier.new(2) done = Concurrent::CyclicBarrier.new(2) do_nesting = Concurrent::CountDownLatch.new threads = [ Thread.new do @lock.sharing do ready.wait @lock.exclusive(purpose: :x) {} done.wait end end, Thread.new do @lock.sharing do @lock.yield_shares(compatible: [:x]) do @lock.sharing do ready.wait do_nesting.wait @lock.yield_shares(compatible: [:x, :y]) do done.wait end end end end end ] assert_threads_stuck threads do_nesting.count_down assert_threads_not_stuck threads end
test_manual_recursive_yield_cannot_expand_outer_compatible()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 381 def test_manual_recursive_yield_cannot_expand_outer_compatible ready = Concurrent::CyclicBarrier.new(2) do_compatible_nesting = Concurrent::CountDownLatch.new in_compatible_nesting = Concurrent::CountDownLatch.new incompatible_thread = Thread.new do @lock.sharing do ready.wait @lock.exclusive(purpose: :x) {} end end yield_shares_thread = Thread.new do @lock.sharing do ready.wait @lock.yield_shares(compatible: [:y]) do do_compatible_nesting.wait @lock.sharing do @lock.yield_shares(compatible: [:x, :y]) do in_compatible_nesting.wait end end end end end assert_threads_stuck incompatible_thread do_compatible_nesting.count_down assert_threads_stuck incompatible_thread in_compatible_nesting.count_down assert_threads_not_stuck [yield_shares_thread, incompatible_thread] end
test_manual_recursive_yield_restores_previous_compatible()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 414 def test_manual_recursive_yield_restores_previous_compatible ready = Concurrent::CyclicBarrier.new(2) do_nesting = Concurrent::CountDownLatch.new after_nesting = Concurrent::CountDownLatch.new incompatible_thread = Thread.new do ready.wait @lock.exclusive(purpose: :z) {} end recursive_yield_shares_thread = Thread.new do @lock.sharing do ready.wait @lock.yield_shares(compatible: [:y]) do do_nesting.wait @lock.sharing do @lock.yield_shares(compatible: [:x, :y]) {} end after_nesting.wait end end end assert_threads_stuck incompatible_thread do_nesting.count_down assert_threads_stuck incompatible_thread compatible_thread = Thread.new do @lock.exclusive(purpose: :y) {} end assert_threads_not_stuck compatible_thread post_nesting_incompatible_thread = Thread.new do @lock.exclusive(purpose: :x) {} end assert_threads_stuck post_nesting_incompatible_thread after_nesting.count_down assert_threads_not_stuck recursive_yield_shares_thread # post_nesting_incompatible_thread can now proceed assert_threads_not_stuck post_nesting_incompatible_thread # assert_threads_not_stuck can now proceed assert_threads_not_stuck incompatible_thread end
test_manual_yield()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 292 def test_manual_yield ready = Concurrent::CyclicBarrier.new(2) done = Concurrent::CyclicBarrier.new(2) threads = [ Thread.new do @lock.sharing do ready.wait @lock.exclusive(purpose: :x) {} done.wait end end, Thread.new do @lock.sharing do ready.wait @lock.yield_shares(compatible: [:x]) do done.wait end end end, ] assert_threads_not_stuck threads end
test_multiple_exlusives_are_able_to_progress()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 41 def test_multiple_exlusives_are_able_to_progress with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| exclusive_threads = (1..2).map do Thread.new do @lock.exclusive {} end end assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch end end
test_reentrancy()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 12 def test_reentrancy thread = Thread.new do @lock.sharing { @lock.sharing {} } @lock.exclusive { @lock.exclusive {} } end assert_threads_not_stuck thread end
test_sharing_blocks_exclusive()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 26 def test_sharing_blocks_exclusive with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| @lock.exclusive(no_wait: true) { flunk } # polling should fail exclusive_thread = Thread.new { @lock.exclusive {} } assert_threads_stuck_but_releasable_by_latch exclusive_thread, sharing_thread_release_latch end end
test_sharing_doesnt_block()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 20 def test_sharing_doesnt_block with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_latch| assert_threads_not_stuck(Thread.new { @lock.sharing {} }) end end
test_sharing_is_upgradeable_to_exclusive()
click to toggle source
# File activesupport/test/share_lock_test.rb, line 53 def test_sharing_is_upgradeable_to_exclusive upgrading_thread = Thread.new do @lock.sharing do @lock.exclusive {} end end assert_threads_not_stuck upgrading_thread end
Private Instance Methods
with_thread_waiting_in_lock_section(lock_section) { |section_release| ... }
click to toggle source
# File activesupport/test/share_lock_test.rb, line 562 def with_thread_waiting_in_lock_section(lock_section) in_section = Concurrent::CountDownLatch.new section_release = Concurrent::CountDownLatch.new stuck_thread = Thread.new do @lock.send(lock_section) do in_section.count_down section_release.wait end end in_section.wait yield section_release ensure section_release.count_down stuck_thread.join # clean up end