Long-running function

Here is a problem that crops up occasionally:

consider the code fragment

puts "Hello"

long_running_function_with_side_effects

puts "World"

When the computationally-heavy function runs for too long, it causes the thread to fall too far behind time and raise an exception. What we would like to happen is for the function to run asynchronously in the background without affecting the current thread’s time at all. For instance, we could wrap it in an in_thread { ... } or Thread.new {...} block, but then that thread will get behind time. What is the suggested solution, assuming we want to execute a block of code without timing? Also, if you find alternate approaches or hacks like using Ractors instead of Threads more elegant, please tell!

Have you tried adding a sleep before the call to the long running function? I think that would give Sonic Pi some time to execute the code before the side-effects have to be applied.

Or if that fails, I think interspersing some shorter sleeps throughout the long running function might help.

Do you really mean before the call?

in_thread { long_running_function }
sleep rt(120)

seems to work; however

in_thread { long_running_function }
while not_done do
  puts "..."
  sleep 1
end

fails right away.

I was thinking something like:

in_thread do
  sleep 5
  long_running_function
end

I may well be wrong, but from my understanding of how Sonic Pi works, I would have thought the sleep before the function call would work best, because it’s basically saying that Sonic Pi now has 5 seconds (or whatever) before the side-effects of the function need to be visible.

However, increasing the sleep over the sched_ahead_time is unlikely to give any extra benefit, so you might need to also increase that with set_sched_ahead_time! 5 or whatever. But watch out, because too big a value here might create too much delay between the code executing and the sound coming out, especially if live coding.

In that case, instead of (or as well as) increasing the sched_ahead_time time, it might be better to intersperse sleeps inside the function, something like:

define :long_running_function do
  some_code
  1000.times do
    some_more_code
    sleep 0.01 # add a small sleep inside a loop
  end
  other_code
end

Of course, this only works if the function is structured in such a way to make this possible, but if that is the case it might be worth a try.

I don’t know how much any of this will work, they are just my thoughts and I haven’t tried them out, so maybe someone else will come along with a better idea.

Hi there,

At this point in time, Sonic Pi doesn’t support “long running” sections of code. This could be considered for the future, but as of now, by design, there aren’t any officially supported functions that would typically take a long time to execute.

Could I ask what you’re trying to do with the long running functions?

It is not (yet) for live performance, obviously.

It was part of a series of experimental demos; in this case you specify a bassline, and the script automatically calculates extra voices to turn it into 2 or 3 or 4-part harmony. However, if you work on too many bars or too many voices at the same time, the function times out (in this case because it is badly implemented, but you could imagine a calculation that genuinely takes a fair bit of computation, like some of those neural-network based models).

Sure, totally makes sense.

Would something like this work for you:

puts "hello"

with_no_timing_safety do
  # long running code
end

puts "world"

In my case that would work, because the function does not trigger any synths; it just returns a value (and it does not matter if the calculation takes a few seconds).

Ok, well if you’re returning a value to use, that should probably be exchanged using the set/sync system - which is the supported way to share values across threads (assuming you wanted the long running thing to be async).

That way the thread that is waiting for the value can call sync and the thread that is creating the value can call set with the new value when it is ready.

Out of interest, what kind of value is your function returning?

The value is a Hash (I am not saying it absolutely must be a hash, but that is what was going on in the test).

I tested it a little bit, and set/sync was not working as expected (or, better to say, it worked exactly as expected) because of that. But it’s not the mutability of the value that was an issue in the first place, rather the timing of the thread that performs the async calculation; merely putting a sync in the main thread did not prevent the timing exception. Compare the following pseudocode, which does not seem to crash when I run it:

init_stuff
long_running_function
__init_spider_time_and_beat!
play_the_song

Cool, to be clear, the set/sync stuff only makes sense here from within a thread that has no timing safety.

This is something I’ll look to add post v4 and will make available in a new BETA so you and others can have an opportunity to play with it and give feedback :slight_smile: