Partially add ActiveSupport and Concurrent Ruby gems.
This commit is contained in:
parent
76b41ba3f7
commit
a4d9b4816d
8
.gitignore
vendored
8
.gitignore
vendored
@ -30,8 +30,14 @@
|
|||||||
# Unignore vendored gems
|
# Unignore vendored gems
|
||||||
!**/vendor/bundle-standalone/ruby/*/gems/*/lib
|
!**/vendor/bundle-standalone/ruby/*/gems/*/lib
|
||||||
|
|
||||||
# Ignore backports gem (we don't need all files)
|
# Ignore partially included gems where we don't need all files
|
||||||
|
**/vendor/bundle-standalone/ruby/*/gems/activesupport-*/lib
|
||||||
|
**/vendor/bundle-standalone/ruby/*/gems/concurrent-ruby-*/lib
|
||||||
**/vendor/bundle-standalone/ruby/*/gems/backports-*/lib
|
**/vendor/bundle-standalone/ruby/*/gems/backports-*/lib
|
||||||
|
**/vendor/bundle-standalone/ruby/*/gems/i18n-*/lib
|
||||||
|
**/vendor/bundle-standalone/ruby/*/gems/minitest-*/lib
|
||||||
|
**/vendor/bundle-standalone/ruby/*/gems/thread_safe-*/lib
|
||||||
|
**/vendor/bundle-standalone/ruby/*/gems/tzinfo-*/lib
|
||||||
|
|
||||||
# Ignore `bin` contents (again).
|
# Ignore `bin` contents (again).
|
||||||
/bin
|
/bin
|
||||||
|
|||||||
2
Library/Homebrew/vendor/Gemfile
vendored
2
Library/Homebrew/vendor/Gemfile
vendored
@ -1,5 +1,7 @@
|
|||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "activesupport"
|
||||||
|
gem "concurrent-ruby"
|
||||||
gem "backports"
|
gem "backports"
|
||||||
gem "plist"
|
gem "plist"
|
||||||
gem "ruby-macho"
|
gem "ruby-macho"
|
||||||
|
|||||||
14
Library/Homebrew/vendor/Gemfile.lock
vendored
14
Library/Homebrew/vendor/Gemfile.lock
vendored
@ -1,15 +1,29 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
|
activesupport (5.2.1)
|
||||||
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
i18n (>= 0.7, < 2)
|
||||||
|
minitest (~> 5.1)
|
||||||
|
tzinfo (~> 1.1)
|
||||||
backports (3.11.4)
|
backports (3.11.4)
|
||||||
|
concurrent-ruby (1.0.5)
|
||||||
|
i18n (1.1.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
minitest (5.11.3)
|
||||||
plist (3.4.0)
|
plist (3.4.0)
|
||||||
ruby-macho (2.0.0)
|
ruby-macho (2.0.0)
|
||||||
|
thread_safe (0.3.6)
|
||||||
|
tzinfo (1.2.5)
|
||||||
|
thread_safe (~> 0.1)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
activesupport
|
||||||
backports
|
backports
|
||||||
|
concurrent-ruby
|
||||||
plist
|
plist
|
||||||
ruby-macho
|
ruby-macho
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,12 @@ require 'rbconfig'
|
|||||||
ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
|
ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
|
||||||
ruby_version = RbConfig::CONFIG["ruby_version"]
|
ruby_version = RbConfig::CONFIG["ruby_version"]
|
||||||
path = File.expand_path('..', __FILE__)
|
path = File.expand_path('..', __FILE__)
|
||||||
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/concurrent-ruby-1.0.5/lib"
|
||||||
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/i18n-1.1.0/lib"
|
||||||
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/minitest-5.11.3/lib"
|
||||||
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/thread_safe-0.3.6/lib"
|
||||||
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/tzinfo-1.2.5/lib"
|
||||||
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/activesupport-5.2.1/lib"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/backports-3.11.4/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/backports-3.11.4/lib"
|
||||||
$:.unshift "#{path}/"
|
$:.unshift "#{path}/"
|
||||||
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.4.0/lib"
|
$:.unshift "#{path}/../#{ruby_engine}/#{ruby_version}/gems/plist-3.4.0/lib"
|
||||||
|
|||||||
@ -0,0 +1,156 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "active_support/core_ext/regexp"
|
||||||
|
require "concurrent/map"
|
||||||
|
|
||||||
|
class Object
|
||||||
|
# An object is blank if it's false, empty, or a whitespace string.
|
||||||
|
# For example, +false+, '', ' ', +nil+, [], and {} are all blank.
|
||||||
|
#
|
||||||
|
# This simplifies
|
||||||
|
#
|
||||||
|
# !address || address.empty?
|
||||||
|
#
|
||||||
|
# to
|
||||||
|
#
|
||||||
|
# address.blank?
|
||||||
|
#
|
||||||
|
# @return [true, false]
|
||||||
|
def blank?
|
||||||
|
respond_to?(:empty?) ? !!empty? : !self
|
||||||
|
end
|
||||||
|
|
||||||
|
# An object is present if it's not blank.
|
||||||
|
#
|
||||||
|
# @return [true, false]
|
||||||
|
def present?
|
||||||
|
!blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Returns the receiver if it's present otherwise returns +nil+.
|
||||||
|
# <tt>object.presence</tt> is equivalent to
|
||||||
|
#
|
||||||
|
# object.present? ? object : nil
|
||||||
|
#
|
||||||
|
# For example, something like
|
||||||
|
#
|
||||||
|
# state = params[:state] if params[:state].present?
|
||||||
|
# country = params[:country] if params[:country].present?
|
||||||
|
# region = state || country || 'US'
|
||||||
|
#
|
||||||
|
# becomes
|
||||||
|
#
|
||||||
|
# region = params[:state].presence || params[:country].presence || 'US'
|
||||||
|
#
|
||||||
|
# @return [Object]
|
||||||
|
def presence
|
||||||
|
self if present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class NilClass
|
||||||
|
# +nil+ is blank:
|
||||||
|
#
|
||||||
|
# nil.blank? # => true
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
def blank?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class FalseClass
|
||||||
|
# +false+ is blank:
|
||||||
|
#
|
||||||
|
# false.blank? # => true
|
||||||
|
#
|
||||||
|
# @return [true]
|
||||||
|
def blank?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class TrueClass
|
||||||
|
# +true+ is not blank:
|
||||||
|
#
|
||||||
|
# true.blank? # => false
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
def blank?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Array
|
||||||
|
# An array is blank if it's empty:
|
||||||
|
#
|
||||||
|
# [].blank? # => true
|
||||||
|
# [1,2,3].blank? # => false
|
||||||
|
#
|
||||||
|
# @return [true, false]
|
||||||
|
alias_method :blank?, :empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
class Hash
|
||||||
|
# A hash is blank if it's empty:
|
||||||
|
#
|
||||||
|
# {}.blank? # => true
|
||||||
|
# { key: 'value' }.blank? # => false
|
||||||
|
#
|
||||||
|
# @return [true, false]
|
||||||
|
alias_method :blank?, :empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
class String
|
||||||
|
BLANK_RE = /\A[[:space:]]*\z/
|
||||||
|
ENCODED_BLANKS = Concurrent::Map.new do |h, enc|
|
||||||
|
h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING)
|
||||||
|
end
|
||||||
|
|
||||||
|
# A string is blank if it's empty or contains whitespaces only:
|
||||||
|
#
|
||||||
|
# ''.blank? # => true
|
||||||
|
# ' '.blank? # => true
|
||||||
|
# "\t\n\r".blank? # => true
|
||||||
|
# ' blah '.blank? # => false
|
||||||
|
#
|
||||||
|
# Unicode whitespace is supported:
|
||||||
|
#
|
||||||
|
# "\u00a0".blank? # => true
|
||||||
|
#
|
||||||
|
# @return [true, false]
|
||||||
|
def blank?
|
||||||
|
# The regexp that matches blank strings is expensive. For the case of empty
|
||||||
|
# strings we can speed up this method (~3.5x) with an empty? call. The
|
||||||
|
# penalty for the rest of strings is marginal.
|
||||||
|
empty? ||
|
||||||
|
begin
|
||||||
|
BLANK_RE.match?(self)
|
||||||
|
rescue Encoding::CompatibilityError
|
||||||
|
ENCODED_BLANKS[self.encoding].match?(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Numeric #:nodoc:
|
||||||
|
# No number is blank:
|
||||||
|
#
|
||||||
|
# 1.blank? # => false
|
||||||
|
# 0.blank? # => false
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
def blank?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class Time #:nodoc:
|
||||||
|
# No Time is blank:
|
||||||
|
#
|
||||||
|
# Time.now.blank? # => false
|
||||||
|
#
|
||||||
|
# @return [false]
|
||||||
|
def blank?
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Regexp #:nodoc:
|
||||||
|
def multiline?
|
||||||
|
options & MULTILINE == MULTILINE
|
||||||
|
end
|
||||||
|
|
||||||
|
def match?(string, pos = 0)
|
||||||
|
!!match(string, pos)
|
||||||
|
end unless //.respond_to?(:match?)
|
||||||
|
end
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
require 'thread'
|
||||||
|
require 'concurrent/collection/map/non_concurrent_map_backend'
|
||||||
|
|
||||||
|
module Concurrent
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
module Collection
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
class MriMapBackend < NonConcurrentMapBackend
|
||||||
|
|
||||||
|
def initialize(options = nil)
|
||||||
|
super(options)
|
||||||
|
@write_lock = Mutex.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def []=(key, value)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def compute_if_absent(key)
|
||||||
|
if stored_value = _get(key) # fast non-blocking path for the most likely case
|
||||||
|
stored_value
|
||||||
|
else
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def compute_if_present(key)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def compute(key)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_pair(key, value)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_pair(key, old_value, new_value)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_if_exists(key, new_value)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_and_set(key, value)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(key)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_pair(key, value)
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear
|
||||||
|
@write_lock.synchronize { super }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
require 'concurrent/constants'
|
||||||
|
|
||||||
|
module Concurrent
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
module Collection
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
class NonConcurrentMapBackend
|
||||||
|
|
||||||
|
# WARNING: all public methods of the class must operate on the @backend
|
||||||
|
# directly without calling each other. This is important because of the
|
||||||
|
# SynchronizedMapBackend which uses a non-reentrant mutex for perfomance
|
||||||
|
# reasons.
|
||||||
|
def initialize(options = nil)
|
||||||
|
@backend = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](key)
|
||||||
|
@backend[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
def []=(key, value)
|
||||||
|
@backend[key] = value
|
||||||
|
end
|
||||||
|
|
||||||
|
def compute_if_absent(key)
|
||||||
|
if NULL != (stored_value = @backend.fetch(key, NULL))
|
||||||
|
stored_value
|
||||||
|
else
|
||||||
|
@backend[key] = yield
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_pair(key, old_value, new_value)
|
||||||
|
if pair?(key, old_value)
|
||||||
|
@backend[key] = new_value
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def replace_if_exists(key, new_value)
|
||||||
|
if NULL != (stored_value = @backend.fetch(key, NULL))
|
||||||
|
@backend[key] = new_value
|
||||||
|
stored_value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def compute_if_present(key)
|
||||||
|
if NULL != (stored_value = @backend.fetch(key, NULL))
|
||||||
|
store_computed_value(key, yield(stored_value))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def compute(key)
|
||||||
|
store_computed_value(key, yield(@backend[key]))
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_pair(key, value)
|
||||||
|
if NULL == (stored_value = @backend.fetch(key, NULL))
|
||||||
|
@backend[key] = value
|
||||||
|
else
|
||||||
|
store_computed_value(key, yield(stored_value))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_and_set(key, value)
|
||||||
|
stored_value = @backend[key]
|
||||||
|
@backend[key] = value
|
||||||
|
stored_value
|
||||||
|
end
|
||||||
|
|
||||||
|
def key?(key)
|
||||||
|
@backend.key?(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(key)
|
||||||
|
@backend.delete(key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_pair(key, value)
|
||||||
|
if pair?(key, value)
|
||||||
|
@backend.delete(key)
|
||||||
|
true
|
||||||
|
else
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def clear
|
||||||
|
@backend.clear
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def each_pair
|
||||||
|
return enum_for :each_pair unless block_given?
|
||||||
|
dupped_backend.each_pair do |k, v|
|
||||||
|
yield k, v
|
||||||
|
end
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def size
|
||||||
|
@backend.size
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_or_default(key, default_value)
|
||||||
|
@backend.fetch(key, default_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :_get, :[]
|
||||||
|
alias_method :_set, :[]=
|
||||||
|
private :_get, :_set
|
||||||
|
private
|
||||||
|
def initialize_copy(other)
|
||||||
|
super
|
||||||
|
@backend = {}
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def dupped_backend
|
||||||
|
@backend.dup
|
||||||
|
end
|
||||||
|
|
||||||
|
def pair?(key, expected_value)
|
||||||
|
NULL != (stored_value = @backend.fetch(key, NULL)) && expected_value.equal?(stored_value)
|
||||||
|
end
|
||||||
|
|
||||||
|
def store_computed_value(key, new_value)
|
||||||
|
if new_value.nil?
|
||||||
|
@backend.delete(key)
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
@backend[key] = new_value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
module Concurrent
|
||||||
|
|
||||||
|
# Various classes within allows for +nil+ values to be stored,
|
||||||
|
# so a special +NULL+ token is required to indicate the "nil-ness".
|
||||||
|
# @!visibility private
|
||||||
|
NULL = Object.new
|
||||||
|
|
||||||
|
end
|
||||||
@ -0,0 +1,240 @@
|
|||||||
|
require 'thread'
|
||||||
|
require 'concurrent/constants'
|
||||||
|
require 'concurrent/synchronization'
|
||||||
|
|
||||||
|
module Concurrent
|
||||||
|
# @!visibility private
|
||||||
|
module Collection
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
MapImplementation = if Concurrent.java_extensions_loaded?
|
||||||
|
# noinspection RubyResolve
|
||||||
|
JRubyMapBackend
|
||||||
|
elsif defined?(RUBY_ENGINE)
|
||||||
|
case RUBY_ENGINE
|
||||||
|
when 'ruby'
|
||||||
|
require 'concurrent/collection/map/mri_map_backend'
|
||||||
|
MriMapBackend
|
||||||
|
when 'rbx'
|
||||||
|
require 'concurrent/collection/map/atomic_reference_map_backend'
|
||||||
|
AtomicReferenceMapBackend
|
||||||
|
when 'jruby+truffle'
|
||||||
|
require 'concurrent/collection/map/atomic_reference_map_backend'
|
||||||
|
AtomicReferenceMapBackend
|
||||||
|
else
|
||||||
|
warn 'Concurrent::Map: unsupported Ruby engine, using a fully synchronized Concurrent::Map implementation' if $VERBOSE
|
||||||
|
require 'concurrent/collection/map/synchronized_map_backend'
|
||||||
|
SynchronizedMapBackend
|
||||||
|
end
|
||||||
|
else
|
||||||
|
MriMapBackend
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# `Concurrent::Map` is a hash-like object and should have much better performance
|
||||||
|
# characteristics, especially under high concurrency, than `Concurrent::Hash`.
|
||||||
|
# However, `Concurrent::Map `is not strictly semantically equivalent to a ruby `Hash`
|
||||||
|
# -- for instance, it does not necessarily retain ordering by insertion time as `Hash`
|
||||||
|
# does. For most uses it should do fine though, and we recommend you consider
|
||||||
|
# `Concurrent::Map` instead of `Concurrent::Hash` for your concurrency-safe hash needs.
|
||||||
|
#
|
||||||
|
# > require 'concurrent'
|
||||||
|
# >
|
||||||
|
# > map = Concurrent::Map.new
|
||||||
|
class Map < Collection::MapImplementation
|
||||||
|
|
||||||
|
# @!macro [new] map_method_is_atomic
|
||||||
|
# This method is atomic. Atomic methods of `Map` which accept a block
|
||||||
|
# do not allow the `self` instance to be used within the block. Doing
|
||||||
|
# so will cause a deadlock.
|
||||||
|
|
||||||
|
# @!method put_if_absent
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method compute_if_absent
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method compute_if_present
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method compute
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method merge_pair
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method replace_pair
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method replace_if_exists
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method get_and_set
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method delete
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
# @!method delete_pair
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
|
||||||
|
def initialize(options = nil, &block)
|
||||||
|
if options.kind_of?(::Hash)
|
||||||
|
validate_options_hash!(options)
|
||||||
|
else
|
||||||
|
options = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
super(options)
|
||||||
|
@default_proc = block
|
||||||
|
end
|
||||||
|
|
||||||
|
def [](key)
|
||||||
|
if value = super # non-falsy value is an existing mapping, return it right away
|
||||||
|
value
|
||||||
|
# re-check is done with get_or_default(key, NULL) instead of a simple !key?(key) in order to avoid a race condition, whereby by the time the current thread gets to the key?(key) call
|
||||||
|
# a key => value mapping might have already been created by a different thread (key?(key) would then return true, this elsif branch wouldn't be taken and an incorrent +nil+ value
|
||||||
|
# would be returned)
|
||||||
|
# note: nil == value check is not technically necessary
|
||||||
|
elsif @default_proc && nil == value && NULL == (value = get_or_default(key, NULL))
|
||||||
|
@default_proc.call(self, key)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
alias_method :get, :[]
|
||||||
|
alias_method :put, :[]=
|
||||||
|
|
||||||
|
# @!macro [attach] map_method_not_atomic
|
||||||
|
# The "fetch-then-act" methods of `Map` are not atomic. `Map` is intended
|
||||||
|
# to be use as a concurrency primitive with strong happens-before
|
||||||
|
# guarantees. It is not intended to be used as a high-level abstraction
|
||||||
|
# supporting complex operations. All read and write operations are
|
||||||
|
# thread safe, but no guarantees are made regarding race conditions
|
||||||
|
# between the fetch operation and yielding to the block. Additionally,
|
||||||
|
# this method does not support recursion. This is due to internal
|
||||||
|
# constraints that are very unlikely to change in the near future.
|
||||||
|
def fetch(key, default_value = NULL)
|
||||||
|
if NULL != (value = get_or_default(key, NULL))
|
||||||
|
value
|
||||||
|
elsif block_given?
|
||||||
|
yield key
|
||||||
|
elsif NULL != default_value
|
||||||
|
default_value
|
||||||
|
else
|
||||||
|
raise_fetch_no_key
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!macro map_method_not_atomic
|
||||||
|
def fetch_or_store(key, default_value = NULL)
|
||||||
|
fetch(key) do
|
||||||
|
put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!macro map_method_is_atomic
|
||||||
|
def put_if_absent(key, value)
|
||||||
|
computed = false
|
||||||
|
result = compute_if_absent(key) do
|
||||||
|
computed = true
|
||||||
|
value
|
||||||
|
end
|
||||||
|
computed ? nil : result
|
||||||
|
end unless method_defined?(:put_if_absent)
|
||||||
|
|
||||||
|
def value?(value)
|
||||||
|
each_value do |v|
|
||||||
|
return true if value.equal?(v)
|
||||||
|
end
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def keys
|
||||||
|
arr = []
|
||||||
|
each_pair {|k, v| arr << k}
|
||||||
|
arr
|
||||||
|
end unless method_defined?(:keys)
|
||||||
|
|
||||||
|
def values
|
||||||
|
arr = []
|
||||||
|
each_pair {|k, v| arr << v}
|
||||||
|
arr
|
||||||
|
end unless method_defined?(:values)
|
||||||
|
|
||||||
|
def each_key
|
||||||
|
each_pair {|k, v| yield k}
|
||||||
|
end unless method_defined?(:each_key)
|
||||||
|
|
||||||
|
def each_value
|
||||||
|
each_pair {|k, v| yield v}
|
||||||
|
end unless method_defined?(:each_value)
|
||||||
|
|
||||||
|
alias_method :each, :each_pair unless method_defined?(:each)
|
||||||
|
|
||||||
|
def key(value)
|
||||||
|
each_pair {|k, v| return k if v == value}
|
||||||
|
nil
|
||||||
|
end unless method_defined?(:key)
|
||||||
|
alias_method :index, :key if RUBY_VERSION < '1.9'
|
||||||
|
|
||||||
|
def empty?
|
||||||
|
each_pair {|k, v| return false}
|
||||||
|
true
|
||||||
|
end unless method_defined?(:empty?)
|
||||||
|
|
||||||
|
def size
|
||||||
|
count = 0
|
||||||
|
each_pair {|k, v| count += 1}
|
||||||
|
count
|
||||||
|
end unless method_defined?(:size)
|
||||||
|
|
||||||
|
def marshal_dump
|
||||||
|
raise TypeError, "can't dump hash with default proc" if @default_proc
|
||||||
|
h = {}
|
||||||
|
each_pair {|k, v| h[k] = v}
|
||||||
|
h
|
||||||
|
end
|
||||||
|
|
||||||
|
def marshal_load(hash)
|
||||||
|
initialize
|
||||||
|
populate_from(hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
undef :freeze
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
DEFAULT_OBJ_ID_STR_WIDTH = 0.size == 4 ? 7 : 14 # we want to look "native", 7 for 32-bit, 14 for 64-bit
|
||||||
|
# override default #inspect() method: firstly, we don't want to be spilling our guts (i-vars), secondly, MRI backend's
|
||||||
|
# #inspect() call on its @backend i-var will bump @backend's iter level while possibly yielding GVL
|
||||||
|
def inspect
|
||||||
|
id_str = (object_id << 1).to_s(16).rjust(DEFAULT_OBJ_ID_STR_WIDTH, '0')
|
||||||
|
"#<#{self.class.name}:0x#{id_str} entries=#{size} default_proc=#{@default_proc.inspect}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def raise_fetch_no_key
|
||||||
|
raise KeyError, 'key not found'
|
||||||
|
end
|
||||||
|
|
||||||
|
def initialize_copy(other)
|
||||||
|
super
|
||||||
|
populate_from(other)
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_from(hash)
|
||||||
|
hash.each_pair {|k, v| self[k] = v}
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_options_hash!(options)
|
||||||
|
if (initial_capacity = options[:initial_capacity]) && (!initial_capacity.kind_of?(Integer) || initial_capacity < 0)
|
||||||
|
raise ArgumentError, ":initial_capacity must be a positive Integer"
|
||||||
|
end
|
||||||
|
if (load_factor = options[:load_factor]) && (!load_factor.kind_of?(Numeric) || load_factor <= 0 || load_factor > 1)
|
||||||
|
raise ArgumentError, ":load_factor must be a number between 0 and 1"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
require 'concurrent/utility/engine'
|
||||||
|
|
||||||
|
require 'concurrent/synchronization/abstract_object'
|
||||||
|
require 'concurrent/utility/native_extension_loader' # load native parts first
|
||||||
|
Concurrent.load_native_extensions
|
||||||
|
|
||||||
|
require 'concurrent/synchronization/mri_object'
|
||||||
|
require 'concurrent/synchronization/jruby_object'
|
||||||
|
require 'concurrent/synchronization/rbx_object'
|
||||||
|
require 'concurrent/synchronization/truffle_object'
|
||||||
|
require 'concurrent/synchronization/object'
|
||||||
|
require 'concurrent/synchronization/volatile'
|
||||||
|
|
||||||
|
require 'concurrent/synchronization/abstract_lockable_object'
|
||||||
|
require 'concurrent/synchronization/mri_lockable_object'
|
||||||
|
require 'concurrent/synchronization/jruby_lockable_object'
|
||||||
|
require 'concurrent/synchronization/rbx_lockable_object'
|
||||||
|
require 'concurrent/synchronization/truffle_lockable_object'
|
||||||
|
|
||||||
|
require 'concurrent/synchronization/lockable_object'
|
||||||
|
|
||||||
|
require 'concurrent/synchronization/condition'
|
||||||
|
require 'concurrent/synchronization/lock'
|
||||||
|
|
||||||
|
module Concurrent
|
||||||
|
# {include:file:doc/synchronization.md}
|
||||||
|
# {include:file:doc/synchronization-notes.md}
|
||||||
|
module Synchronization
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
class AbstractLockableObject < Synchronization::Object
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
# @!macro [attach] synchronization_object_method_synchronize
|
||||||
|
#
|
||||||
|
# @yield runs the block synchronized against this object,
|
||||||
|
# equivalent of java's `synchronize(this) {}`
|
||||||
|
# @note can by made public in descendants if required by `public :synchronize`
|
||||||
|
def synchronize
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!macro [attach] synchronization_object_method_ns_wait_until
|
||||||
|
#
|
||||||
|
# Wait until condition is met or timeout passes,
|
||||||
|
# protects against spurious wake-ups.
|
||||||
|
# @param [Numeric, nil] timeout in seconds, `nil` means no timeout
|
||||||
|
# @yield condition to be met
|
||||||
|
# @yieldreturn [true, false]
|
||||||
|
# @return [true, false] if condition met
|
||||||
|
# @note only to be used inside synchronized block
|
||||||
|
# @note to provide direct access to this method in a descendant add method
|
||||||
|
# ```
|
||||||
|
# def wait_until(timeout = nil, &condition)
|
||||||
|
# synchronize { ns_wait_until(timeout, &condition) }
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
|
def ns_wait_until(timeout = nil, &condition)
|
||||||
|
if timeout
|
||||||
|
wait_until = Concurrent.monotonic_time + timeout
|
||||||
|
loop do
|
||||||
|
now = Concurrent.monotonic_time
|
||||||
|
condition_result = condition.call
|
||||||
|
return condition_result if now >= wait_until || condition_result
|
||||||
|
ns_wait wait_until - now
|
||||||
|
end
|
||||||
|
else
|
||||||
|
ns_wait timeout until condition.call
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!macro [attach] synchronization_object_method_ns_wait
|
||||||
|
#
|
||||||
|
# Wait until another thread calls #signal or #broadcast,
|
||||||
|
# spurious wake-ups can happen.
|
||||||
|
#
|
||||||
|
# @param [Numeric, nil] timeout in seconds, `nil` means no timeout
|
||||||
|
# @return [self]
|
||||||
|
# @note only to be used inside synchronized block
|
||||||
|
# @note to provide direct access to this method in a descendant add method
|
||||||
|
# ```
|
||||||
|
# def wait(timeout = nil)
|
||||||
|
# synchronize { ns_wait(timeout) }
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
|
def ns_wait(timeout = nil)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!macro [attach] synchronization_object_method_ns_signal
|
||||||
|
#
|
||||||
|
# Signal one waiting thread.
|
||||||
|
# @return [self]
|
||||||
|
# @note only to be used inside synchronized block
|
||||||
|
# @note to provide direct access to this method in a descendant add method
|
||||||
|
# ```
|
||||||
|
# def signal
|
||||||
|
# synchronize { ns_signal }
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
|
def ns_signal
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!macro [attach] synchronization_object_method_ns_broadcast
|
||||||
|
#
|
||||||
|
# Broadcast to all waiting threads.
|
||||||
|
# @return [self]
|
||||||
|
# @note only to be used inside synchronized block
|
||||||
|
# @note to provide direct access to this method in a descendant add method
|
||||||
|
# ```
|
||||||
|
# def broadcast
|
||||||
|
# synchronize { ns_broadcast }
|
||||||
|
# end
|
||||||
|
# ```
|
||||||
|
def ns_broadcast
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class AbstractObject
|
||||||
|
|
||||||
|
# @abstract has to be implemented based on Ruby runtime
|
||||||
|
def initialize
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @abstract
|
||||||
|
def full_memory_barrier
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.attr_volatile(*names)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,58 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
# TODO (pitr-ch 04-Dec-2016): should be in edge
|
||||||
|
class Condition < LockableObject
|
||||||
|
safe_initialization!
|
||||||
|
|
||||||
|
# TODO (pitr 12-Sep-2015): locks two objects, improve
|
||||||
|
# TODO (pitr 26-Sep-2015): study
|
||||||
|
# http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/java/util/concurrent/locks/AbstractQueuedSynchronizer.java#AbstractQueuedSynchronizer.Node
|
||||||
|
|
||||||
|
singleton_class.send :alias_method, :private_new, :new
|
||||||
|
private_class_method :new
|
||||||
|
|
||||||
|
def initialize(lock)
|
||||||
|
super()
|
||||||
|
@Lock = lock
|
||||||
|
end
|
||||||
|
|
||||||
|
def wait(timeout = nil)
|
||||||
|
@Lock.synchronize { ns_wait(timeout) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_wait(timeout = nil)
|
||||||
|
synchronize { super(timeout) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def wait_until(timeout = nil, &condition)
|
||||||
|
@Lock.synchronize { ns_wait_until(timeout, &condition) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_wait_until(timeout = nil, &condition)
|
||||||
|
synchronize { super(timeout, &condition) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def signal
|
||||||
|
@Lock.synchronize { ns_signal }
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_signal
|
||||||
|
synchronize { super }
|
||||||
|
end
|
||||||
|
|
||||||
|
def broadcast
|
||||||
|
@Lock.synchronize { ns_broadcast }
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_broadcast
|
||||||
|
synchronize { super }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class LockableObject < LockableObjectImplementation
|
||||||
|
def new_condition
|
||||||
|
Condition.private_new(self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
if Concurrent.on_jruby? && Concurrent.java_extensions_loaded?
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class JRubyLockableObject < AbstractLockableObject
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
if Concurrent.on_jruby? && Concurrent.java_extensions_loaded?
|
||||||
|
|
||||||
|
module JRubyAttrVolatile
|
||||||
|
def self.included(base)
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def attr_volatile(*names)
|
||||||
|
names.each do |name|
|
||||||
|
|
||||||
|
ivar = :"@volatile_#{name}"
|
||||||
|
|
||||||
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def #{name}
|
||||||
|
instance_variable_get_volatile(:#{ivar})
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{name}=(value)
|
||||||
|
instance_variable_set_volatile(:#{ivar}, value)
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
|
||||||
|
end
|
||||||
|
names.map { |n| [n, :"#{n}="] }.flatten
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class JRubyObject < AbstractObject
|
||||||
|
include JRubyAttrVolatile
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
# nothing to do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
# TODO (pitr-ch 04-Dec-2016): should be in edge
|
||||||
|
class Lock < LockableObject
|
||||||
|
# TODO use JavaReentrantLock on JRuby
|
||||||
|
|
||||||
|
public :synchronize
|
||||||
|
|
||||||
|
def wait(timeout = nil)
|
||||||
|
synchronize { ns_wait(timeout) }
|
||||||
|
end
|
||||||
|
|
||||||
|
public :ns_wait
|
||||||
|
|
||||||
|
def wait_until(timeout = nil, &condition)
|
||||||
|
synchronize { ns_wait_until(timeout, &condition) }
|
||||||
|
end
|
||||||
|
|
||||||
|
public :ns_wait_until
|
||||||
|
|
||||||
|
def signal
|
||||||
|
synchronize { ns_signal }
|
||||||
|
end
|
||||||
|
|
||||||
|
public :ns_signal
|
||||||
|
|
||||||
|
def broadcast
|
||||||
|
synchronize { ns_broadcast }
|
||||||
|
end
|
||||||
|
|
||||||
|
public :ns_broadcast
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,74 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
LockableObjectImplementation = case
|
||||||
|
when Concurrent.on_cruby? && Concurrent.ruby_version(:<=, 1, 9, 3)
|
||||||
|
MriMonitorLockableObject
|
||||||
|
when Concurrent.on_cruby? && Concurrent.ruby_version(:>, 1, 9, 3)
|
||||||
|
MriMutexLockableObject
|
||||||
|
when Concurrent.on_jruby?
|
||||||
|
JRubyLockableObject
|
||||||
|
when Concurrent.on_rbx?
|
||||||
|
RbxLockableObject
|
||||||
|
when Concurrent.on_truffle?
|
||||||
|
MriMutexLockableObject
|
||||||
|
else
|
||||||
|
warn 'Possibly unsupported Ruby implementation'
|
||||||
|
MriMonitorLockableObject
|
||||||
|
end
|
||||||
|
private_constant :LockableObjectImplementation
|
||||||
|
|
||||||
|
# Safe synchronization under any Ruby implementation.
|
||||||
|
# It provides methods like {#synchronize}, {#wait}, {#signal} and {#broadcast}.
|
||||||
|
# Provides a single layer which can improve its implementation over time without changes needed to
|
||||||
|
# the classes using it. Use {Synchronization::Object} not this abstract class.
|
||||||
|
#
|
||||||
|
# @note this object does not support usage together with
|
||||||
|
# [`Thread#wakeup`](http://ruby-doc.org/core-2.2.0/Thread.html#method-i-wakeup)
|
||||||
|
# and [`Thread#raise`](http://ruby-doc.org/core-2.2.0/Thread.html#method-i-raise).
|
||||||
|
# `Thread#sleep` and `Thread#wakeup` will work as expected but mixing `Synchronization::Object#wait` and
|
||||||
|
# `Thread#wakeup` will not work on all platforms.
|
||||||
|
#
|
||||||
|
# @see {Event} implementation as an example of this class use
|
||||||
|
#
|
||||||
|
# @example simple
|
||||||
|
# class AnClass < Synchronization::Object
|
||||||
|
# def initialize
|
||||||
|
# super
|
||||||
|
# synchronize { @value = 'asd' }
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# def value
|
||||||
|
# synchronize { @value }
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# @!visibility private
|
||||||
|
class LockableObject < LockableObjectImplementation
|
||||||
|
|
||||||
|
# TODO (pitr 12-Sep-2015): make private for c-r, prohibit subclassing
|
||||||
|
# TODO (pitr 12-Sep-2015): we inherit too much ourselves :/
|
||||||
|
|
||||||
|
# @!method initialize(*args, &block)
|
||||||
|
# @!macro synchronization_object_method_initialize
|
||||||
|
|
||||||
|
# @!method synchronize
|
||||||
|
# @!macro synchronization_object_method_synchronize
|
||||||
|
|
||||||
|
# @!method wait_until(timeout = nil, &condition)
|
||||||
|
# @!macro synchronization_object_method_ns_wait_until
|
||||||
|
|
||||||
|
# @!method wait(timeout = nil)
|
||||||
|
# @!macro synchronization_object_method_ns_wait
|
||||||
|
|
||||||
|
# @!method signal
|
||||||
|
# @!macro synchronization_object_method_ns_signal
|
||||||
|
|
||||||
|
# @!method broadcast
|
||||||
|
# @!macro synchronization_object_method_ns_broadcast
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class MriLockableObject < AbstractLockableObject
|
||||||
|
protected
|
||||||
|
|
||||||
|
def ns_signal
|
||||||
|
@__condition__.signal
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_broadcast
|
||||||
|
@__condition__.broadcast
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class MriMutexLockableObject < MriLockableObject
|
||||||
|
safe_initialization!
|
||||||
|
|
||||||
|
def initialize(*defaults)
|
||||||
|
super(*defaults)
|
||||||
|
@__lock__ = ::Mutex.new
|
||||||
|
@__condition__ = ::ConditionVariable.new
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def synchronize
|
||||||
|
if @__lock__.owned?
|
||||||
|
yield
|
||||||
|
else
|
||||||
|
@__lock__.synchronize { yield }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_wait(timeout = nil)
|
||||||
|
@__condition__.wait @__lock__, timeout
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class MriMonitorLockableObject < MriLockableObject
|
||||||
|
safe_initialization!
|
||||||
|
|
||||||
|
def initialize(*defaults)
|
||||||
|
super(*defaults)
|
||||||
|
@__lock__ = ::Monitor.new
|
||||||
|
@__condition__ = @__lock__.new_cond
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def synchronize # TODO may be a problem with lock.synchronize { lock.wait }
|
||||||
|
@__lock__.synchronize { yield }
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_wait(timeout = nil)
|
||||||
|
@__condition__.wait timeout
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
module MriAttrVolatile
|
||||||
|
def self.included(base)
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def attr_volatile(*names)
|
||||||
|
names.each do |name|
|
||||||
|
ivar = :"@volatile_#{name}"
|
||||||
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def #{name}
|
||||||
|
#{ivar}
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{name}=(value)
|
||||||
|
#{ivar} = value
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
names.map { |n| [n, :"#{n}="] }.flatten
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_memory_barrier
|
||||||
|
# relying on undocumented behavior of CRuby, GVL acquire has lock which ensures visibility of ivars
|
||||||
|
# https://github.com/ruby/ruby/blob/ruby_2_2/thread_pthread.c#L204-L211
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class MriObject < AbstractObject
|
||||||
|
include MriAttrVolatile
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
# nothing to do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
ObjectImplementation = case
|
||||||
|
when Concurrent.on_cruby?
|
||||||
|
MriObject
|
||||||
|
when Concurrent.on_jruby?
|
||||||
|
JRubyObject
|
||||||
|
when Concurrent.on_rbx?
|
||||||
|
RbxObject
|
||||||
|
when Concurrent.on_truffle?
|
||||||
|
TruffleObject
|
||||||
|
else
|
||||||
|
MriObject
|
||||||
|
end
|
||||||
|
private_constant :ObjectImplementation
|
||||||
|
|
||||||
|
# Abstract object providing final, volatile, ans CAS extensions to build other concurrent abstractions.
|
||||||
|
# - final instance variables see {Object.safe_initialization!}
|
||||||
|
# - volatile instance variables see {Object.attr_volatile}
|
||||||
|
# - volatile instance variables see {Object.attr_atomic}
|
||||||
|
class Object < ObjectImplementation
|
||||||
|
# TODO make it a module if possible
|
||||||
|
|
||||||
|
# @!method self.attr_volatile(*names)
|
||||||
|
# Creates methods for reading and writing (as `attr_accessor` does) to a instance variable with
|
||||||
|
# volatile (Java) semantic. The instance variable should be accessed oly through generated methods.
|
||||||
|
#
|
||||||
|
# @param [Array<Symbol>] names of the instance variables to be volatile
|
||||||
|
# @return [Array<Symbol>] names of defined method names
|
||||||
|
|
||||||
|
# Has to be called by children.
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
initialize_volatile_with_cas
|
||||||
|
end
|
||||||
|
|
||||||
|
# By calling this method on a class, it and all its children are marked to be constructed safely. Meaning that
|
||||||
|
# all writes (ivar initializations) are made visible to all readers of newly constructed object. It ensures
|
||||||
|
# same behaviour as Java's final fields.
|
||||||
|
# @example
|
||||||
|
# class AClass < Concurrent::Synchronization::Object
|
||||||
|
# safe_initialization!
|
||||||
|
#
|
||||||
|
# def initialize
|
||||||
|
# @AFinalValue = 'value' # published safely, does not have to be synchronized
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
def self.safe_initialization!
|
||||||
|
# define only once, and not again in children
|
||||||
|
return if safe_initialization?
|
||||||
|
|
||||||
|
def self.new(*args, &block)
|
||||||
|
object = super(*args, &block)
|
||||||
|
ensure
|
||||||
|
object.full_memory_barrier if object
|
||||||
|
end
|
||||||
|
|
||||||
|
@safe_initialization = true
|
||||||
|
end
|
||||||
|
|
||||||
|
# @return [true, false] if this class is safely initialized.
|
||||||
|
def self.safe_initialization?
|
||||||
|
@safe_initialization = false unless defined? @safe_initialization
|
||||||
|
@safe_initialization || (superclass.respond_to?(:safe_initialization?) && superclass.safe_initialization?)
|
||||||
|
end
|
||||||
|
|
||||||
|
# For testing purposes, quite slow. Injects assert code to new method which will raise if class instance contains
|
||||||
|
# any instance variables with CamelCase names and isn't {.safe_initialization?}.
|
||||||
|
def self.ensure_safe_initialization_when_final_fields_are_present
|
||||||
|
Object.class_eval do
|
||||||
|
def self.new(*args, &block)
|
||||||
|
object = super(*args, &block)
|
||||||
|
ensure
|
||||||
|
has_final_field = object.instance_variables.any? { |v| v.to_s =~ /^@[A-Z]/ }
|
||||||
|
if has_final_field && !safe_initialization?
|
||||||
|
raise "there was an instance of #{object.class} with final field but not marked with safe_initialization!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Creates methods for reading and writing to a instance variable with
|
||||||
|
# volatile (Java) semantic as {.attr_volatile} does.
|
||||||
|
# The instance variable should be accessed oly through generated methods.
|
||||||
|
# This method generates following methods: `value`, `value=(new_value) #=> new_value`,
|
||||||
|
# `swap_value(new_value) #=> old_value`,
|
||||||
|
# `compare_and_set_value(expected, value) #=> true || false`, `update_value(&block)`.
|
||||||
|
# @param [Array<Symbol>] names of the instance variables to be volatile with CAS.
|
||||||
|
# @return [Array<Symbol>] names of defined method names.
|
||||||
|
def self.attr_atomic(*names)
|
||||||
|
@volatile_cas_fields ||= []
|
||||||
|
@volatile_cas_fields += names
|
||||||
|
safe_initialization!
|
||||||
|
define_initialize_volatile_with_cas
|
||||||
|
|
||||||
|
names.each do |name|
|
||||||
|
ivar = :"@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }}"
|
||||||
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def #{name}
|
||||||
|
#{ivar}.get
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{name}=(value)
|
||||||
|
#{ivar}.set value
|
||||||
|
end
|
||||||
|
|
||||||
|
def swap_#{name}(value)
|
||||||
|
#{ivar}.swap value
|
||||||
|
end
|
||||||
|
|
||||||
|
def compare_and_set_#{name}(expected, value)
|
||||||
|
#{ivar}.compare_and_set expected, value
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_#{name}(&block)
|
||||||
|
#{ivar}.update(&block)
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
names.flat_map { |n| [n, :"#{n}=", :"swap_#{n}", :"compare_and_set_#{n}", :"update_#{n}"] }
|
||||||
|
end
|
||||||
|
|
||||||
|
# @param [true,false] inherited should inherited volatile with CAS fields be returned?
|
||||||
|
# @return [Array<Symbol>] Returns defined volatile with CAS fields on this class.
|
||||||
|
def self.volatile_cas_fields(inherited = true)
|
||||||
|
@volatile_cas_fields ||= []
|
||||||
|
((superclass.volatile_cas_fields if superclass.respond_to?(:volatile_cas_fields) && inherited) || []) +
|
||||||
|
@volatile_cas_fields
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def self.define_initialize_volatile_with_cas
|
||||||
|
assignments = @volatile_cas_fields.map { |name| "@Atomic#{name.to_s.gsub(/(?:^|_)(.)/) { $1.upcase }} = AtomicReference.new(nil)" }.join("\n")
|
||||||
|
class_eval <<-RUBY
|
||||||
|
def initialize_volatile_with_cas
|
||||||
|
super
|
||||||
|
#{assignments}
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
|
||||||
|
private_class_method :define_initialize_volatile_with_cas
|
||||||
|
|
||||||
|
def initialize_volatile_with_cas
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class RbxLockableObject < AbstractLockableObject
|
||||||
|
safe_initialization!
|
||||||
|
|
||||||
|
def initialize(*defaults)
|
||||||
|
super(*defaults)
|
||||||
|
@__Waiters__ = []
|
||||||
|
@__owner__ = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def synchronize(&block)
|
||||||
|
if @__owner__ == Thread.current
|
||||||
|
yield
|
||||||
|
else
|
||||||
|
result = nil
|
||||||
|
Rubinius.synchronize(self) do
|
||||||
|
begin
|
||||||
|
@__owner__ = Thread.current
|
||||||
|
result = yield
|
||||||
|
ensure
|
||||||
|
@__owner__ = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_wait(timeout = nil)
|
||||||
|
wchan = Rubinius::Channel.new
|
||||||
|
|
||||||
|
begin
|
||||||
|
@__Waiters__.push wchan
|
||||||
|
Rubinius.unlock(self)
|
||||||
|
signaled = wchan.receive_timeout timeout
|
||||||
|
ensure
|
||||||
|
Rubinius.lock(self)
|
||||||
|
|
||||||
|
if !signaled && !@__Waiters__.delete(wchan)
|
||||||
|
# we timed out, but got signaled afterwards,
|
||||||
|
# so pass that signal on to the next waiter
|
||||||
|
@__Waiters__.shift << true unless @__Waiters__.empty?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_signal
|
||||||
|
@__Waiters__.shift << true unless @__Waiters__.empty?
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
|
def ns_broadcast
|
||||||
|
@__Waiters__.shift << true until @__Waiters__.empty?
|
||||||
|
self
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
module RbxAttrVolatile
|
||||||
|
def self.included(base)
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
|
||||||
|
def attr_volatile(*names)
|
||||||
|
names.each do |name|
|
||||||
|
ivar = :"@volatile_#{name}"
|
||||||
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||||
|
def #{name}
|
||||||
|
Rubinius.memory_barrier
|
||||||
|
#{ivar}
|
||||||
|
end
|
||||||
|
|
||||||
|
def #{name}=(value)
|
||||||
|
#{ivar} = value
|
||||||
|
Rubinius.memory_barrier
|
||||||
|
end
|
||||||
|
RUBY
|
||||||
|
end
|
||||||
|
names.map { |n| [n, :"#{n}="] }.flatten
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_memory_barrier
|
||||||
|
# Rubinius instance variables are not volatile so we need to insert barrier
|
||||||
|
# TODO (pitr 26-Nov-2015): check comments like ^
|
||||||
|
Rubinius.memory_barrier
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class RbxObject < AbstractObject
|
||||||
|
include RbxAttrVolatile
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
# nothing to do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
class TruffleLockableObject < AbstractLockableObject
|
||||||
|
def new(*)
|
||||||
|
raise NotImplementedError
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
module TruffleAttrVolatile
|
||||||
|
def self.included(base)
|
||||||
|
base.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
def attr_volatile(*names)
|
||||||
|
# TODO may not always be available
|
||||||
|
attr_atomic(*names)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def full_memory_barrier
|
||||||
|
Truffle::System.full_memory_barrier
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
# @!macro internal_implementation_note
|
||||||
|
class TruffleObject < AbstractObject
|
||||||
|
include TruffleAttrVolatile
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
# nothing to do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Synchronization
|
||||||
|
|
||||||
|
# Volatile adds the attr_volatile class method when included.
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# class Foo
|
||||||
|
# include Concurrent::Synchronization::Volatile
|
||||||
|
#
|
||||||
|
# attr_volatile :bar
|
||||||
|
#
|
||||||
|
# def initialize
|
||||||
|
# self.bar = 1
|
||||||
|
# end
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
# foo = Foo.new
|
||||||
|
# foo.bar
|
||||||
|
# => 1
|
||||||
|
# foo.bar = 2
|
||||||
|
# => 2
|
||||||
|
|
||||||
|
Volatile = case
|
||||||
|
when Concurrent.on_cruby?
|
||||||
|
MriAttrVolatile
|
||||||
|
when Concurrent.on_jruby?
|
||||||
|
JRubyAttrVolatile
|
||||||
|
when Concurrent.on_rbx? || Concurrent.on_truffle?
|
||||||
|
RbxAttrVolatile
|
||||||
|
else
|
||||||
|
MriAttrVolatile
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
module Concurrent
|
||||||
|
module Utility
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
module EngineDetector
|
||||||
|
def on_jruby?
|
||||||
|
ruby_engine == 'jruby'
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_jruby_9000?
|
||||||
|
on_jruby? && ruby_version(:>=, 9, 0, 0, JRUBY_VERSION)
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_cruby?
|
||||||
|
ruby_engine == 'ruby'
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_rbx?
|
||||||
|
ruby_engine == 'rbx'
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_truffle?
|
||||||
|
ruby_engine == 'jruby+truffle'
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_windows?
|
||||||
|
!(RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/).nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_osx?
|
||||||
|
!(RbConfig::CONFIG['host_os'] =~ /darwin|mac os/).nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def on_linux?
|
||||||
|
!(RbConfig::CONFIG['host_os'] =~ /linux/).nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def ruby_engine
|
||||||
|
defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
|
||||||
|
end
|
||||||
|
|
||||||
|
def ruby_version(comparison, major, minor, patch, version = RUBY_VERSION)
|
||||||
|
result = (version.split('.').map(&:to_i) <=> [major, minor, patch])
|
||||||
|
comparisons = { :== => [0],
|
||||||
|
:>= => [1, 0],
|
||||||
|
:<= => [-1, 0],
|
||||||
|
:> => [1],
|
||||||
|
:< => [-1] }
|
||||||
|
comparisons.fetch(comparison).include? result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
extend Utility::EngineDetector
|
||||||
|
end
|
||||||
@ -0,0 +1,73 @@
|
|||||||
|
require 'concurrent/utility/engine'
|
||||||
|
|
||||||
|
module Concurrent
|
||||||
|
|
||||||
|
module Utility
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
module NativeExtensionLoader
|
||||||
|
|
||||||
|
def allow_c_extensions?
|
||||||
|
Concurrent.on_cruby?
|
||||||
|
end
|
||||||
|
|
||||||
|
def c_extensions_loaded?
|
||||||
|
@c_extensions_loaded ||= false
|
||||||
|
end
|
||||||
|
|
||||||
|
def java_extensions_loaded?
|
||||||
|
@java_extensions_loaded ||= false
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_c_extensions_loaded
|
||||||
|
@c_extensions_loaded = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_java_extensions_loaded
|
||||||
|
@java_extensions_loaded = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_native_extensions
|
||||||
|
unless defined? Synchronization::AbstractObject
|
||||||
|
raise 'native_extension_loader loaded before Synchronization::AbstractObject'
|
||||||
|
end
|
||||||
|
|
||||||
|
if Concurrent.on_cruby? && !c_extensions_loaded?
|
||||||
|
tries = [
|
||||||
|
lambda do
|
||||||
|
require 'concurrent/extension'
|
||||||
|
set_c_extensions_loaded
|
||||||
|
end,
|
||||||
|
lambda do
|
||||||
|
# may be a Windows cross-compiled native gem
|
||||||
|
require "concurrent/#{RUBY_VERSION[0..2]}/extension"
|
||||||
|
set_c_extensions_loaded
|
||||||
|
end]
|
||||||
|
|
||||||
|
tries.each do |try|
|
||||||
|
begin
|
||||||
|
try.call
|
||||||
|
break
|
||||||
|
rescue LoadError
|
||||||
|
next
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if Concurrent.on_jruby? && !java_extensions_loaded?
|
||||||
|
begin
|
||||||
|
require 'concurrent_ruby_ext'
|
||||||
|
set_java_extensions_loaded
|
||||||
|
rescue LoadError
|
||||||
|
# move on with pure-Ruby implementations
|
||||||
|
raise 'On JRuby but Java extensions failed to load.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# @!visibility private
|
||||||
|
extend Utility::NativeExtensionLoader
|
||||||
|
end
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user