[pull] master from ruby:master - #1394
Merged
Merged
Conversation
test/ruby/test_class.rb defines a top-level TestClass, so when the two files share a test-all worker the assertion that the box's class is invisible from the main box finds that one and fails. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test_port_undelivered_message_does_not_leak asserted on the ratio of two RSS samples, which the allocator's own behaviour moves as much as the leak did. It failed on macOS CI at 2.70x against a 2.0x bound, after an earlier round of tuning (large payloads, a baseline at the high-water mark) had already been needed to get it under the bound at all. A bound that has to be tuned to the allocator is measuring the allocator. Drop the test; the fix in ae5ca45 stays. [Bug #22122] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A new box copies the master box's $LOAD_PATH, so the tool/lib entry that tool/test/init.rb adds to the main box at boot never reaches it, and requiring 'envutil' by name raised LoadError under RUBY_BOX=1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test_add_constants_in_box defines String::STR_CONST0 on the main box and never removed it, so it leaked into every later test in the same worker process. Those lines run only now that requiring envutil into the box works under RUBY_BOX=1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
At MinGW (CLANGARM64) builds:
```
In file included from ../src/gc.c:617:
../src/gc/default/default.c:2543:10: warning: unused variable 'need_reuse' [-Wunused-variable]
2543 | bool need_reuse = false;
| ^~~~~~~~~~
```
It is not used on Windows:
```
../src/time.c:1048:1: warning: unused function 'zone_str' [-Wunused-function]
1048 | zone_str(const char *zone)
| ^~~~~~~~
```
A box copies an extension to "<box id>_<serial>_<basename>" in a private directory, but the test still expected the flattened full path the copy was named after before that. The ABI error message therefore never matched under `RUBY_BOX=1`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
None of these `assert_separately` sources interpolate, and the interpolating form consumes backslash escapes before the child ever parses them, so a regexp written with `\d` or `\.` silently loses it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pattern matched `<digits>_<digits>_abi.` anywhere in the message, so an unrelated component of `TMPDIR` satisfied it just as well, and it said nothing about the per-process directory the copy is isolated in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three tests spawn a child with `RUBY_BOX=1` and assert that the two Ruby::Box experimental warning lines come first on stderr. They rely on the warning setting the parent process happens to pass down. Pass `-W:experimental` so they get what they assert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test raises the child's warning level to assert that `ARGF.puts` prints nothing, but `-W2` turns every category back on, so under `RUBY_BOX=1` the child starts with the Ruby::Box experimental warning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
test-all already turns experimental warnings off for its own process, but tests that spawn ruby directly with Open3, IO.popen or PTY assert the captured output verbatim. With `RUBY_BOX=1` in the environment every child prints the Ruby::Box experimental warning to stderr, and 51 tests and specs fail on it. Pass `-W:no-experimental` down through RUBYOPT. It has to precede the bare "-" that common.mk puts there, because that ends the option scan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The example asserts the default value of the experimental category and already skips when the ruby_exe command carries -W. RUBYOPT can carry it too, and test-spec now puts -W:no-experimental there, so clear it for the two children rather than lose the example. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last -W switch wins, so prepending it left an inherited -w or -W2 in control and the suppression quietly did nothing. `make test-all RUBYOPT=-w` reproduces it: under `RUBY_BOX=1` test_open3.rb and test_pty.rb stay at 12 and 4 failures, and reach 0 once the switch moves behind the inherited options and in front of the bare "-". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The -w it hands every child turns all the categories back on, so the RUBYOPT setting test-all passes down never reaches them and a Ruby::Box startup warning lands in output the assertions compare verbatim. The switch is 2.7 or later, and this file is copied verbatim into ruby/test-unit-ruby-core, which still runs on older rubies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test hands -w to the ruby it spawns, so the suppression the harness sets up does not survive into that grandchild. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…te matches `bundle update --bundler ">= <version>"` crashes with "comparison of NilClass with Bundler::StubSpecification failed" when the requirement is satisfied locally but by nothing on rubygems.org. ruby/rubygems@ad1e0d4bdb Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… one `bundle update --bundler` and `bundle lock --update --bundler` defaulted to `> 0.a`, so publishing 4.1.0.beta1 moved every project onto the beta. Default to `>= <running version>` and skip prerelease candidates unless the requirement mentions one, the same way `gem update --system` does. Anyone already running a prerelease keeps it, since the floor makes the default requirement a prerelease requirement in that case. ruby/rubygems#9867 ruby/rubygems@3a6c6e9781 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`bundle update --bundler --pre` did nothing: the self manager only looked at whether the requirement named a prerelease, so it never considered one and never restarted, leaving the flag silently ignored. ruby/rubygems@0d527fd5e4 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Auto switching restarts into whatever prerelease the lockfile names, so the requirement derived from the running version is a prerelease one and a bare `--bundler` keeps the prerelease. Naming the target explicitly is the way out, and nothing pinned that. ruby/rubygems@c7a375c7ea Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both man pages said a bare `--bundler` picks the latest version, which no longer holds, and neither showed that a version or requirement can be passed. That argument is now the way to reach a prerelease. ruby/rubygems@ba934bef60 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Once the flag had fallen to 0 the short-circuited condition still landed in the else, so every later call stored 0 again. Guard the attempt so the store happens only on the call that saw clock_gettime fail. current_process_time carries the same idiom and is what current_thread_time falls through to, so both are fixed together. Pointed out by nobu in review of #18717 Co-Authored-By: Nobuyoshi Nakada <nobu@ruby-lang.org> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thread_sched_wait_events() decided "timed out" from waiting_reason.data.result == 0, which only says that no event fired. A thread woken by an interrupt lands there too, so an interrupted wait was reported as thread_sched_wait_timeout. rb_thread_io_wait() then left revents at 0 and IO#wait_readable returned nil even though the fd had become readable. Only RUBY_MN_THREADS=2 could hit it, because that is what puts the main thread on the M:N io path; =1 and =-1 keep the 1:1 path, which retries around EINTR and reports the fd correctly. Hand an interrupted wait back to the caller's blocking path instead, and charge the time the M:N wait already spent against the timeout so the deadline stays the one the caller asked for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The test asserts that exactly one SIGCHLD arrives while its trap is installed, but it cannot guarantee that: Signal.trap does not discard a SIGCHLD that was delivered before the handler was installed, and under -j20 the worker process has reaped children of earlier tests whose signal can still be delivered inside the window. This is the same tolerance the test carried until 4a67ef0, where it was dropped along with the RJIT branch that needed it ("It may trigger extra SIGCHLD"). An extra true is still tolerated; an empty array, which is the regression [ruby-core:19744] guards, still fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The expected message bakes the receiver's `#inspect` into a heredoc before the block raises, so a moving GC that relocates the receiver in between makes the two addresses disagree. That is why these tests fail intermittently under MMTk. `GC.compact` reproduces the same failure with the default GC. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
env_copy() overwrote the SPECVAL slot of every local env with
VM_BLOCK_HANDLER_NONE. For a TOP/CLASS frame's env that slot stores
the box (VM_ENV_BOX), not a block handler, so a proc defined at a
class/module body and isolated by Ractor.make_shareable lost its box
while keeping its frame type. Any method call inside such a proc then
made rb_current_box() return NULL via current_box_on_cfp(), and the
classext lookup dereferenced the NULL box:
RUBY_BOX=1 ruby -e \
'module M; L = Ractor.make_shareable(->(*a){ Rational(*a) }); end;
p M::L.call(3, 4)'
# => SIGSEGV at 0x0 in rb_vm_search_method_slowpath
Copy the SPECVAL slot through instead when the source env belongs to
a TOP/CLASS frame, so the isolated proc keeps resolving methods in
its defining box.
[Bug #22260]
(ruby/erb#142) Shifting by 64 or more bits is undefined behavior. ruby/erb@a9316104fc
A Ractor id must stay unique for the life of the process, but the 32 bit counter wraps after ~4e9 Ractors and comes back onto ids that are still in use. That is not out of reach: a few thousand Ractor.new/s is days, not years. The wrapped value is 0 too, which some callers read as "the main Ractor". There is no portable 64 bit atomic, so RUBY_ATOMIC_FETCH_ADD gives way to the VM lock, which Ractor.new acquires a few frames later anyway. That lock is a no-op until there is a second Ractor, which is exactly when the GVL alone already serializes the counter, so ractor_next_id asserts the same condition vm_insert_ractor0 does. rb_serial_t, to match ec->ractor_id and bmethod's defined_ractor_id. Neither struct grows: rb_ractor_pub had 4 bytes of padding after id. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every class/module records the id of the Ractor that created it in rb_classext_t::owner_ractor_id; only that Ractor may modify it. 0 means the main Ractor, so a program that never leaves it stores nothing. An id rather than the Ractor object: ids come from a monotonic counter and are never reused, so the comparison can never meet a dangling or recycled identity, there is nothing for the GC to trace or move, and a class does not keep its creator alive. It also gives the termination rule for free -- once the owner is gone no live Ractor carries its id, so no comparison can succeed and the class is read-only for everybody. Singleton classes and metaclasses inherit the owner of the attached class/module instead of the Ractor which happened to trigger their lazy creation. rb_class_owned_p() is inline: later commits put it on the class instance variable and constant read paths, where it replaces what used to be a single load of ruby_single_main_ractor. Only the non-main-owner half is out of line. That needs rb_ractor_main_p() in internal/class.h, hence the include guard ractor_core.h was missing. This commit only records the ownership and introduces the rb_class_owned_p()/rb_class_owner_check() helpers; no behavior changes yet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Only the Ractor which created a class/module may modify it: define, remove or undefine methods, alias, include/prepend, refine, define or remove constants, register an autoload, and set the class's own instance variables. Reading is unchanged. This replaces the assorted "main Ractor only" rules with one creator-based rule, and in exchange a Ractor gets full use of the classes it creates itself -- unshareable constant and instance variable values included, which even the main Ractor could not do for its own classes before. Three paths reach a class's tables without going through rb_class_modify_check, so they get the check of their own: Module#refine (which writes its refinement tables into the receiver as well as into the refined class), Class#initialize (which writes the superclass of a not-yet-initialized class, and had no frozen check either), and Module#ruby2_keywords. The constant inline cache has to move with the rule. Its hit condition was "shareable value, or we are the main Ractor", which was sound only because a non-main Ractor could not have an unshareable constant in the first place. Now it can, so an owner priming the cache would let any other Ractor read that value straight out of it, never reaching the owner check in vm_get_ev_const. The entry records the Ractor which filled it instead -- the one that passed the check -- which also gives a non-main owner a fast path it never had. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A class variable is shared across the inheritance chain and the class it is stored in can migrate (cvar_overtaken), so the deciding Ractor is the owner of the class the variable is actually stored in, not of the receiver it was looked up through. That is a single well-defined owner at any moment, it is the same single writer the rest of that class's fields already have, and it keeps the cross-boundary writes out: main cannot write into a Ractor's class, and a Ractor cannot write into main's. Two holes turned up on the way: - Module#remove_class_variable had no ownership check at all, so any Ractor could delete a class variable out of a foreign class. - The class variable inline caches keyed the fast path on rb_ractor_main_p(), and the fast path writes straight into ic->entry->class_value without going through rb_cvar_set. Main running a method of a foreign class therefore skipped the ownership check entirely. The condition is now the owner of class_value, matching what the instance variable fast paths already do -- and a non-main owner gets the fast path it could not reach before. cvar_overtaken() no longer deletes the duplicated entry from a class owned by another Ractor: that deletion is reachable from read paths (rb_cvar_find), so doing it there would be a cross-Ractor write, and it is only clean-up. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Module#initialize_copy (Class#dup etc.) of a class/module created by another Ractor makes a copy owned by the copying Ractor. Since an owner may read the unshareable constant and instance variable values of its own classes, a raw table copy would hand the copier unshareable objects belonging to the source's Ractor. Raise Ractor::IsolationError in that case. The metaclass is checked as well: rb_singleton_class_clone_and_attach copies its fields and constants into the copy's own metaclass, which the copier then owns. Copying classes whose constants and fields are all shareable still works, which gives a Ractor a way to "monkey-patch" a foreign class without mutating it: dup it and modify the copy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Requested by matz on [Feature #22226]: a non-owner freezing somebody else's class is a hole, because it lets a third party make a class permanently unmodifiable. Naming is the same kind of change to the class itself. Module#freeze checks only when it would actually change something, so an already frozen class stays a no-op for every Ractor and idempotent .freeze calls keep working. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
set_method_visibility only called rb_check_frozen, so public/private/protected and module_function never met the ownership check, even though the proposal lists visibility changes as owner-only. A non-owner Ractor could rewrite the method entries of a foreign class through them. The check goes next to the frozen one rather than replacing both with the rb_class_modify_check every other method table modification uses: that one also marks a module initialized, which this path has never done. module_function with no arguments only sets the visibility of the current scope, so it stays unchecked; the definitions that follow are checked as definitions. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A singleton class belongs to the owner of the object it is attached to, and Ractor#send(move: true) hands that object over. Without this the receiver could define singleton methods on a moved object only when its singleton class had not been materialized before the move. The eigenclass chain above it goes too: each of those is attached to the class below it, so by the same rule they follow. The move is refused when any of them holds unshareable constants or variables: the receiver would become their owner, and so allowed to read them, while the sender still holds the objects themselves. The check runs in the preflight pass, before anything is mutated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rship Document the model: only the creator Ractor can modify a class/module, singleton class ownership follows the attached object, classes of a terminated Ractor become read-only, and the main-Ractor-only rules for class instance variables, constants and class variables are generalized to the owner. For a class variable, say which owner decides -- the class it is stored in, not the receiver it was looked up through. State explicitly what ownership does not give. Reads are unsynchronized and a class modification is not atomic, so a non-owner Ractor can observe a class while its owner is modifying it: ownership is a single-writer guarantee, not a consistent view for readers. And that single writer is not always the class's own owner -- M.include(N) rewires the ancestor chain of every class that already includes M, whoever owns them, because modifying M is M's owner's right. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
With RUBY_BOX=1 inherited from the environment, a child that clears or overrides RUBYOPT, or that passes its own warning flags, prints the experimental warning into the captured output and fails the assertions on it. Tests that hand RUBY_BOX to the child themselves still see the warning. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Same reason as the EnvUtil change. A spec that replaces RUBYOPT loses the suppression, so the ruby mspec spawns prints the experimental warning into the output ruby_exe returns. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… input Testing on the default tty should be performed only in the CI environment. ruby/io-console@0e04785a7d
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 : )