Add bin and edit workflow
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 9s

This commit is contained in:
2026-09-16 13:11:16 -06:00
parent c8ac4fcae5
commit 4cee170d66
17576 changed files with 895740 additions and 2 deletions
@@ -0,0 +1,47 @@
require 'delegate'
require 'monitor'
module Concurrent
# This class provides a trivial way to synchronize all calls to a given object
# by wrapping it with a `Delegator` that performs `Monitor#enter/exit` calls
# around the delegated `#send`. Example:
#
# array = [] # not thread-safe on many impls
# array = SynchronizedDelegator.new([]) # thread-safe
#
# A simple `Monitor` provides a very coarse-grained way to synchronize a given
# object, in that it will cause synchronization for methods that have no need
# for it, but this is a trivial way to get thread-safety where none may exist
# currently on some implementations.
#
# This class is currently being considered for inclusion into stdlib, via
# https://bugs.ruby-lang.org/issues/8556
#
# @!visibility private
class SynchronizedDelegator < SimpleDelegator
def setup
@old_abort = Thread.abort_on_exception
Thread.abort_on_exception = true
end
def teardown
Thread.abort_on_exception = @old_abort
end
def initialize(obj)
__setobj__(obj)
@monitor = Monitor.new
end
def method_missing(method, *args, &block)
monitor = @monitor
begin
monitor.enter
super
ensure
monitor.exit
end
end
end
end
@@ -0,0 +1,16 @@
module Concurrent
# @!visibility private
module ThreadSafe
# @!visibility private
module Util
# TODO (pitr-ch 15-Oct-2016): migrate to Utility::NativeInteger
FIXNUM_BIT_SIZE = (0.size * 8) - 2
MAX_INT = (2 ** FIXNUM_BIT_SIZE) - 1
# TODO (pitr-ch 15-Oct-2016): migrate to Utility::ProcessorCounter
CPU_COUNT = 16 # is there a way to determine this?
end
end
end
@@ -0,0 +1,74 @@
require 'concurrent/thread_safe/util'
require 'concurrent/thread_safe/util/striped64'
module Concurrent
# @!visibility private
module ThreadSafe
# @!visibility private
module Util
# A Ruby port of the Doug Lea's jsr166e.LongAdder class version 1.8
# available in public domain.
#
# Original source code available here:
# http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/LongAdder.java?revision=1.8
#
# One or more variables that together maintain an initially zero
# sum. When updates (method +add+) are contended across threads,
# the set of variables may grow dynamically to reduce contention.
# Method +sum+ returns the current total combined across the
# variables maintaining the sum.
#
# This class is usually preferable to single +Atomic+ reference when
# multiple threads update a common sum that is used for purposes such
# as collecting statistics, not for fine-grained synchronization
# control. Under low update contention, the two classes have similar
# characteristics. But under high contention, expected throughput of
# this class is significantly higher, at the expense of higher space
# consumption.
#
# @!visibility private
class Adder < Striped64
# Adds the given value.
def add(x)
if (current_cells = cells) || !cas_base_computed {|current_base| current_base + x}
was_uncontended = true
hash = hash_code
unless current_cells && (cell = current_cells.volatile_get_by_hash(hash)) && (was_uncontended = cell.cas_computed {|current_value| current_value + x})
retry_update(x, hash, was_uncontended) {|current_value| current_value + x}
end
end
end
def increment
add(1)
end
def decrement
add(-1)
end
# Returns the current sum. The returned value is _NOT_ an
# atomic snapshot: Invocation in the absence of concurrent
# updates returns an accurate result, but concurrent updates that
# occur while the sum is being calculated might not be
# incorporated.
def sum
x = base
if current_cells = cells
current_cells.each do |cell|
x += cell.value if cell
end
end
x
end
def reset
internal_reset(0)
end
end
end
end
end
@@ -0,0 +1,52 @@
require 'concurrent/thread_safe/util'
require 'concurrent/utility/engine'
# Shim for TruffleRuby.synchronized
if Concurrent.on_truffleruby? && !TruffleRuby.respond_to?(:synchronized)
module TruffleRuby
def self.synchronized(object, &block)
Truffle::System.synchronized(object, &block)
end
end
end
module Concurrent
module ThreadSafe
module Util
def self.make_synchronized_on_cruby(klass)
klass.class_eval do
def initialize(*args, &block)
@_monitor = Monitor.new
super
end
def initialize_copy(other)
# make sure a copy is not sharing a monitor with the original object!
@_monitor = Monitor.new
super
end
end
klass.superclass.instance_methods(false).each do |method|
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args)
monitor = @_monitor
monitor or raise("BUG: Internal monitor was not properly initialized. Please report this to the concurrent-ruby developers.")
monitor.synchronize { super }
end
RUBY
end
end
def self.make_synchronized_on_truffleruby(klass)
klass.superclass.instance_methods(false).each do |method|
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{method}(*args, &block)
TruffleRuby.synchronized(self) { super(*args, &block) }
end
RUBY
end
end
end
end
end
@@ -0,0 +1,38 @@
require 'concurrent/thread_safe/util'
require 'concurrent/tuple'
module Concurrent
# @!visibility private
module ThreadSafe
# @!visibility private
module Util
# @!visibility private
class PowerOfTwoTuple < Concurrent::Tuple
def initialize(size)
raise ArgumentError, "size must be a power of 2 (#{size.inspect} provided)" unless size > 0 && size & (size - 1) == 0
super(size)
end
def hash_to_index(hash)
(size - 1) & hash
end
def volatile_get_by_hash(hash)
volatile_get(hash_to_index(hash))
end
def volatile_set_by_hash(hash, value)
volatile_set(hash_to_index(hash), value)
end
def next_in_size_table
self.class.new(size << 1)
end
end
end
end
end
@@ -0,0 +1,246 @@
require 'concurrent/thread_safe/util'
require 'concurrent/thread_safe/util/power_of_two_tuple'
require 'concurrent/thread_safe/util/volatile'
require 'concurrent/thread_safe/util/xor_shift_random'
module Concurrent
# @!visibility private
module ThreadSafe
# @!visibility private
module Util
# A Ruby port of the Doug Lea's jsr166e.Striped64 class version 1.6
# available in public domain.
#
# Original source code available here:
# http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/jsr166e/Striped64.java?revision=1.6
#
# Class holding common representation and mechanics for classes supporting
# dynamic striping on 64bit values.
#
# This class maintains a lazily-initialized table of atomically updated
# variables, plus an extra +base+ field. The table size is a power of two.
# Indexing uses masked per-thread hash codes. Nearly all methods on this
# class are private, accessed directly by subclasses.
#
# Table entries are of class +Cell+; a variant of AtomicLong padded to
# reduce cache contention on most processors. Padding is overkill for most
# Atomics because they are usually irregularly scattered in memory and thus
# don't interfere much with each other. But Atomic objects residing in
# arrays will tend to be placed adjacent to each other, and so will most
# often share cache lines (with a huge negative performance impact) without
# this precaution.
#
# In part because +Cell+s are relatively large, we avoid creating them until
# they are needed. When there is no contention, all updates are made to the
# +base+ field. Upon first contention (a failed CAS on +base+ update), the
# table is initialized to size 2. The table size is doubled upon further
# contention until reaching the nearest power of two greater than or equal
# to the number of CPUS. Table slots remain empty (+nil+) until they are
# needed.
#
# A single spinlock (+busy+) is used for initializing and resizing the
# table, as well as populating slots with new +Cell+s. There is no need for
# a blocking lock: When the lock is not available, threads try other slots
# (or the base). During these retries, there is increased contention and
# reduced locality, which is still better than alternatives.
#
# Per-thread hash codes are initialized to random values. Contention and/or
# table collisions are indicated by failed CASes when performing an update
# operation (see method +retry_update+). Upon a collision, if the table size
# is less than the capacity, it is doubled in size unless some other thread
# holds the lock. If a hashed slot is empty, and lock is available, a new
# +Cell+ is created. Otherwise, if the slot exists, a CAS is tried. Retries
# proceed by "double hashing", using a secondary hash (XorShift) to try to
# find a free slot.
#
# The table size is capped because, when there are more threads than CPUs,
# supposing that each thread were bound to a CPU, there would exist a
# perfect hash function mapping threads to slots that eliminates collisions.
# When we reach capacity, we search for this mapping by randomly varying the
# hash codes of colliding threads. Because search is random, and collisions
# only become known via CAS failures, convergence can be slow, and because
# threads are typically not bound to CPUS forever, may not occur at all.
# However, despite these limitations, observed contention rates are
# typically low in these cases.
#
# It is possible for a +Cell+ to become unused when threads that once hashed
# to it terminate, as well as in the case where doubling the table causes no
# thread to hash to it under expanded mask. We do not try to detect or
# remove such cells, under the assumption that for long-running instances,
# observed contention levels will recur, so the cells will eventually be
# needed again; and for short-lived ones, it does not matter.
#
# @!visibility private
class Striped64
# Padded variant of AtomicLong supporting only raw accesses plus CAS.
# The +value+ field is placed between pads, hoping that the JVM doesn't
# reorder them.
#
# Optimisation note: It would be possible to use a release-only
# form of CAS here, if it were provided.
#
# @!visibility private
class Cell < Concurrent::AtomicReference
alias_method :cas, :compare_and_set
def cas_computed
cas(current_value = value, yield(current_value))
end
# @!visibility private
def self.padding
# TODO: this only adds padding after the :value slot, need to find a way to add padding before the slot
# TODO (pitr-ch 28-Jul-2018): the padding instance vars may not be created
# hide from yardoc in a method
attr_reader :padding_0, :padding_1, :padding_2, :padding_3, :padding_4, :padding_5, :padding_6, :padding_7, :padding_8, :padding_9, :padding_10, :padding_11
end
padding
end
extend Volatile
attr_volatile :cells, # Table of cells. When non-null, size is a power of 2.
:base, # Base value, used mainly when there is no contention, but also as a fallback during table initialization races. Updated via CAS.
:busy # Spinlock (locked via CAS) used when resizing and/or creating Cells.
alias_method :busy?, :busy
def initialize
super()
self.busy = false
self.base = 0
end
# Handles cases of updates involving initialization, resizing,
# creating new Cells, and/or contention. See above for
# explanation. This method suffers the usual non-modularity
# problems of optimistic retry code, relying on rechecked sets of
# reads.
#
# Arguments:
# [+x+]
# the value
# [+hash_code+]
# hash code used
# [+x+]
# false if CAS failed before call
def retry_update(x, hash_code, was_uncontended) # :yields: current_value
hash = hash_code
collided = false # True if last slot nonempty
while true
if current_cells = cells
if !(cell = current_cells.volatile_get_by_hash(hash))
if busy?
collided = false
else # Try to attach new Cell
if try_to_install_new_cell(Cell.new(x), hash) # Optimistically create and try to insert new cell
break
else
redo # Slot is now non-empty
end
end
elsif !was_uncontended # CAS already known to fail
was_uncontended = true # Continue after rehash
elsif cell.cas_computed {|current_value| yield current_value}
break
elsif current_cells.size >= CPU_COUNT || cells != current_cells # At max size or stale
collided = false
elsif collided && expand_table_unless_stale(current_cells)
collided = false
redo # Retry with expanded table
else
collided = true
end
hash = XorShiftRandom.xorshift(hash)
elsif try_initialize_cells(x, hash) || cas_base_computed {|current_base| yield current_base}
break
end
end
self.hash_code = hash
end
private
# Static per-thread hash code key. Shared across all instances to
# reduce Thread locals pollution and because adjustments due to
# collisions in one table are likely to be appropriate for
# others.
THREAD_LOCAL_KEY = "#{name}.hash_code".to_sym
# A thread-local hash code accessor. The code is initially
# random, but may be set to a different value upon collisions.
def hash_code
Thread.current[THREAD_LOCAL_KEY] ||= XorShiftRandom.get
end
def hash_code=(hash)
Thread.current[THREAD_LOCAL_KEY] = hash
end
# Sets base and all +cells+ to the given value.
def internal_reset(initial_value)
current_cells = cells
self.base = initial_value
if current_cells
current_cells.each do |cell|
cell.value = initial_value if cell
end
end
end
def cas_base_computed
cas_base(current_base = base, yield(current_base))
end
def free?
!busy?
end
def try_initialize_cells(x, hash)
if free? && !cells
try_in_busy do
unless cells # Recheck under lock
new_cells = PowerOfTwoTuple.new(2)
new_cells.volatile_set_by_hash(hash, Cell.new(x))
self.cells = new_cells
end
end
end
end
def expand_table_unless_stale(current_cells)
try_in_busy do
if current_cells == cells # Recheck under lock
new_cells = current_cells.next_in_size_table
current_cells.each_with_index {|x, i| new_cells.volatile_set(i, x)}
self.cells = new_cells
end
end
end
def try_to_install_new_cell(new_cell, hash)
try_in_busy do
# Recheck under lock
if (current_cells = cells) && !current_cells.volatile_get(i = current_cells.hash_to_index(hash))
current_cells.volatile_set(i, new_cell)
end
end
end
def try_in_busy
if cas_busy(false, true)
begin
yield
ensure
self.busy = false
end
end
end
end
end
end
end
@@ -0,0 +1,75 @@
require 'concurrent/thread_safe/util'
module Concurrent
# @!visibility private
module ThreadSafe
# @!visibility private
module Util
# @!visibility private
module Volatile
# Provides +volatile+ (in the JVM's sense) attribute accessors implemented
# atop of +Concurrent::AtomicReference+.
#
# Usage:
# class Foo
# extend Concurrent::ThreadSafe::Util::Volatile
# attr_volatile :foo, :bar
#
# def initialize(bar)
# super() # must super() into parent initializers before using the volatile attribute accessors
# self.bar = bar
# end
#
# def hello
# my_foo = foo # volatile read
# self.foo = 1 # volatile write
# cas_foo(1, 2) # => true | a strong CAS
# end
# end
def attr_volatile(*attr_names)
return if attr_names.empty?
include(Module.new do
atomic_ref_setup = attr_names.map {|attr_name| "@__#{attr_name} = Concurrent::AtomicReference.new"}
initialize_copy_setup = attr_names.zip(atomic_ref_setup).map do |attr_name, ref_setup|
"#{ref_setup}(other.instance_variable_get(:@__#{attr_name}).get)"
end
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def initialize(*)
super
#{atomic_ref_setup.join('; ')}
end
def initialize_copy(other)
super
#{initialize_copy_setup.join('; ')}
end
RUBY_EVAL
attr_names.each do |attr_name|
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
def #{attr_name}
@__#{attr_name}.get
end
def #{attr_name}=(value)
@__#{attr_name}.set(value)
end
def compare_and_set_#{attr_name}(old_value, new_value)
@__#{attr_name}.compare_and_set(old_value, new_value)
end
RUBY_EVAL
alias_method :"cas_#{attr_name}", :"compare_and_set_#{attr_name}"
alias_method :"lazy_set_#{attr_name}", :"#{attr_name}="
end
end)
end
end
end
end
end
@@ -0,0 +1,50 @@
require 'concurrent/thread_safe/util'
module Concurrent
# @!visibility private
module ThreadSafe
# @!visibility private
module Util
# A xorshift random number (positive +Fixnum+s) generator, provides
# reasonably cheap way to generate thread local random numbers without
# contending for the global +Kernel.rand+.
#
# Usage:
# x = XorShiftRandom.get # uses Kernel.rand to generate an initial seed
# while true
# if (x = XorShiftRandom.xorshift).odd? # thread-locally generate a next random number
# do_something_at_random
# end
# end
module XorShiftRandom
extend self
MAX_XOR_SHIFTABLE_INT = MAX_INT - 1
# Generates an initial non-zero positive +Fixnum+ via +Kernel.rand+.
def get
Kernel.rand(MAX_XOR_SHIFTABLE_INT) + 1 # 0 can't be xorshifted
end
# xorshift based on: http://www.jstatsoft.org/v08/i14/paper
if 0.size == 4
# using the "yˆ=y>>a; yˆ=y<<b; yˆ=y>>c;" transform with the (a,b,c) tuple with values (3,1,14) to minimise Bignum overflows
def xorshift(x)
x ^= x >> 3
x ^= (x << 1) & MAX_INT # cut-off Bignum overflow
x ^= x >> 14
end
else
# using the "yˆ=y>>a; yˆ=y<<b; yˆ=y>>c;" transform with the (a,b,c) tuple with values (1,1,54) to minimise Bignum overflows
def xorshift(x)
x ^= x >> 1
x ^= (x << 1) & MAX_INT # cut-off Bignum overflow
x ^= x >> 54
end
end
end
end
end
end