Skip to content

[pull] master from ruby:master - #1343

Merged
pull[bot] merged 26 commits into
turkdevops:masterfrom
ruby:master
Aug 24, 2026
Merged

[pull] master from ruby:master#1343
pull[bot] merged 26 commits into
turkdevops:masterfrom
ruby:master

Conversation

@pull

@pull pull Bot commented Aug 24, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

byroot and others added 26 commits August 24, 2026 09:12
Rather than start from a mutable hash created with `newhash` or
`duphash`, and then iteratively insert into it, it's preferable to
start from a constant hidden hash, so that we can directly allocate
the final hash with the right size.

For instance:

```ruby
{a: 1, B => 2, c: 3}
```

Before:

```
0000 duphash                                {a: 1}                    (   1)[Li]
0002 putspecialobject                       1
0004 swap
0005 opt_getconstant_path                   <ic:0 B>
0007 putobject                              2
0009 putobject                              :c
0011 putobject                              3
0013 opt_send_without_block                 <calldata!mid:core#hash_merge_ptr, argc:5, ARGS_SIMPLE>
```

After:

```
0000 putobject                              {a: 1}                    (   1)[Li]
0002 putspecialobject                       1
0004 swap
0005 opt_getconstant_path                   <ic:0 B>
0007 putobject                              2
0009 putobject                              :c
0011 putobject                              3
0013 opt_send_without_block                 <calldata!mid:core#hash_merge_ptr, argc:5, ARGS_SIMPLE>
```

Since, `duphash` has no idea how large the final hash might be,
it can't right-size the hash it allocates.

Hence it's preferable to use a `putobject` and let `hash_merge_ptr`
compute the final hash right size by assuming all keys are unique.
The resolver asks for the cooldown once per candidate spec, paying a
Bundler.settings lookup each time, so read it once per remote. That
makes the value a snapshot, which is fine because nothing changes the
setting after the sources are built. Also check the day count before
the locked specs lookup, so a resolve without a cooldown configured
stops earlier.

ruby/rubygems@85bac3b375
A cooldown from a config file or from BUNDLE_COOLDOWN went through
value.to_i, so "abc" became 0 and "-5" stayed negative, and either one
disabled the cooldown without saying so. Read it with Integer() and
warn, naming the effect: such a value disables the cooldown for every
source, overriding any per-source cooldown: in the Gemfile.

The check runs at the CLI entry point rather than while reading the
setting, so bundle config does not warn merely for listing settings,
and bundle outdated does not swallow it inside Bundler.ui.silence.
gem warns the same way for gemrc values, including ones that are not
even numbers, and rejects a negative --cooldown outright.

ruby/rubygems@971ab9d01b
A created_at without a time zone offset was parsed as local time, so a
third-party server that omits the offset shifted the cooldown window by
whatever offset the machine running bundler happened to have.
rubygems.org always sends one, so this never showed up against it.
Complete the missing offset with "Z" before parsing, which leaves the
set of accepted formats unchanged.

Time.new(value, in: "UTC") would be the obvious way to say this, but it
raises TypeError on JRuby, and rescue ArgumentError does not catch that.
gem outdated and gem update read timestamps through a second parser in
Gem::Source that still used local time, so route both through one
helper.

ruby/rubygems@ea314c48a0
…iles_in_dir

Matched entries were resolved with File.expand_path, so an entry starting with `~` was expanded into the home directory. A cache directory named `~` made `bundle cache` pruning delete files under `$HOME`, and an entry like `~foo` raised `ArgumentError`.

ruby/rubygems@a20a024887

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`gem contents`, `gem stale` and the `gem setup` old-doc cleanup returned no matches when the install path contains glob metacharacters. For `--lib-only`, glob `raw_require_paths`, which stay relative to `full_gem_path`, rather than `require_paths`, which prepends the absolute `extension_dir` and would escape the glob base.

ruby/rubygems@de0c1f71fe

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ns in Bundler

Bundler runs against whatever RubyGems the host provides, so calling `Gem::Util.glob_files_in_dir` left `bundle cache` pruning able to resolve a cache entry named `~` to the real home directory and hand it to `FileUtils.rm_rf` on every RubyGems older than that fix. Move the helper into `SharedHelpers`. This also covers `bundle doctor`, which previously found no `.bundle` files under an install path containing glob metacharacters.

