Replies: 3 comments 1 reply
-
Thanks for writing up this proposal. At a high-level, I agree that making a goroutine equivalent in Risor is needed. You kind of touched on this I think, but I have thought that something like the Javascript Web Worker API might do it. Strict isolation, with no data sharing, only messaging. Having each coroutine actually have its own VM could be part of the solution, potentially. You also mentioned this in passing it looks like. Sorry I'll spend more time reading your proposal in detail soon. |
Beta Was this translation helpful? Give feedback.
-
What do you think about this approach? #155 |
Beta Was this translation helpful? Give feedback.
-
The |
Beta Was this translation helpful? Give feedback.
-
I'm mostly rewriting my Bash scripts in Risor.
One feature I'm really missing is the "run in background" (bash:
&
, go: goroutines)Adding this would not be super easy feat, as it requires new thinking in the vm. Should it be proper multi-threading, or can some simpler solution be sufficient?
To not have to deal with race conditions, I suggest doing an event-based solution on a single thread. Similar to languages like JavaScript and Lua.
As for syntax, as Risor pivots itself towards Go developers, I think it'd be only natural if it was using the
go
syntax.However, instead of having to deal with mutexes, wait groups, channels, etc, I suggest that a
go
expression returns a handle to the coroutine. Similar to JavaScript'sPromise
, or C#'sTask
.So that code like this could be possible:
If the function had an error, then
.wait()
would also return that error.Avoiding function coloring
The hot topic of the year in language/library design is: What Color is Your Function?
I think that taking a similar approach to Bash's "job control", Go's "goroutines" and Zig's "colorblind async/await model" would be good.
Meaning, all functions are just normally synchronous. But then you can tell the runtime to run a certain function in the background.
What happens with un-waited coroutines
Given this:
What should happen, is that the Risor VM should just automatically wait for any dangling coroutines at the end of execution.
In practice, it would as if it ran this:
But implemented in the VM, not in the compiler.
Implementation
A simple way to implement the coroutines would be as in the definition of a coroutine. It's a function that can be suspended, and later resumed.
My proposed approach:
coroutine
x := go myfunc()
coroutine
objectcoroutine
object to a queue for potential future processingx.wait()
coroutine
coroutine
in the queueIO operations (e.g
fetch
,exec.command
) would need to be implemented as:coroutine
object who's.wait()
will wait for the underlying Go goroutine to finishfetch(...)
function itself calls this.wait()
functionThis is basically an event-based multi-threading model. The IO operations make use of Go goroutines to actually run in parallel, but the Risor code is always executed on the same thread in sequence.
This is very much akin to how languages like JavaScript, Lua, and even most Rust libraries deals with coroutines, except that in this proposal it inverses the calling convention of async functions, so that they're the same as non-async functions.
So one way to look at this is that a function call would always try to "await" the function if it is "awaitable", but otherwise just run it as usual.
Drawbacks in implementation
One thing that might be considered confusing is when a piece of code is actually executed.
Consider the example:
The output here would be:
While the following:
Outputs:
Alternative implementation
Same as before, but when entering a
go myfunc()
, start executing as much as possible, until you reach a suspending IO operation.This is akin to C#'s multi-threading implementation.
And in effect then the above example would be:
Outputs:
Scope of changes
go
keyword.go
keyword into the equivalent of just a function call, likego myfunc(1,2,3)
=go(myfunc, 1, 2, 3)
. No complex data shuffling needed here.Beta Was this translation helpful? Give feedback.
All reactions