Worth clarifying what we mean by blocking code
Blocking code:
When the docs talk about blocking code, they are referring to what would be blocking code in regular Python. In Anvil that is most commonly time.sleep()
or anvil.server.call()
.
In client-side Python (Skulpt), code that would block in regular Python cannot block because that’s not possible in a JavaScript runtime. So it “Suspends”. Suspensions are a bit like the yield
statement in a Python generator. The compiler remembers the state at the point the code suspended and when the code that would block in regular Python is ready with a result (and when the JavaScript runtime says it’s ok) the Suspension resumes.
If statements aren’t blocking in regular Python they won’t suspend in Skulpt.
e.g.
with skulpt_interrupts_suspended() as transaction:
# Transfer of funds; must be atomic!
account1 -= amount_to_transfer
account2 += amount_to_transfer
won’t have an effect. Those two lines are not blocking in regular Python, so they won’t suspend in client-side Python and will always run in order as expected.
The demo in the linked post provides examples of blocking code in regular Python that suspends in client-side Python, through its use of sleep()
and anvil.server.call()
. These points of blocking code must suspend in the JavaScript runtime. There is no way to prevent this. You cannot block the JavaScript runtime with a server call or a call to time.sleep()
. If it were possible, JavaScript would have a native sleep
function (it doesn’t).
Most other Python - JavaScript browser options do not have a time.sleep()
for this reason.
- RustPython Demo - python written in rust compiled to WASM running in the browser.
RuntimeError: panicked at 'can't sleep', library/std/src/sys/wasm/thread.rs:26:9
- https://pyodide.org/en/stable/console.html
time.sleep()
is a dummy function and calling it has no effect
- Brython interactive mode
NotImplementedError: Blocking functions like time.sleep() are not supported in the browser
This ability to implement Suspensions was one of the main reasons Anvil chose Skulpt in the early days.
How can you do a blocking-like call to the server if your client-side Python doesn’t support blocking-like code?
Here’s the perfect 5 minute talk by @mereydydd about client-side Python titled: “Compiling blocking Python to non-blocking JS”
JavaScript in its infinite wisdom is 100% non-blocking
~ Meredydd 2017
Anvil can manage its own event system but it can’t stop the browser from executing JavaScript.
If you click a Button while a server call is going on, the Browser still raises that click event.
Likewise a user is free to hit the browser back button while a server call is going on (think HashRouting).
There’s also a pay-off here between developer experience (DX) and user experience (UX).
If we want to prevent click events, then from a UX perspective, disabling the button might be the best approach. But that’s a bit annoying from a DX perspective.
Just as an experiment, here's an implementation that prevents Anvil event handlers from firing while another Anvil event is being handled. It also implements an API for disabling components that should be disabled during such handling of events.
It adapts the demo from the linked post:
from event_utils import disable_in_event, event_safe
class Form1(Form1Template):
def __init__(self, **properties):
disable_in_event(self.button_0, self.button_1, self.button_2, self.button_3)
@event_safe
def timer_1_tick(self, **event_args):
# don't tick if there's another event_safe event going on
print("tick")
@event_safe
def button_1_click(self, **event_args):
"""This method is called when the button is clicked"""
anvil.server.call("my_long_func")
An event_safe
event handler when invoked will disable components that were called using the disable_in_event
method.
Other event_safe
events will exit early if another event_safe
event is going on.
Caveat to some of the above
If a process in client-side Python is running for a long time and has no blocking code, e.g. a long running while loop that increments a counter, then it may briefly suspend after it has been running for a long period of uninterrupted execution. This prevents infinite loops from locking up the browser.