ruby/rubygems@4d79dcebf4

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…dated

With cooldown enabled, "newest" is often a version still inside the
cooldown window and the newest version bundler would actually adopt was
not shown anywhere. Append it to the outdated line, both in the
parseable output and in the table, when it sits strictly between the
installed version and the newest one. Existing tokens are left
untouched so tools parsing this output (e.g. libyear-bundler) keep
working.

Closes ruby/rubygems#9624

ruby/rubygems@8ae7825c63

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The table said "1.5.0 available", but "available" already denotes the
opposite state in the install summary, which reports a blocked version
as "(available in 6 days)". Reuse the exact antonym of the prose
"in cooldown for N more days" instead, so both formats name the
concept the same way.

ruby/rubygems@a09f72bd58

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…outdated(1)

The man page enumerates the cooldown output tokens, so the two tokens
added for #9624 belong there too. Review finding UX-1.

ruby/rubygems@9d227a2b10

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The candidate list was recomputed for the same gem by
retrieve_active_spec and newest_out_of_cooldown. Review finding API-2.

ruby/rubygems@1be428df41

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review finding GEM-1.

ruby/rubygems@595c21856d

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ooldown

In strict mode the resolved version is already cooldown-filtered, so no
cooldown annotations appear. Pin that down. Review finding UX-4.

ruby/rubygems@6f564d219a

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…note

No test exercised the "1 more day" path. Review finding GEM-2.

ruby/rubygems@a1baec1d4b

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Unreachable today because specs_for_outdated_check yields one spec per
name, but keying on the argument that feeds the computation keeps the
cache honest. Review finding API-1 (round 2).

ruby/rubygems@e710e9ef93

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…estamp

The resolver already pins its cooldown clock via cooldown_now; do the
same here so every annotation in one run shares the same reference
time. Review finding API-2 (round 2).

ruby/rubygems@8a945d0d61

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ions

Review finding UX-3 (round 2).

ruby/rubygems@0fe5f1591e

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
They flip the same strict flag as --filter-strict, so the cooldown
caveat applies to them too. Review finding UX-1 (round 3).

ruby/rubygems@92933f7b7b

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- All `null?` buffers will result in zero-length operations.
A problem I ran into in production, is that the boot sequence,
which tend to be allocation heavy, would cause the `allocatable_bytes`
(or previous version equivalent) to grow much higher than what the
application runtime actually needs.

This resulted in GC not triggering for a long time, with the
adverse effect of a unreasonably high memory usage and infrequent
but long GC pauses to sweep dozens of requests worth of garbage.

I worked around that problem by explictly tuning the `INIT_SLOT_SIZE*`
environment variables, to curb that growth. It works OK but is high
maintaince as it needs to be retuned frequently as the application
shape changes.

Overall I think the the boot sequence isn't a good indicator of
the heap size the application will need at runtime (particularly
for monolith type applications).

Hence it is desirable to somewhat "reset" (or recompute) the
heap limits at end of boot. If we somehow undershoot, it will
correct itself quickly after a few GC, which is preferable to overshooting.

```ruby
def to_mib(bytes) = "#{(bytes / 1024.0 / 1024).round(1)}MiB"
puts "heap_allocatable_bytes: #{to_mib(GC.stat[:heap_allocatable_bytes])}"
keep = 2_000_000.times.map { Object.new }
garbage = 10_000_000.times.map { Object.new }
garbage.clear
puts "heap_allocatable_bytes: #{to_mib(GC.stat[:heap_allocatable_bytes])}"
Process.warmup
puts "heap_allocatable_bytes: #{to_mib(GC.stat[:heap_allocatable_bytes])}"
```

master:

```
heap_allocatable_bytes: 0.0MiB
heap_allocatable_bytes: 79.2MiB
heap_allocatable_bytes: 372.1MiB
```

this branch:

```
heap_allocatable_bytes: 0.0MiB
heap_allocatable_bytes: 79.2MiB
heap_allocatable_bytes: 57.1MiB
```
Start slower and less predictable shards earlier to reduce the overall
workflow completion time.
Saves on potentially having to reallocate multiple times.
@pull pull Bot locked and limited conversation to collaborators Aug 24, 2026
@pull pull Bot added the ⤵️ pull label Aug 24, 2026
@pull
pull Bot merged commit 2449a13 into turkdevops:master Aug 24, 2026
0 of 2 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants