<feed xmlns='http://www.w3.org/2005/Atom'>
<title>ruby.git, branch master</title>
<subtitle>The Ruby Programming Language</subtitle>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/'/>
<entry>
<title>gc: take the VM barrier inside rb_objspace_each_objects</title>
<updated>2026-07-04T05:58:44+00:00</updated>
<author>
<name>Koichi Sasada</name>
<email>ko1@atdot.net</email>
</author>
<published>2026-07-04T05:29:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=e5518bee27f9a89114d248dd12adb53d0ea0668c'/>
<id>e5518bee27f9a89114d248dd12adb53d0ea0668c</id>
<content type='text'>
Walking the heap needs a stable set of pages: a concurrent GC on another
Ractor is stop-the-world and would otherwise pause the walk mid-iteration
and free or move objects out from under the callback. Callers that mutate
what they visit (rb_iseq_trace_set_all, rb_clear_attr_ccs, rb_clear_bf_ccs)
also relied on the barrier for atomicity and took it by hand; move it into
the callee so every caller is covered and drop the now-redundant wrappers.

This also gives rb_iseq_remove_coverage_all the barrier it was missing --
it mutates iseqs (ISEQ_COVERAGE_SET) just like its siblings but walked
without stopping other Ractors.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Walking the heap needs a stable set of pages: a concurrent GC on another
Ractor is stop-the-world and would otherwise pause the walk mid-iteration
and free or move objects out from under the callback. Callers that mutate
what they visit (rb_iseq_trace_set_all, rb_clear_attr_ccs, rb_clear_bf_ccs)
also relied on the barrier for atomicity and took it by hand; move it into
the callee so every caller is covered and drop the now-redundant wrappers.

This also gives rb_iseq_remove_coverage_all the barrier it was missing --
it mutates iseqs (ISEQ_COVERAGE_SET) just like its siblings but walked
without stopping other Ractors.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>iseq: use RB_OBJ_WRITE for the lazy-load loader object</title>
<updated>2026-07-04T05:54:35+00:00</updated>
<author>
<name>Koichi Sasada</name>
<email>ko1@atdot.net</email>
</author>
<published>2026-07-04T04:01:06+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=db3a1939f54c57747cb8af4f2aac41c1acf0021a'/>
<id>db3a1939f54c57747cb8af4f2aac41c1acf0021a</id>
<content type='text'>
iseq-&gt;aux.loader.obj holds the ibf loader while an iseq is
ISEQ_NOT_LOADED_YET, and iseq_mark marks it, so the store into it is a
GC-managed reference and should go through the write barrier rather than a
raw assignment.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
iseq-&gt;aux.loader.obj holds the ibf loader while an iseq is
ISEQ_NOT_LOADED_YET, and iseq_mark marks it, so the store into it is a
GC-managed reference and should go through the write barrier rather than a
raw assignment.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>Use shape bits to store capacity of object</title>
<updated>2026-07-04T01:14:12+00:00</updated>
<author>
<name>Peter Zhu</name>
<email>peter@peterzhu.ca</email>
</author>
<published>2026-06-30T05:10:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=870c8d6a50dae8855f9167bc3c8ec8286d86e964'/>
<id>870c8d6a50dae8855f9167bc3c8ec8286d86e964</id>
<content type='text'>
This commit uses 7 shape bits to store the capacity of the object instead
of the heap ID. This allows for slot sizes with up to capacity of 127.
This removes the abstraction leak of slot sizes from the GC, which allows
more flexibility in the GC.

This implementation should currently make no difference for the default
GC as it will end up creating the same root shapes. We can see that there
is basically no change in benchmarks:

