I'm working on a little platform in Zig for static site generation. After it generates the site it starts a webserver to serve it. What surprised me was that the webserving portion was using a significant amount of CPU, even when it was doing nothing while waiting for requests.
At first I thought I was maybe doing something silly like an infinite loop in Zig, but I think it's some Roc internals using the CPU, even though the platform is not doing anything at that time. I was curious if anyone might know what's going on.
I've used samply to get some information on what was happening (I think that was a tip from Brendan at one point? Great tool!). It shows that Roc is spending a lot of time in roc_repl_expect::run::ExpectMemory::wait_for_child
(see screenshot below). My constant refreshing to excercise the fileserver doesn't even register, so I really think it's this function that's doing a lot of work for some reason, was wondering if anyone knew why.
screenshot_2024-11-11T20:20:56+01:00.png
I also took a look at the compiler code. I think the expect_child
bit it's running is here:
https://github.com/roc-lang/roc/blob/b7c2cb084e15a56320e652ad3c77fdb8262bc1a4/crates/repl_expect/src/run.rs#L540
There is a loop
there, so that might explain why I'm seeing a lot of CPU usage, but I'm not sure why I'd end up there. Any expect
's would have completed long ago.
Note: I'm running my Roc code as a script using roc run
(the platform is designed to work that way).
I'd be grateful for any ideas!
Can you build roc from source and use a debug build of the compiler?
It seems strange that the repl is even involved at all. But hard to say withiut seeing your setup.
The stack trace is in the image
Apparently roc_dev_native
calls into that
Probably a bug
Reading the code a bit, I think the call into repl code is intentional. My understanding now: because roc run
runs Roc in dev mode we want expect
s to trigger. We fork to have one process running the actual Roc code, another to do bookkeeping with relation to expects that the first encounters.
This reuses some logic used by the repl, I imagine because the repl splits into a repl process and a compile+run process.
My guess is that the logic that gets expect
updates from the child polls the child process in a non-blocking loop, which runs one CPU core as fast as it can. I can take a look at maybe blocking that thread while waiting for child updates.
Another thing I wonder is if it'd be possible/valuable to combine these in a single process in some way. The way I understand roc run
it's not just for development, it's also what scripts run in by default (like ones with a shebang). I don't know if spawning multiple processes when you run a script might break expectations in some ways when using Roc as an alternative for, say, a bash script.
scripts run in by default (like ones with a shebang).
I thought they just run roc script.roc
not using the subcommand
That's true, I thought those were the same thing but don't remember why I think that :sweat_smile:
Probably this really is just the bug that expects are done in an outdated way today
They need to be updated to a platform function
So that whole system should get ripped out
It's possible that roc run
still tries to run the program even if there are compile errors, I recommend using roc dev
. Not sure if that is affecting your issue but it's something to eliminate.
Last updated: Jul 05 2025 at 12:14 UTC