What is the recommended way to terminate an external UpLink script?
I’m working on an external script connected to my anvil-app-server using anvil.server.connect(). I often need to stop and restart my script to test code changes as I am working. I hoped I could define a server.callable function to call anvil.server.disconnect() to terminate the loop then quit(). Unfortunately, as soon as I disconnect and before I quit() I get a storm of exceptions.
Anvil websocket open
Connected to "App Server" as SERVER
Anvil websocket closed (code 1000, reason=b'')
Exception in thread Thread-3:
Traceback (most recent call last):
File "C:\Users\Roy\.conda\envs\Anvil\lib\site-packages\anvil\_threaded_server.py", line 265, in make_call
send_reqresp(resp)
File "C:\Users\Roy\.conda\envs\Anvil\lib\site-packages\anvil\server.py", line 53, in <lambda>
_threaded_server.send_reqresp = lambda r, collect_capabilities=None: _get_connection().send_reqresp(r, collect_capabilities=collect_capabilities)
File "C:\Users\Roy\.conda\envs\Anvil\lib\site-packages\anvil\server.py", line 324, in _get_connection
raise Exception("You must use anvil.server.connect(key) before anvil.server.call()")
Exception: You must use anvil.server.connect(key) before anvil.server.call()
I think you should just be able to use quit() or exit() without anything bad happening.
When you are calling disconnect from inside a function wrapped in the callable decorator, I’m sure it’s trying to return some kind of information to anvil when the decorated function completes, however you are already disconnected, causing the exceptions.
I have that sense I sometimes get that if I understood something seemingly tiny I would understand a lot.
How could I ever call disconnect() except inside a callable decorated function?
Also calling quit() in the callable function results in the same/similar storm of exceptions. I’ll make the effort to reproduce it if anyone thinks this is something more than me just being wrong-headed about how to stop my script.
Ok, the last “correct” way I can think of, I did not suggest before because it is overly complicated.
The connection runs as async, if you know how to program async code, create a different async function that contains a small delay timer (like a second or so), then calls disconnect, then your exit(). You could then launch this task from inside the callable function.
This would allow the callable function to return it’s information to anvil, then after some delay, disconnect and exit the program.
However, I’m still not sure if this is even a problem (the way you are doing it now).
I have quite a few uplink scripts running, and the errors you see might (I haven’t checked) be caught by the uplink code and are just being sent to stdout (writing to the console), and not raising the exception to the point where your program is exiting with a status other than 0.
Aka, your computer that runs the script might not even be interpreting this as a “crash”.
Thank you for that. I have dabbled—no more—with async so I’m not going to implement that solution without a lot more reading and fumbling than my problem justifies. I can just make a note the PID when I start the script and kill it with Task Manager, like I’ve been doing.
The fact that anvil.server.disconnect() is documented as if you can just call it, without fancy antics (out of character with Anvil), still nags at me.
For completeness, here’s what I hoped would have worked:
import anvil.server
@anvil.server.callable
def test_uplink():
return 'greetings from UplinkDemo'
@anvil.server.callable
def stop_uplinkdemo():
anvil.server.disconnect()
UPLINK_KEY = '39dbca46-d30d-ea4b-a962-0b876b888a1f'
UPLINK_URL = 'ws://localhost:3030/_/uplink'
anvil.server.connect(UPLINK_KEY,url=UPLINK_URL)
anvil.server.wait_forever()
# ?this won't be reached until anvil.server.disconnect() executes?
quit()
Sure, I understand, I think the disconnect() feature only exists because we (the forum people) asked for it, so we could use the same script to connect, disconnect and then reconnect to anvil at will without using wait_forever(), or even to connect to a completely different apps uplink key without having to restart the script.
On the inside, disconnect just takes an object that normally keeps the connection alive (and is not documented or meant to be set by the user directly) and set’s that object to None so it no longer stays connected.
Also, you put an uplink key in your last post, you might want to reset the key in your app and script now.
Yes, I was surprised to see that show in clear when I hit ctrl-V; I thought I’d redacted it. But if anyone can get to where knowing it would cause me a problem I’ve got way bigger problems!
Your background on the origin of disconnect() and that it isn’t expected to be used with wait_forever() is the sort tiny thing I was thinking might reveal to me a big thing…
Ok, so I built something that will exit the way you want cleanly.
In case anyone is scared about calling the private method _get_connection(), the anvil.server.wait_forever() literally just looks like this: (from server.py in anvil-uplink)
Anyway, this will allow you to exit your code clean:
from time import sleep
from contextlib import contextmanager
@contextmanager
def open_custom_anvil_connection(uplink_key):
anvil.server.connect(uplink_key)
# print("Connected to Anvil")
yield
anvil.server.disconnect()
# print("Disconnected from Anvil")
print("")
@anvil.server.callable
def remote_disconnect():
global anvil_connection_keepalive
anvil_connection_keepalive = False
def custom_wait_forever():
global anvil_connection_keepalive
anvil_connection_keepalive = True
anvil.server._get_connection()
while anvil_connection_keepalive:
sleep(1)
with open_custom_anvil_connection(YOUR-UPLINK-KEY-HERE):
custom_wait_forever()
exit()
At the risk of keeping this topic alive beyond its “sell by” date…
Now I have a clearer idea of what is going on I have re-read the documentation with more care . I can report there is no need to call _get_connection() explicitly. Calling it does no harm, but the name hints we shouldn’t want to.
I agree, I’m going to start a feature request. Anvil has a pattern where they accept a function as a parameter for many things, it would be nice if the next release of anvil-uplink contained an optional parameter where you could provide a function that would return true or false after checking whatever you wanted during the while loop contained in wait_forever()
Example use:
def check_if_i_want_to_stop():
# Take the weekend off and return False if it isn't a weekday.
return datetime.now().weekday() < 5
anvil.server.wait_forever(check_if_i_want_to_stop)
… and then it would run check_if_i_want_to_stop() every 1 seconds in the while loop from within the wait_forever() function, the same way I did in the code above.