--------------  ------------  ------------  ------------  ------------  --------------  -------------  -----------------
bench            master (ms)     RSS (MiB)   branch (ms)     RSS (MiB)  branch 1st itr  master/branch  RSS master/branch
activerecord     99.6 ± 3.9%   75.5 ± 0.3%   98.9 ± 2.4%   74.0 ± 1.3%           0.919          1.007              1.021
chunky-png      328.2 ± 0.6%   83.0 ± 3.9%  336.4 ± 0.7%   87.0 ± 2.2%           0.979          0.976              0.954
erubi-rails     440.4 ± 1.7%  138.1 ± 0.3%  431.0 ± 0.5%  135.2 ± 0.0%           0.992          1.022              1.022
hexapdf         855.6 ± 0.7%  577.0 ± 0.1%  856.7 ± 0.9%  649.0 ± 2.6%           0.993          0.999              0.889
liquid-c         21.6 ± 8.2%  69.6 ± 14.7%   23.2 ± 8.4%  64.6 ± 15.4%           0.993          0.930              1.077
liquid-compile   19.9 ± 7.9%   47.0 ± 5.7%   20.2 ± 8.7%   46.9 ± 5.1%           0.925          0.987              1.001
liquid-render    54.0 ± 2.7%   55.3 ± 8.6%   55.2 ± 2.7%   55.9 ± 9.0%           0.977          0.978              0.990
lobsters        351.6 ± 0.7%  336.8 ± 0.2%  357.0 ± 0.6%  335.4 ± 0.1%           0.997          0.985              1.004
mail             47.5 ± 3.1%   75.6 ± 2.7%   46.8 ± 1.3%   72.3 ± 0.5%           0.974          1.016              1.045
psych-load      829.1 ± 1.2%   55.0 ± 0.4%  832.9 ± 0.5%   55.3 ± 0.7%           1.006          0.995              0.995
railsbench      713.8 ± 0.4%  134.6 ± 1.1%  717.9 ± 1.0%  138.2 ± 1.2%           0.992          0.994              0.974
rubocop          73.1 ± 6.0%  106.7 ± 1.7%   73.2 ± 6.1%  106.4 ± 1.4%           0.997          0.998              1.003
ruby-lsp         69.0 ± 1.2%   78.3 ± 0.2%   67.8 ± 2.3%   74.2 ± 0.0%           1.043          1.017              1.055
sequel           21.7 ± 6.8%   56.1 ± 1.6%   21.0 ± 4.4%   55.9 ± 1.1%           0.873          1.032              1.003
shipit          608.9 ± 1.4%  160.2 ± 0.5%  619.2 ± 1.4%  157.3 ± 0.2%           1.012          0.983              1.018
--------------  ------------  ------------  ------------  ------------  --------------  -------------  -----------------
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
This commit uses 7 shape bits to store the capacity of the object instead
of the heap ID. This allows for slot sizes with up to capacity of 127.
This removes the abstraction leak of slot sizes from the GC, which allows
more flexibility in the GC.

This implementation should currently make no difference for the default
GC as it will end up creating the same root shapes. We can see that there
is basically no change in benchmarks:

