> In the cooperative style, the correctness of any subroutine is implicitly dependent on there not being scheduling points where they're not explicit
Removing double negations, you're saying "cooperative style is bad because the runtime can't interrupt user code implicitly"? You are looking at this from the perspective of the runtime implementer (meaning yourself, thank you by the way), not the programmer. You absolutely should look at this from the perspective of the programmer too.
Preemptive multitasking gives the runtime more capabilities because you can just assume the user has to deal with anything that your implicit rescheduling causes. But users don't actually deal with that, even if they should! The havoc that concurrency bugs are causing is evidence for that!
JavaScript's concurrency model is so nice because despite lacking real parallelism, you only have to think about concurrency (race conditions, data races, deadlocks, ...) at scheduling points. Rust has an interesting model with both parallelism and cooperative async-await. Its type system requires strong guarantees to move data between (preemptively scheduled) threads, but much weaker guarantees to use it in a cooperatively scheduled async function. Of course, it comes at a cost, namely function coloring.
After decades of legacy, none of that is feasible in Java of course, but let's not pretend that preemptive scheduling is objectively "preferable".
> That's a weird way to phrase "in the cooperative style, scheduling points are explicit, and in the preemptive style, the burden is on the developer to get it right".
Because it's the opposite. In the preemptive style, the mutual exclusion assumption -- i.e. the relevant correctness assumption -- is explicit, while in the cooperative style the correctness assumption is implicit and does not compose well. I.e. any addition of a scheduling point requires re-examining the implicit assumptions in all transitive callers.
> Preemptive multitasking gives the runtime more capabilities and makes it easier to implement because you can just assume the user has to deal with anything that your implicit rescheduling causes.
Preemptive multitasking is far, far harder to implement (that's why virtual threads took so long) and it provides superior composability. Cooperative scheduling is very easy to implement (often all it requires is just a change to the frontend compiler, which is why languages that have no support of the backend have to choose it) but it places the burden of examining implicit assumptions on the user.
> After decades of legacy, none of that is feasible in Java of course, but let's not pretend that preemptive scheduling is objectively "preferable".
Java is no different in this case from C# or Rust. They all support threading. But after significant research we've decided to spend years on offering the better approach. It looks like C# has now realised that, while hard, it may be possible for them, too, so they're giving it a go.
If we look at true clean-slate choices, the two languages that are most identified with concurrency as their core -- Erlang and Go -- have also chosen preemptive scheduling. Languages that didn't have either done so due to legacy constraints (JS), lack of control over the backend (Kotlin), complex/expensive allocation in low-level languages (C++ and Rust), or possibly because it's simply much easier (C# I guess).
You don't have to transitively examine callers because you have colored functions. Every awaited call is a scheduling point, and once a function is colored it can't be "doubly colored". You can then still use the mutexes provided by those languages.
From a programmer standpoint, preemptive scheduling is like cooperative scheduling, except that every function and operation is colored as async by default. Sometimes that simplifies things! But sometimes you'd like to have some guarantees that an async function cannot give you.
The "easier to implement" part was a remnant of an older rewording, and I edited it out immediately after posting. You must've seen the comment right after I posted it, sorry about that, that wasn't supposed to be my point.
Either way, we could argue here for days, but the difference in opinions here on this thread AND among programming language maintainers are enough evidence to show that the decision isn't as clear as you make it out to be. It's a little dismissive to pretend that other language designers, eg. those of Python, Kotlin, haven't done their years of research, and made inferior choices due to lack of expertise, when a LOT of developers (eg. myself, or many commenters here on this thread) like those languages particularly because of those decisions.
Opinions differ. And if you do research on Java developers you'll get different results than if you do research on JS developers. For good reasons. I fully trust you that you made the right decision for Java.
> You don't have to transitively examine callers because you have colored functions.
You do, because the problem is not the "await" calls but everything else. The language tells you which are the callers that need to be changed when you a scheduling point, but it doesn't tell you whether that change preserves their correctness or not. You have to analyse every caller individually to know whether there was a reliance on mutual exclusion or not.
> It's a little dismissive to pretend that other language designers, eg. those of Python, Kotlin, haven't done their years of research, and made inferior choices due to lack of expertise,
But I said the opposite: that they chose the best option that was available to them. Kotlin couldn't implement user-mode threads, or at least not efficiently, because they have no control over the platforms they run on. JS also had little choice because adding threads would have broken a lot of existing code. In their place I would have made the same choice. But the point is that in imperative languages the choice of cooperative scheduling is not done because the programming model it offers is attractive but because of other constraints. All else being equal, cooperative scheduling offers worse composition in imperative languages; it's just that in many cases all else is not equal and there are other constraints.
But yes, the important thing is that even though cooperative scheduling would have been so much easier to provide in Java, we decided to take our time and do what offers the best experience to Java developers.
> It's a little dismissive to pretend that other language designers, eg. those of Python, Kotlin, haven't done their years of research
Different features have different levels of depth in languages - as mentioned, where it is only a frontend compiler level change, it definitely didn’t require as much background knowledge as Loom.
Hell, Ron has been working on this exact problem for close to a decade, if I’m not mistaken! Kotlin is barely a decade old!
Removing double negations, you're saying "cooperative style is bad because the runtime can't interrupt user code implicitly"? You are looking at this from the perspective of the runtime implementer (meaning yourself, thank you by the way), not the programmer. You absolutely should look at this from the perspective of the programmer too.
Preemptive multitasking gives the runtime more capabilities because you can just assume the user has to deal with anything that your implicit rescheduling causes. But users don't actually deal with that, even if they should! The havoc that concurrency bugs are causing is evidence for that!
JavaScript's concurrency model is so nice because despite lacking real parallelism, you only have to think about concurrency (race conditions, data races, deadlocks, ...) at scheduling points. Rust has an interesting model with both parallelism and cooperative async-await. Its type system requires strong guarantees to move data between (preemptively scheduled) threads, but much weaker guarantees to use it in a cooperatively scheduled async function. Of course, it comes at a cost, namely function coloring.
After decades of legacy, none of that is feasible in Java of course, but let's not pretend that preemptive scheduling is objectively "preferable".