AppOfflineError if the browser is busy with long running functions

I don’t know if this is a bug. Perhaps it’s a known limitation and we need to live with it.
I don’t think it’s a big problem, I can live with it, but I thought I would report it anyway.

Anvil says AppOfflineError which makes me think the problem is on the server or on the connection, instead it’s on the client. Perhaps AppOfflineError is intended to catch all the server call problems, so it’s OK.


I have a form with a timer with interval set to 5 seconds that checks if the current document has been modified by another user. It’s a simple server call that compares the timestamp of the current document with the timestamp of the same document on the database.

The form has a button that runs an optimization on the client that can take a long time. In order to prevent the browser from showing the message about the non responding page, I have split the optimization in smaller discrete steps and there is a second timer with interval set to 0.01 that executes the next step of the optimization. Each step can take anywhere between a few seconds and 1 minute or more, so in theory I could see the non responding page warning anyway, but it’s unlikely.

Everything works well, but once in a while I see the error AppOfflineError: Connection to server failed (1006).

I think this happens when the first timer calls the server, then the second timer takes over to run the next step and it takes longer than the server call likes.

I can disable the 5 second check while the optimization is running, then reenable it at the end. This should fix my form.


Here is a simple app that reproduces the problem:
https://anvil.works/build#clone:EUIRO2CQXVW3MABT=RSN2N47GT6G2OHB6EL66PHYF

Here is the output.
On the left the output from the slow timer. It starts with 21 second long tasks, then 22 seconds, etc.
On the right the output from the server call timer. It works if the slow task lasts less than 30 seconds, it fails if it lasts longer.

3 Likes

This is happening because the client side optimization task described is doing a lot of work and blocking the JavaScript thread.
When that thread is blocked for long enough, the connection to anvil’s server is falling over.
Whenever the connection to the server fails you get the AppOfflineError.

What you can do:
Add a sleep(0) call every so often in your client side optimization task.
This will unblock the thread.

What we will do
We’ll have a look at our internal implementation and see if we can unblock the thread every so often on your behalf :wink:

2 Likes

Adding sleep(0) gives me the control over what happens when.

Unleashing the equivalent at random times could introduce weird behaviors difficult to trace and debug.

It reminds me of threads vs async. We are in an async world, we don’t want to convert it to a threads world and risk to break all kinds of apps.

Perhaps you should “unblock the thread every so often” only to make the server calls happy, and give the control back to the long running task when they are completed?

Then, when the long running task ends, the the event loop will keep doing its job and the line that follows the server call will be executed.

2 Likes

I share this concern. The partial list of things that behave like sleep(0) is here, right? (I can’t quite wrap my head around the right way to talk about this. In your comment, you say “This will unblock the thread.” But the docs refer to sleep() as “blocking code.”)

By the way, I can no longer replicate @stefano.menci’s result with his/your clone link. I’m wondering whether that’s due to a change in the app, Anvil, or both:

It depends of the reference frame: if you sit on the blocked or on the blocker.

sleep() is blocker according to the function that calls it, is unblocker according to the other functions that sit in line waiting for their turn.

When blocking code is executed, the function that contains the blocking code interrupts the execution and goes at the back of the line. By doing so it will unblock all the other threads (I don’t know if thread is technically correct here, but it gives the idea).

When all the other threads are finished with their job, or have reached some blocking code, the event loop will pick the next thread in that is waiting in line.

1 Like

You are right!
The app has not changed, it looks like Anvil’s behavior has changed.
It looks like the behavior of this has changed, and now it is blocking (or unblocking :slight_smile:):

while time.time() < t + self.slow_runner_duration:
    pass
1 Like