--------------  ------------  ------------  ------------  ------------  --------------  -------------  -----------------
bench            master (ms)     RSS (MiB)   branch (ms)     RSS (MiB)  branch 1st itr  master/branch  RSS master/branch
activerecord     99.6 ± 3.9%   75.5 ± 0.3%   98.9 ± 2.4%   74.0 ± 1.3%           0.919          1.007              1.021
chunky-png      328.2 ± 0.6%   83.0 ± 3.9%  336.4 ± 0.7%   87.0 ± 2.2%           0.979          0.976              0.954
erubi-rails     440.4 ± 1.7%  138.1 ± 0.3%  431.0 ± 0.5%  135.2 ± 0.0%           0.992          1.022              1.022
hexapdf         855.6 ± 0.7%  577.0 ± 0.1%  856.7 ± 0.9%  649.0 ± 2.6%           0.993          0.999              0.889
liquid-c         21.6 ± 8.2%  69.6 ± 14.7%   23.2 ± 8.4%  64.6 ± 15.4%           0.993          0.930              1.077
liquid-compile   19.9 ± 7.9%   47.0 ± 5.7%   20.2 ± 8.7%   46.9 ± 5.1%           0.925          0.987              1.001
liquid-render    54.0 ± 2.7%   55.3 ± 8.6%   55.2 ± 2.7%   55.9 ± 9.0%           0.977          0.978              0.990
lobsters        351.6 ± 0.7%  336.8 ± 0.2%  357.0 ± 0.6%  335.4 ± 0.1%           0.997          0.985              1.004
mail             47.5 ± 3.1%   75.6 ± 2.7%   46.8 ± 1.3%   72.3 ± 0.5%           0.974          1.016              1.045
psych-load      829.1 ± 1.2%   55.0 ± 0.4%  832.9 ± 0.5%   55.3 ± 0.7%           1.006          0.995              0.995
railsbench      713.8 ± 0.4%  134.6 ± 1.1%  717.9 ± 1.0%  138.2 ± 1.2%           0.992          0.994              0.974
rubocop          73.1 ± 6.0%  106.7 ± 1.7%   73.2 ± 6.1%  106.4 ± 1.4%           0.997          0.998              1.003
ruby-lsp         69.0 ± 1.2%   78.3 ± 0.2%   67.8 ± 2.3%   74.2 ± 0.0%           1.043          1.017              1.055
sequel           21.7 ± 6.8%   56.1 ± 1.6%   21.0 ± 4.4%   55.9 ± 1.1%           0.873          1.032              1.003
shipit          608.9 ± 1.4%  160.2 ± 0.5%  619.2 ± 1.4%  157.3 ± 0.2%           1.012          0.983              1.018
--------------  ------------  ------------  ------------  ------------  --------------  -------------  -----------------
</pre>
</div>
</content>
</entry>
<entry>
<title>Postponed jobs targeted at a specific Ractor</title>
<updated>2026-07-04T00:03:29+00:00</updated>
<author>
<name>Koichi Sasada</name>
<email>ko1@atdot.net</email>
</author>
<published>2026-06-11T23:58:44+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=e70011179e75ac0e4157bf61793321c94fadb6dd'/>
<id>e70011179e75ac0e4157bf61793321c94fadb6dd</id>
<content type='text'>
rb_postponed_job_trigger_for_ractor(h, running_ractor) runs a
preregistered postponed job on running_ractor rather than on the
caller's or the main Ractor: the handle's bit is set in a per-Ractor
atomic mask and a POSTPONED_JOB interrupt is posted to the Ractor's
running EC (or its main thread's EC before it starts running);
whichever of the target's threads next checks interrupts drains the
mask in rb_postponed_job_flush alongside the VM-global bitset.

Delivery is trap-style lazy, like rb_postponed_job_trigger:

* the target is not woken out of a blocking call (no unblock
  function); a blocked Ractor picks the job up at its next natural
  safepoint,
* jobs targeted at a Ractor that exits before checking them are
  simply discarded with it,
* across fork(2), jobs that targeted the forking Ractor survive into
  the child (it is the child's main Ractor now); jobs for any other
  Ractor are discarded.

The function is internal (declared in vm_core.h) and exported only
for ext/-test-/postponed_job for now; the first planned user is
per-Ractor GC (handing a terminated Ractor's objspace over to the
main Ractor by a main-targeted job). Promoting it to the public C
API can be discussed separately.
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
rb_postponed_job_trigger_for_ractor(h, running_ractor) runs a
preregistered postponed job on running_ractor rather than on the
caller's or the main Ractor: the handle's bit is set in a per-Ractor
atomic mask and a POSTPONED_JOB interrupt is posted to the Ractor's
running EC (or its main thread's EC before it starts running);
whichever of the target's threads next checks interrupts drains the
mask in rb_postponed_job_flush alongside the VM-global bitset.

Delivery is trap-style lazy, like rb_postponed_job_trigger:

* the target is not woken out of a blocking call (no unblock
  function); a blocked Ractor picks the job up at its next natural
  safepoint,
* jobs targeted at a Ractor that exits before checking them are
  simply discarded with it,
* across fork(2), jobs that targeted the forking Ractor survive into
  the child (it is the child's main Ractor now); jobs for any other
  Ractor are discarded.

The function is internal (declared in vm_core.h) and exported only
for ext/-test-/postponed_job for now; the first planned user is
per-Ractor GC (handing a terminated Ractor's objspace over to the
main Ractor by a main-targeted job). Promoting it to the public C
API can be discussed separately.
</pre>
</div>
</content>
</entry>
<entry>
<title>test: a new Ractor does not inherit the creating thread's fiber storage</title>
<updated>2026-07-03T16:25:03+00:00</updated>
<author>
<name>Koichi Sasada</name>
<email>ko1@atdot.net</email>
</author>
<published>2026-07-02T21:23:11+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=9233cc3b07cff4429538fe6336e15a8caee36c62'/>
<id>9233cc3b07cff4429538fe6336e15a8caee36c62</id>
<content type='text'>
Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>vm_trace: read postponed-job table entries atomically</title>
<updated>2026-07-03T15:51:20+00:00</updated>
<author>
<name>Koichi Sasada</name>
<email>ko1@atdot.net</email>
</author>
<published>2026-07-02T20:36:43+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=6f13d75598568b68193dede5d01813a279bd3c40'/>
<id>6f13d75598568b68193dede5d01813a279bd3c40</id>
<content type='text'>
rb_postponed_job_preregister already stores table[i].func/data with atomic
CAS/EXCHANGE, but rb_postponed_job_flush read them non-atomically, which
races when a job is (pre)registered on another thread while a job fires.
Load them atomically.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
rb_postponed_job_preregister already stores table[i].func/data with atomic
CAS/EXCHANGE, but rb_postponed_job_flush read them non-atomically, which
races when a job is (pre)registered on another thread while a job fires.
Load them atomically.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>depend: add ruby/ractor.h for concurrent_set.o</title>
<updated>2026-07-03T14:50:37+00:00</updated>
<author>
<name>Koichi Sasada</name>
<email>ko1@atdot.net</email>
</author>
<published>2026-07-02T21:17:24+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=c4bda536f67c1616ae2047f9ac1538d66c98f133'/>
<id>c4bda536f67c1616ae2047f9ac1538d66c98f133</id>
<content type='text'>
concurrent_set.c now includes ruby/ractor.h for RB_OBJ_SET_SHAREABLE.
(id_table.c is #included into symbol.c, which already depends on ruby/ractor.h,
so no separate entry is needed.)

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
concurrent_set.c now includes ruby/ractor.h for RB_OBJ_SET_SHAREABLE.
(id_table.c is #included into symbol.c, which already depends on ruby/ractor.h,
so no separate entry is needed.)

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>mark internal cross-Ractor structures as shareable</title>
<updated>2026-07-03T14:50:37+00:00</updated>
<author>
<name>Koichi Sasada</name>
<email>ko1@atdot.net</email>
</author>
<published>2026-07-02T20:31:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=3c3a805c35ebcc396625499034296a63478de682'/>
<id>3c3a805c35ebcc396625499034296a63478de682</id>
<content type='text'>
The concurrent set, managed id-table dups and symbol id-entry buckets are
reachable from every Ractor via VM-global state (the frozen-string/symbol
tables, shape-tree edge tables, the symbol table), so flag them shareable --
as enc_list_update already does for the encoding list.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
The concurrent set, managed id-table dups and symbol id-entry buckets are
reachable from every Ractor via VM-global state (the frozen-string/symbol
tables, shape-tree edge tables, the symbol table), so flag them shareable --
as enc_list_update already does for the encoding list.

Co-Authored-By: Claude Opus 4.8 (1M context) &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>[ruby/json] Add JSON::ResumableParser#partial_value? and #empty?</title>
<updated>2026-07-03T09:24:56+00:00</updated>
<author>
<name>Shizuo Fujita</name>
<email>fujita@clear-code.com</email>
</author>
<published>2026-07-03T07:33:08+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=cf42ce9d17073e7541cf8e992f8b273346173bd7'/>
<id>cf42ce9d17073e7541cf8e992f8b273346173bd7</id>
<content type='text'>
There was no single API answering "does the stream end in the middle of
a document?" once all parseable fed bytes have been consumed. Callers
had to combine two complementary APIs:

  !parser.rest.empty? || !parser.partial_value.nil?

`rest` only reflects unconsumed tokenizer bytes, so it is empty when the
stream is truncated exactly on a token boundary (right after a ':' or
','), while `partial_value` is nil when truncation happens mid-token
before any container is registered. Neither alone covers all shapes,
and `partial_value` materializes the partially built Ruby objects just
to test for nil.

`partial_value?` answers the same question as `!partial_value.nil?` by
looking at the parser's internal value stack directly, without building
the partial Ruby object graph.

`empty?` is strict: true only when the buffer is fully consumed, no
document is under construction and no parsed value awaits retrieval
with `value`. It is defined in Ruby as the composition of the three
underlying predicates so its definition doubles as documentation:

  def empty?
    eos? &amp;&amp; !partial_value? &amp;&amp; !value?
  end

https://github.com/ruby/json/commit/0864e83701

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
There was no single API answering "does the stream end in the middle of
a document?" once all parseable fed bytes have been consumed. Callers
had to combine two complementary APIs:

  !parser.rest.empty? || !parser.partial_value.nil?

`rest` only reflects unconsumed tokenizer bytes, so it is empty when the
stream is truncated exactly on a token boundary (right after a ':' or
','), while `partial_value` is nil when truncation happens mid-token
before any container is registered. Neither alone covers all shapes,
and `partial_value` materializes the partially built Ruby objects just
to test for nil.

`partial_value?` answers the same question as `!partial_value.nil?` by
looking at the parser's internal value stack directly, without building
the partial Ruby object graph.

`empty?` is strict: true only when the buffer is fully consumed, no
document is under construction and no parsed value awaits retrieval
with `value`. It is defined in Ruby as the composition of the three
underlying predicates so its definition doubles as documentation:

  def empty?
    eos? &amp;&amp; !partial_value? &amp;&amp; !value?
  end

https://github.com/ruby/json/commit/0864e83701

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>[ruby/json] Fix ResumableParser treating unterminated line comments as complete</title>
<updated>2026-07-03T09:22:42+00:00</updated>
<author>
<name>Masataka Pocke Kuwabara</name>
<email>kuwabara@pocke.me</email>
</author>
<published>2026-07-02T09:52:53+00:00</published>
<link rel='alternate' type='text/html' href='https://git.ruby-lang.org/ruby.git/commit/?id=31bf04b4094a255b36f482a53f23e2fa3e8c8a1c'/>
<id>31bf04b4094a255b36f482a53f23e2fa3e8c8a1c</id>
<content type='text'>
A `//` line comment split across a feed boundary was treated as fully
consumed, so comment body delivered in a later chunk leaked out as parsed
JSON values.

## Reproduction

```ruby
require "json"

parser = JSON::ResumableParser.new(allow_comments: true)
documents = []
"[1]//[999]\n[3]".each_char do |char|
  parser &lt;&lt; char
  documents &lt;&lt; parser.value while parser.parse
end
p documents
```

## Expected Behavior

```
[[1], [3]]
```

The `[999]` is inside the `//` comment (which runs until the newline), so it
is ignored, leaving the two documents `[1]` and `[3]`. This is what feeding
the whole string in a single chunk produces.

## Actual Behavior

```
[[1], [999], [3]]
```

The commented-out `[999]` leaked out as a real value. When the comment body
does not form valid JSON the split feed raises a ParserError instead. This
happens with the default configuration too, since comments are accepted
(with a deprecation warning) unless disabled.

## Description

A `//` line comment is only terminated by a newline. `json_eat_comments`
searched for that newline and, when none was present in the buffer, moved
the cursor to the end of the buffer and returned as if the comment had been
fully consumed.

For the one-shot `JSON.parse` this is correct: the buffer holds the whole
document, so a `//` comment with no trailing newline genuinely runs to the
end of input. For `ResumableParser`, however, the end of the buffer is only
a chunk boundary, not the end of input. Reaching it does not mean the comment
is over -- the newline (and possibly more comment body) may still arrive in a
later `&lt;&lt;`. Swallowing the buffered bytes as a finished comment loses the
"still inside a comment" state, so any comment body delivered in the next
chunk was parsed as JSON instead of being ignored. Because the result then
depended on where the input happened to be split, the same byte stream could
produce different values.

The block-comment branch already handles this correctly: when it cannot find
the closing `*/` it rewinds to the comment start and raises an EOS-tagged
error, which `ResumableParser#parse` swallows into a `false` return so the
comment is retried once more input is available. This change makes the
line-comment branch behave the same way, but only in resumable mode
(detected via `state-&gt;parser`, the same discriminator `raise_parse_error`
already uses). In non-resumable mode the previous behaviour is preserved: a
`//` comment with no trailing newline is still consumed to the end of input.

As a consequence, a stream that ends with an unterminated line comment now
stays incomplete (`parse` keeps returning `false`) until a newline arrives,
which mirrors how a trailing bare number is only considered complete once a
following separator is seen.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01PpeQSEFkF1X1Uuzjx9BsYe

https://github.com/ruby/json/commit/9dbfeb83df
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
A `//` line comment split across a feed boundary was treated as fully
consumed, so comment body delivered in a later chunk leaked out as parsed
JSON values.

## Reproduction

```ruby
require "json"

parser = JSON::ResumableParser.new(allow_comments: true)
documents = []
"[1]//[999]\n[3]".each_char do |char|
  parser &lt;&lt; char
  documents &lt;&lt; parser.value while parser.parse
end
p documents
```

## Expected Behavior

```
[[1], [3]]
```

The `[999]` is inside the `//` comment (which runs until the newline), so it
is ignored, leaving the two documents `[1]` and `[3]`. This is what feeding
the whole string in a single chunk produces.

## Actual Behavior

```
[[1], [999], [3]]
```

The commented-out `[999]` leaked out as a real value. When the comment body
does not form valid JSON the split feed raises a ParserError instead. This
happens with the default configuration too, since comments are accepted
(with a deprecation warning) unless disabled.

## Description

A `//` line comment is only terminated by a newline. `json_eat_comments`
searched for that newline and, when none was present in the buffer, moved
the cursor to the end of the buffer and returned as if the comment had been
fully consumed.

For the one-shot `JSON.parse` this is correct: the buffer holds the whole
document, so a `//` comment with no trailing newline genuinely runs to the
end of input. For `ResumableParser`, however, the end of the buffer is only
a chunk boundary, not the end of input. Reaching it does not mean the comment
is over -- the newline (and possibly more comment body) may still arrive in a
later `&lt;&lt;`. Swallowing the buffered bytes as a finished comment loses the
"still inside a comment" state, so any comment body delivered in the next
chunk was parsed as JSON instead of being ignored. Because the result then
depended on where the input happened to be split, the same byte stream could
produce different values.

The block-comment branch already handles this correctly: when it cannot find
the closing `*/` it rewinds to the comment start and raises an EOS-tagged
error, which `ResumableParser#parse` swallows into a `false` return so the
comment is retried once more input is available. This change makes the
line-comment branch behave the same way, but only in resumable mode
(detected via `state-&gt;parser`, the same discriminator `raise_parse_error`
already uses). In non-resumable mode the previous behaviour is preserved: a
`//` comment with no trailing newline is still consumed to the end of input.

As a consequence, a stream that ends with an unterminated line comment now
stays incomplete (`parse` keeps returning `false`) until a newline arrives,
which mirrors how a trailing bare number is only considered complete once a
following separator is seen.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01PpeQSEFkF1X1Uuzjx9BsYe

https://github.com/ruby/json/commit/9dbfeb83df
</pre>
</div>
</content>
</entry>
</feed>
