Skip to content

Default gems: skip the gem-cache subprocess, parse RBS immediately - #87

Draft
apiology wants to merge 2 commits into
masterfrom
route-default-gems-to-stdlib
Draft

Default gems: skip the gem-cache subprocess, parse RBS immediately#87
apiology wants to merge 2 commits into
masterfrom
route-default-gems-to-stdlib

Conversation

@apiology

@apiology apiology commented Sep 6, 2026

Copy link
Copy Markdown
Owner

This PR was written by Claude Code on behalf of @apiology.

Problem: A default gem waits on a cache that can never hold more than the RBS it already has.

DocMap picks the pin-building path by whether a gemspec exists, not by whether the gem ships any Ruby source. Every stdlib package Ruby promotes to a default gem inherits a gemspec and silently starts waiting on solargraph gems to cache it — of 65 stdlib requires probed on Ruby 3.2.6, 43 now take that path, versus 5 that still resolve straight from RBS. That wait exists to isolate a cost this gem never has.

Solution: route default gems to the stdlib RBS path, keyed on Gem::Specification#default_gem?.

Why this is correct, not just faster

Gem-caching runs in a subprocess so LSP requests stay responsive: a project's gem set is unbounded, and an arbitrary gem's YARD source has no bound on parse time, so that work is deferred to a background thread while the editor answers immediately. Core and stdlib skip that detour because neither risk applies to them — RbsMap::CoreMap builds all of Ruby core synchronously at boot, before any request is served, and RbsMap::StdlibMap does the same per library, live, mid-session, for any bare require with no gemspec (require 'set'), because there's no arbitrary source to parse, only RBS Ruby itself maintains.

A default gem has that same property, and it's cheap enough to measure: digesting forwardable cold (never touched in this process) took 9.3ms; pathname, digested right after, took 2.39ms. Both drop under 1ms once PinCache has them — for scale, even a warm read of the already-cached core pins (6,849 of them) costs 306ms, a price every boot already pays. Opening a file that introduces one never-before-seen default gem adds single-digit milliseconds, once; every default gem after that is instant:

forwardable 1.3.3  default_gem?=true   ruby files under gem path: 0
pathname    0.2.1  default_gem?=true   ruby files under gem path: 0
logger      1.7.0  default_gem?=false  ruby files under gem path: 7

logger shows the boundary holds — real source, so it correctly stays on the subprocess path.

DocMap splits requires by whether a gemspec exists: no gemspec goes to
deserialize_stdlib_rbs_map, which builds pins on the spot, and a
gemspec goes to deserialize_combined_pin_cache, which waits for
`solargraph gems` to write a cache entry. Ruby has promoted most of the
standard library to default gems, and each promotion silently moved
that library from the first path to the second.

A default gem carries no documentation of its own, so a combined cache
entry could never hold more than the stdlib RBS already does. Measured
on Ruby 3.2.6, abbrev, cgi, csv, delegate, digest, etc, fileutils,
find, forwardable, ipaddr, mutex_m and nkf all yield zero YARD pins.

Send those straight down the stdlib path. Of 65 stdlib requires
probed, 29 qualify, and they also drop out of the caching queue since
nothing is left for `solargraph gems` to build.

Gems that merely look like stdlib are unaffected. logger 1.7.0 is not
a default gem, and its YARD pins add 69 source locations and 39 doc
strings over the RBS alone, so it stays on the cache path.
@apiology apiology changed the title Route default gems to the stdlib RBS path Default gems: skip the gem-cache subprocess, parse RBS immediately Sep 7, 2026
The pending review noted a default gem could ship its own RBS, and
that "the stdlib RBS" implies one atomic source when it is not.
State the actual, checkable reason instead: RbsMap.from_gemspec
already tries RbsMap::StdlibMap first and returns it whenever
resolved, so this method returns identical pins to that path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant