Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Running coro in R scripts #34

Open
Fabrice-Clement opened this issue Mar 4, 2021 · 6 comments
Open

Running coro in R scripts #34

Fabrice-Clement opened this issue Mar 4, 2021 · 6 comments

Comments

@Fabrice-Clement
Copy link

Hello,

Nice R package that I love to use now !

I would like to use coro in a R script, but when I run the example https://coro.r-lib.org/reference/async.html with Rscript, it ends immediatly.

How can I wait the termination of the promise before R exits ?

The only ( and dirty ) solution I found is :

# mycoroScript.R
# see https://coro.r-lib.org/reference/async.html
promises::promise_all( async_count_down(5), async_count_up(5) ) %>%  then( function(value) {
        writeLines("q( status = 0)", f <- file("/tmp/fifo", raw=T) ); close(f)
}

To run it from bash :
$ ( mkfifo /tmp/fifo; cat mycoroScript.R; cat /tmp/fifo)  | R --interactive --no-save

Thanks for your advice !

@lionel-
Copy link
Member

lionel- commented Mar 4, 2021

Hello,

Unfortunately this is not officially possible with promises. That's something that I would like to become possible in the future though. If you'd like to use an unsupported workaround in the meantime, see the implementation of wait_for() in https://github.com/r-lib/coro/blob/main/R/utils-promises.R

@Fabrice-Clement
Copy link
Author

Hello lionel,

Thanks for your reply !
After reading your wait_for code and the fact that it is based on later, I do that :

.Last <- while( ! later::loop_empty() ) later::run_now(timeoutSecs = Inf, all = TRUE, loop = later::global_loop())

In fact, waiting for all promises to complete before R exits just fits my needs.

All the best

@dereckmezquita
Copy link

dereckmezquita commented Feb 9, 2025

@Fabrice-Clement I do something similar I include this at the bottom of my script.

while (!later::loop_empty()) {
    later::run_now()
}

CC: @lionel-

@lionel-
Copy link
Member

lionel- commented Feb 10, 2025

@dereckmezquita Interesting. Is this busy-looping or does run_now() block until the next event has been handled? If the former, you might want to throw in a small Sys.sleep to reduce the polling frequency.

@dereckmezquita
Copy link

dereckmezquita commented Feb 10, 2025

@lionel- I did some digging into the behaviour of later::run_now().

Indeed it was busy looping:

while (!later::loop_empty()) {
    later::run_now()
}

If I understood correclty, busy looping means that later::run_now() returns immediately when there aren’t any due events, causing the loop to repeatedly check over and over again with no pause. This constant checking can consume a lot of CPU unnecessarily.

On the other hand, a blocking call would wait until there is an event ready to be processed. For example, if run_now() had a blocking behaviour, it wouldn’t return until it had something to do, meaning the loop wouldn’t run continuously and hog the CPU.

I believe we have two options here: 1. adding a small pause or using timeoutSecs = Inf argument.

This reduces the polling frequency by inserting a brief delay.

while (!later::loop_empty()) {
    later::run_now()
    Sys.sleep(0.001)  # pause for 1 millisecond to lower CPU usage
}

From the later docs:

timeoutSecs: Wait (block) for up to this number of seconds waiting for an operation to be ready to run. If 0, then return immediately if there are no operations that are ready to run. If Inf or negative, then wait as long as it takes (if none are scheduled, then this will block forever).
all: If FALSE, run_now() will execute at most one scheduled operation (instead of all eligible operations). This can be useful in cases where you want to interleave scheduled operations with your own logic.

while (!later::loop_empty()) {
    later::run_now(timeoutSecs = Inf, all = TRUE)
}

This way, the function waits for the next event rather than returning immediately.

I apprecite your suggestion that got me to go look at the docs.

Edit: this works nicely:

# Simulate an asynchronous API call that returns a promise after a delay
get_data_async <- function(url) {
    promises::promise(function(resolve, reject) {
        # Simulate network delay of 1 second
        later::later(function() {
            resolve(paste("Data from", url))
        }, delay = 10)
    })
}

# Define an asynchronous main function
async_main <- coro::async(function() {
    # Await the asynchronous API call
    data <- await(get_data_async("http://example.com/api"))
    cat("Received:", data, "\n")
})

# Kick off the asynchronous main function
async_main()

# Process the event loop until all tasks are completed
while (!later::loop_empty()) {
    later::run_now(timeoutSecs = Inf, all = TRUE)
}

@lionel-
Copy link
Member

lionel- commented Feb 10, 2025

Makes sense, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants