Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Current

* (#1057) Fix `Integer#size`-derived width constants (`Utility::NativeInteger` bounds, `ThreadSafe::Util::FIXNUM_BIT_SIZE`/`MAX_INT`, `XorShiftRandom`'s transform selection) being wrong on 64-bit Windows (LLP64), where `Integer#size` reports the 4-byte C `long` width instead of the 8-byte pointer width.

## Release v1.3.8 (19 July 2026)

concurrent-ruby:
Expand Down
6 changes: 5 additions & 1 deletion lib/concurrent-ruby/concurrent/thread_safe/util.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'rbconfig/sizeof'

module Concurrent

# @!visibility private
Expand All @@ -7,7 +9,9 @@ module ThreadSafe
module Util

# TODO (pitr-ch 15-Oct-2016): migrate to Utility::NativeInteger
FIXNUM_BIT_SIZE = (0.size * 8) - 2
# Use the pointer width, not Integer#size (the C `long` width), since
# the two diverge on LLP64 platforms (64-bit Windows) - see #1057.
FIXNUM_BIT_SIZE = (RbConfig::SIZEOF['void*'] * 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?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'concurrent/thread_safe/util'
require 'rbconfig/sizeof'

module Concurrent

Expand Down Expand Up @@ -29,7 +30,9 @@ def get
end

# xorshift based on: http://www.jstatsoft.org/v08/i14/paper
if 0.size == 4
# Use the pointer width, not Integer#size (the C `long` width), since
# the two diverge on LLP64 platforms (64-bit Windows) - see #1057.
if RbConfig::SIZEOF['void*'] == 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
Expand Down
8 changes: 6 additions & 2 deletions lib/concurrent-ruby/concurrent/utility/native_integer.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
require 'rbconfig/sizeof'

module Concurrent
# @!visibility private
module Utility
# @private
module NativeInteger
# http://stackoverflow.com/questions/535721/ruby-max-integer
MIN_VALUE = -(2**(0.size * 8 - 2))
MAX_VALUE = (2**(0.size * 8 - 2) - 1)
# Use the pointer width, not Integer#size (the C `long` width), since
# the two diverge on LLP64 platforms (64-bit Windows) - see #1057.
MIN_VALUE = -(2**(RbConfig::SIZEOF['void*'] * 8 - 2))
MAX_VALUE = (2**(RbConfig::SIZEOF['void*'] * 8 - 2) - 1)

def ensure_upper_bound(value)
if value > MAX_VALUE
Expand Down
57 changes: 57 additions & 0 deletions spec/concurrent/utility/native_integer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
require 'rbconfig/sizeof'
require 'concurrent/utility/native_integer'
require 'concurrent/thread_safe/util'
require 'concurrent/atomic/atomic_fixnum'

# Regression for #1057: `Integer#size` reports the C `long` byte width, which
# is 4 on 64-bit Windows (LLP64) even though pointers are 8 bytes there, so
# every `0.size`-derived width constant used to be wrong on that platform.
# These specs assert literal values (not the bounds formula itself), so a
# reversion to `0.size`-derived bounds fails visibly on Win64 CI instead of
# passing self-referentially.
module Concurrent

RSpec.describe Utility::NativeInteger do
if RbConfig::SIZEOF['void*'] == 8
it 'derives a 62-bit signed bound from an 8-byte pointer width' do
expect(Utility::NativeInteger::MAX_VALUE).to eq 4611686018427387903
expect(Utility::NativeInteger::MIN_VALUE).to eq(-4611686018427387904)
end
elsif RbConfig::SIZEOF['void*'] == 4
it 'derives a 30-bit signed bound from a 4-byte pointer width' do
expect(Utility::NativeInteger::MAX_VALUE).to eq 1073741823
expect(Utility::NativeInteger::MIN_VALUE).to eq(-1073741824)
end
end
end

RSpec.describe MutexAtomicFixnum do
# Consumer-level regression: on Win64 the old `0.size`-derived bound
# (~1.07e9) rejected valid values well within a real Fixnum's range.
if RbConfig::SIZEOF['void*'] == 8
it 'accepts a value beyond the old 32-bit-derived bound' do
expect { described_class.new(2**31) }.not_to raise_error
end
end

it 'accepts Utility::NativeInteger::MAX_VALUE' do
expect { described_class.new(Utility::NativeInteger::MAX_VALUE) }.not_to raise_error
end

it 'raises RangeError one past Utility::NativeInteger::MAX_VALUE' do
expect { described_class.new(Utility::NativeInteger::MAX_VALUE + 1) }.to raise_error(RangeError)
end
end

module ThreadSafe
module Util
RSpec.describe 'ThreadSafe::Util::FIXNUM_BIT_SIZE' do
if RbConfig::SIZEOF['void*'] == 8
it 'is 62 on an 8-byte pointer width' do
expect(FIXNUM_BIT_SIZE).to eq 62
end
end
end
end
end
end