From 2366a0d391c777b54eded64157ef40b1a68c7f2e Mon Sep 17 00:00:00 2001 From: Jerry D'Antonio Date: Thu, 1 Oct 2015 13:30:31 -0400 Subject: [PATCH] Upgraded to latest version of concurrent-ruby * Completely removed local ReadWriteLocal * Replaced with Concurrent::ReadWriteLock * Updated gemspec * Updated all Gemfiles and templates --- Gemfile | 4 +- lib/volt/server.rb | 2 +- lib/volt/server/forking_server.rb | 2 +- lib/volt/utils/read_write_lock.rb | 171 ------------------------------ spec/apps/kitchen_sink/Gemfile | 4 +- templates/newgem/Gemfile.tt | 6 +- templates/project/Gemfile.tt | 4 +- volt.gemspec | 5 +- 8 files changed, 13 insertions(+), 185 deletions(-) delete mode 100644 lib/volt/utils/read_write_lock.rb diff --git a/Gemfile b/Gemfile index 601d196b..44099cb0 100644 --- a/Gemfile +++ b/Gemfile @@ -46,8 +46,8 @@ group :development, :test do end platform :mri do - # The implementation of ReadWriteLock in Volt uses concurrent ruby and ext helps performance. - gem 'concurrent-ruby-ext' + # ReadWriteLock in Volt comes from concurrent ruby and ext helps performance. + gem 'concurrent-ruby-ext', '~> 1.0.0.pre3', '< 2.0.0' # For debugging gem 'pry-byebug', '~> 2.0.0', require: false diff --git a/lib/volt/server.rb b/lib/volt/server.rb index c8400fe2..bd8ebcdb 100644 --- a/lib/volt/server.rb +++ b/lib/volt/server.rb @@ -4,6 +4,7 @@ require 'rack' require 'sass' +require 'concurrent' require 'volt/utils/tilt_patch' require 'volt' @@ -13,7 +14,6 @@ require 'volt/server/template_handlers/sprockets_component_handler' require 'volt/server/websocket/websocket_handler' -require 'volt/utils/read_write_lock' require 'volt/server/forking_server' require 'volt/server/websocket/rack_server_adaptor' diff --git a/lib/volt/server/forking_server.rb b/lib/volt/server/forking_server.rb index 838adf6a..06d5ce13 100644 --- a/lib/volt/server/forking_server.rb +++ b/lib/volt/server/forking_server.rb @@ -17,7 +17,7 @@ module Volt class ForkingServer def initialize(server) # A read write lock for accessing and creating the lock - @child_lock = ReadWriteLock.new + @child_lock = Concurrent::ReadWriteLock.new # Trap exit at_exit do diff --git a/lib/volt/utils/read_write_lock.rb b/lib/volt/utils/read_write_lock.rb deleted file mode 100644 index 15fbe281..00000000 --- a/lib/volt/utils/read_write_lock.rb +++ /dev/null @@ -1,171 +0,0 @@ -# Currently the released gem version of the concurrent-ruby gem does not have the ReadWriteLock -# relased in it (it is on master). - -# Ruby read-write lock implementation -# Allows any number of concurrent readers, but only one concurrent writer -# (And if the "write" lock is taken, any readers who come along will have to wait) - -# If readers are already active when a writer comes along, the writer will wait for -# all the readers to finish before going ahead -# But any additional readers who come when the writer is already waiting, will also -# wait (so writers are not starved) - -# Written by Alex Dowad -# Bug fixes contributed by Alex Kliuchnikau -# Suggestion on avoiding reader starvation contributed by maniek -# Thanks to Doug Lea for java.util.concurrent.ReentrantReadWriteLock (used for inspiration) - -# Usage: -# lock = ReadWriteLock.new -# lock.with_read_lock { data.retrieve } -# lock.with_write_lock { data.modify! } - -# NOTE: DON'T try to acquire the write lock while already holding a read lock! -# OR try to acquire the write lock while you already have it -# It will lead to deadlock - -# Implementation notes: -# A goal is to make the uncontended path for both readers/writers lock-free -# Only if there is reader-writer or writer-writer contention, should locks be used -# Internal state is represented by a single integer ("counter"), and updated -# using atomic compare-and-swap operations -# When the counter is 0, the lock is free -# Each reader increments the counter by 1 when acquiring a read lock -# (and decrements by 1 when releasing the read lock) -# The counter is increased by (1 << 15) for each writer waiting to acquire the -# write lock, and by (1 << 30) if the write lock is taken - -require 'rubygems' # for compatibility with JRuby, MRI 1.8, etc -require 'concurrent/atomic' -require 'thread' - -class ReadWriteLock - def initialize - @counter = Concurrent::Atomic.new(0) # single integer which represents lock state - @reader_q = ConditionVariable.new # queue for waiting readers - @reader_mutex = Mutex.new # to protect reader queue - @writer_q = ConditionVariable.new # queue for waiting writers - @writer_mutex = Mutex.new # to protect writer queue - end - - WAITING_WRITER = 1 << 15 - RUNNING_WRITER = 1 << 30 - MAX_READERS = WAITING_WRITER - 1 - MAX_WRITERS = RUNNING_WRITER - MAX_READERS - 1 - - def with_read_lock - acquire_read_lock - result = yield - release_read_lock - result - end - - def with_write_lock - acquire_write_lock - result = yield - release_write_lock - result - end - - def acquire_read_lock - loop do - c = @counter.value - fail 'Too many reader threads!' if (c & MAX_READERS) == MAX_READERS - - # If a writer is waiting when we first queue up, we need to wait - if c >= WAITING_WRITER - # But it is possible that the writer could finish and decrement @counter right here... - @reader_mutex.synchronize do - # So check again inside the synchronized section - @reader_q.wait(@reader_mutex) if @counter.value >= WAITING_WRITER - end - - # after a reader has waited once, they are allowed to "barge" ahead of waiting writers - # but if a writer is *running*, the reader still needs to wait (naturally) - loop do - c = @counter.value - if c >= RUNNING_WRITER - @reader_mutex.synchronize do - @reader_q.wait(@reader_mutex) if @counter.value >= RUNNING_WRITER - end - else - return if @counter.compare_and_swap(c, c + 1) - end - end - else - break if @counter.compare_and_swap(c, c + 1) - end - end - end - - def release_read_lock - loop do - c = @counter.value - if @counter.compare_and_swap(c, c - 1) - # If one or more writers were waiting, and we were the last reader, wake a writer up - if c >= WAITING_WRITER && (c & MAX_READERS) == 1 - @writer_mutex.synchronize { @writer_q.signal } - end - break - end - end - end - - def acquire_write_lock - loop do - c = @counter.value - fail 'Too many writers!' if (c & MAX_WRITERS) == MAX_WRITERS - - if c == 0 # no readers OR writers running - # if we successfully swap the RUNNING_WRITER bit on, then we can go ahead - break if @counter.compare_and_swap(0, RUNNING_WRITER) - elsif @counter.compare_and_swap(c, c + WAITING_WRITER) - loop do - # Now we have successfully incremented, so no more readers will be able to increment - # (they will wait instead) - # However, readers OR writers could decrement right here, OR another writer could increment - @writer_mutex.synchronize do - # So we have to do another check inside the synchronized section - # If a writer OR reader is running, then go to sleep - c = @counter.value - @writer_q.wait(@writer_mutex) if (c >= RUNNING_WRITER) || ((c & MAX_READERS) > 0) - end - - # We just came out of a wait - # If we successfully turn the RUNNING_WRITER bit on with an atomic swap, - # Then we are OK to stop waiting and go ahead - # Otherwise go back and wait again - c = @counter.value - break if (c < RUNNING_WRITER) && - ((c & MAX_READERS) == 0) && - @counter.compare_and_swap(c, c + RUNNING_WRITER - WAITING_WRITER) - end - break - end - end - end - - def release_write_lock - loop do - c = @counter.value - if @counter.compare_and_swap(c, c - RUNNING_WRITER) - @reader_mutex.synchronize { @reader_q.broadcast } - @writer_mutex.synchronize { @writer_q.signal } if (c & MAX_WRITERS) > 0 # if any writers are waiting... - break - end - end - end - - def to_s - c = @counter.value - s = if c >= RUNNING_WRITER - '1 writer running, ' - elsif (c & MAX_READERS) > 0 - "#{c & MAX_READERS} readers running, " - else - '' - end - - "#" - end -end diff --git a/spec/apps/kitchen_sink/Gemfile b/spec/apps/kitchen_sink/Gemfile index dbd25078..f0c27744 100644 --- a/spec/apps/kitchen_sink/Gemfile +++ b/spec/apps/kitchen_sink/Gemfile @@ -45,8 +45,8 @@ end # Server for MRI platform :mri do - # The implementation of ReadWriteLock in Volt uses concurrent ruby and ext helps performance. - gem 'concurrent-ruby-ext', '~> 0.8.0' + # ReadWriteLock in Volt comes from concurrent ruby and ext helps performance. + gem 'concurrent-ruby-ext', '~> 1.0.0.pre3', '< 2.0.0' # Thin is the default volt server, you Puma is also supported gem 'thin', '~> 1.6.0' diff --git a/templates/newgem/Gemfile.tt b/templates/newgem/Gemfile.tt index ea08595d..a1ead2f6 100644 --- a/templates/newgem/Gemfile.tt +++ b/templates/newgem/Gemfile.tt @@ -5,11 +5,11 @@ gemspec # Optional Gems for testing/dev -# The implementation of ReadWriteLock in Volt uses concurrent ruby and ext helps performance. -gem 'concurrent-ruby-ext', '~> 0.8.0' +# ReadWriteLock in Volt comes from concurrent ruby and ext helps performance. +gem 'concurrent-ruby-ext', '~> 1.0.0.pre3', '< 2.0.0' # For mongo (optional) gem 'bson_ext', '~> 1.9.0' # Gems you use for development should be added to the gemspec file as -# development dependencies. \ No newline at end of file +# development dependencies. diff --git a/templates/project/Gemfile.tt b/templates/project/Gemfile.tt index 4621e1a9..acb40062 100644 --- a/templates/project/Gemfile.tt +++ b/templates/project/Gemfile.tt @@ -39,8 +39,8 @@ end # Server for MRI platform :mri, :mingw, :x64_mingw do - # The implementation of ReadWriteLock in Volt uses concurrent ruby and ext helps performance. - gem 'concurrent-ruby-ext', '~> 0.8.0' + # ReadWriteLock in Volt comes from concurrent ruby and ext helps performance. + gem 'concurrent-ruby-ext', '~> 1.0.0.pre3', '< 2.0.0' # Thin is the default volt server, Puma is also supported gem 'thin', '~> 1.6.0' diff --git a/volt.gemspec b/volt.gemspec index 99166aad..f430fe63 100644 --- a/volt.gemspec +++ b/volt.gemspec @@ -31,9 +31,8 @@ Gem::Specification.new do |spec| spec.add_dependency 'faye-websocket', '~> 0.10.0' spec.add_dependency 'sprockets-helpers', '~> 1.2.1' - # Locking down concurrent-ruby because one currently used feature is going to - # be deprecated (which we need to build a work around for) - spec.add_dependency 'concurrent-ruby', '= 0.8.0' + # 1.0.0 will be released before 15 November 2015 + spec.add_dependency 'concurrent-ruby', '~> 1.0.0.pre3', '< 2.0.0' # For user passwords spec.add_dependency 'bcrypt', '~> 3.1.9'