<div dir="ltr"><div dir="ltr"><p class="gmail-p1"><b>Thank you for your reply.</b><b></b></p><p class="gmail-p2">While testing the code, I found an issue when using the custom scheduler feature.</p><p class="gmail-p2">When I delegate to the default scheduler, I noticed that the nested virtual thread created inside another virtual thread does <span class="gmail-s1"><b>not</b></span> actually “use” my custom scheduler.<br>``` java</p><p class="gmail-p2">public static void main(String[] args) throws Throwable {<br>    System.getProperties().setProperty("jdk.virtualThreadScheduler.implClass", "io.github.dreamlike.CustomerVirtualThreadScheduler");<br>    CompletableFuture<Void> future = new CompletableFuture<>();<br>    Thread.ofVirtual()<br>            .name("parent vt")<br>            .start(() -> {<br>                Thread.ofVirtual().name("sub vt")<br>                        .start(() -> {<br>                            future.complete(null);<br>                        });<br>            });<br><br>    future.get();<br>}<br><br>// other file io/github/dreamlike/CustomerVirtualThreadScheduler.java<br>public class CustomerVirtualThreadScheduler implements Thread.VirtualThreadScheduler  {<br><br>    private final Thread.VirtualThreadScheduler defaultScheduler;<br><br>    public CustomerVirtualThreadScheduler(Thread.VirtualThreadScheduler defaultScheduler) {<br>        this.defaultScheduler = defaultScheduler;<br>    }<br><br>    @Override<br>    public void execute(Thread vthread, Runnable task) {<br>        Thread thread = Thread.currentThread();<br>        System.out.println(thread.getClass());<br>        System.out.println(vthread.toString() + " " + vthread.getState() + " currentThread " + thread + " current is virtual " + thread.isVirtual());<br>        new RuntimeException().printStackTrace();<br>        defaultScheduler.execute(vthread, task);<br>    }<br>}<br>```<br></p><p class="gmail-p1">Console output:<br>``` bash<br>WARNING: Using custom default scheduler, this is an experimental feature!<br>class java.lang.Thread<br>VirtualThread[#27,VirtualThreadA]/runnable RUNNABLE currentThread Thread[#3,main,5,main] current is virtual false<br>java.lang.RuntimeException<br>        at io.github.dreamlike.CustomerVirtualThreadScheduler.execute(CustomerVirtualThreadScheduler.java:16)<br>        at java.base/java.lang.VirtualThread.submitRunContinuation(VirtualThread.java:367)<br>        at java.base/java.lang.VirtualThread.externalSubmitRunContinuationOrThrow(VirtualThread.java:456)<br>        at java.base/java.lang.VirtualThread.start(VirtualThread.java:734)<br>        at java.base/java.lang.VirtualThread.start(VirtualThread.java:745)<br>        at java.base/java.lang.ThreadBuilders$VirtualThreadBuilder.start(ThreadBuilders.java:257)<br>        at io.github.dreamlike.VTMain.main(VTMain.java:14)<br>```<br><br></p><p class="gmail-p1">After checking the relevant code, it seems that execution goes through the <span class="gmail-s1"><b>first branch</b></span> below.</p><p class="gmail-p1"></p><p class="gmail-p1">This behavior doesn’t appear to align with the semantics of a custom scheduler — the nested virtual thread is submitted directly to <span class="gmail-s2">ct.getPool()</span> instead of going through <span class="gmail-s2">CustomerVirtualThreadScheduler::execute</span>.<br>``` java</p><p class="gmail-p1">// VirtualThread.java<br>private void externalSubmitRunContinuationOrThrow() {<br>    if (scheduler == DEFAULT_SCHEDULER && currentCarrierThread() instanceof CarrierThread ct) {<br>        try {<br>            ct.getPool().externalSubmit(ForkJoinTask.adapt(runContinuation));<br>        } catch (RejectedExecutionException ree) {<br>            submitFailed(ree);<br>            throw ree;<br>        }<br>    } else {<br>        submitRunContinuation(false);<br>    }<br>}<br>```<br></p><p class="gmail-p1">Best regards,</p><p class="gmail-p1"></p><p class="gmail-p2">Mengyang li</p></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">Alan Bateman <<a href="mailto:alan.bateman@oracle.com">alan.bateman@oracle.com</a>> 于2025年10月5日周日 00:35写道:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><u></u><div><div>On 04/10/2025 14:19, dreamlike_ocean lei wrote:<br></div><blockquote type="cite"><div dir="ltr"><p>Hello @loom-dev <loom-dev at <a href="http://openjdk.org/" target="_blank">openjdk.org</a>>,</p><p>I have been enjoying the new POLLER_PER_CARRIER design in the Loom repo and I really like the direction it is going.<br>While building on top of the latest code, I noticed a couple of issues and would like to ask for clarification.</p></div></blockquote>Thanks for your mail. It's useful to hear from folks that are trying out the experimental support for custom schedulers in the loom repo as this will help to inform whether any of the directions prototyped should be taken further.<br><br>BTW: I assume "dreamlike_ocean lei" isn't your real name. It would be helpful to use a real name or affiliation so we have some idea who you are.<br><br><blockquote type="cite"><div dir="ltr"><ol><li><p>When calling <code>Thread.startVirtualThread</code>, the new virtual thread does not inherit the scheduler of the calling virtual thread, but instead uses <code>DEFAULT_SCHEDULER</code>. What is the reasoning behind this design? Could there be a mechanism to allow implicit inheritance? This would be very helpful for custom schedulers based on the per-core model.</p></li></ol></div></blockquote>In a system with several virtual thread schedulers in use, which I think is what you mean, then it would be unpredictable and problematic to inherit the scheduler in many cases. If some library were, on first usage, start a virtual thread as a "background thread" that runs indefinitely then inheritance would mean it depends on the first usage. Examples where inheritance at thread create time is problematic are the thread context class loader, and until recently, the inherited access control context.<br><br>When you say "custom schedulers based on the per-core model" do you mean you are experimenting with a scheduler per core in order to get "carrier affinity"  (virtual thread X will always be scheduled on carrier Y). You might also be using processor affinity to bind carrier Y to specific sets of CPUs.<br><br>You might find it simpler to just use one scheduler and keep a mapping of virtual thread to carrier. That would remove complications with lifecycle that would arise if carriers were to terminate (e.g. idle/keep-alive). It might avoid needing to expose APIs to get a virtual thread's scheduler, which I think what your second point is about.<br><br>-Alan</div></blockquote></div></div><br><div class="gmail_quote gmail_quote_container"><div dir="ltr" class="gmail_attr">Alan Bateman <<a href="mailto:alan.bateman@oracle.com">alan.bateman@oracle.com</a>> 于2025年10月5日周日 00:35写道:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><u></u>

  
  <div>
    <div>On 04/10/2025 14:19, dreamlike_ocean
      lei wrote:<br>
    </div>
    <blockquote type="cite">
      
      <div dir="ltr">
        <p>Hello @loom-dev <loom-dev at <a href="http://openjdk.org" target="_blank">openjdk.org</a>>,</p>
        <p>I have been enjoying the new POLLER_PER_CARRIER design in the
          Loom repo and I really like the direction it is going.<br>
          While building on top of the latest code, I noticed a couple
          of issues and would like to ask for clarification.</p>
      </div>
    </blockquote>
    Thanks for your mail. It's useful to hear from folks that are trying
    out the experimental support for custom schedulers in the loom repo
    as this will help to inform whether any of the directions prototyped
    should be taken further.<br>
    <br>
    BTW: I assume "dreamlike_ocean lei" isn't your real name. It would
    be helpful to use a real name or affiliation so we have some idea
    who you are.<br>
    <br>
    <blockquote type="cite">
      <div dir="ltr">
        <ol>
          <li>
            <p>When calling <code>Thread.startVirtualThread</code>, the
              new virtual thread does not inherit the scheduler of the
              calling virtual thread, but instead uses <code>DEFAULT_SCHEDULER</code>.
              What is the reasoning behind this design? Could there be a
              mechanism to allow implicit inheritance? This would be
              very helpful for custom schedulers based on the per-core
              model.</p>
          </li>
        </ol>
      </div>
    </blockquote>
    In a system with several virtual thread schedulers in use, which I
    think is what you mean, then it would be unpredictable and
    problematic to inherit the scheduler in many cases. If some library
    were, on first usage, start a virtual thread as a "background
    thread" that runs indefinitely then inheritance would mean it
    depends on the first usage. Examples where inheritance at thread
    create time is problematic are the thread context class loader, and
    until recently, the inherited access control context.<br>
    <br>
    When you say "custom schedulers based on the per-core model" do you
    mean you are experimenting with a scheduler per core in order to get
    "carrier affinity"  (virtual thread X will always be scheduled on
    carrier Y). You might also be using processor affinity to bind
    carrier Y to specific sets of CPUs. <br>
    <br>
    You might find it simpler to just use one scheduler and keep a
    mapping of virtual thread to carrier. That would remove
    complications with lifecycle that would arise if carriers were to
    terminate (e.g. idle/keep-alive). It might avoid needing to expose
    APIs to get a virtual thread's scheduler, which I think what your
    second point is about.<br>
    <br>
    -Alan<br>
  </div>

</blockquote></div>