lona
lona copied to clipboard
Frontend-/Backend synchronisation error?
While working on a non-public lona application I discovered a possible bug in the backend frontend synchronization. I have tried to break it down into a somewhat minimal reproducer. I'll show my reproducer and the effects I have found while creating the reproducer. Turns out: There are cases where this code (with some minimal changes) works as expected.
Here is my reproducer as a Lona Script:
from lona.html import HTML, Div, Button, TextArea, Widget
from lona import LonaApp, LonaView
class Modal(Widget):
def __init__(self, title='', body=''):
self.title = Div(title)
self.body = Div(body)
self.buttons = []
self.model_footer = Div()
self.nodes = [self.body, self.model_footer]
self.hide()
def set_body(self, *nodes):
self.body.nodes = [*nodes]
def set_buttons(self, *nodes):
self.buttons = [*nodes]
self.model_footer.nodes = [*nodes]
def show(self):
self.nodes[0].style['display'] = 'block'
def hide(self):
self.nodes[0].style['display'] = 'none'
app = LonaApp(__file__)
@app.route('/')
class MyView(LonaView):
def handle_request(self, request):
self.comment = TextArea(placeholder='Editor Comment', bubble_up=True)
self.modal = Modal()
self.modal.set_body(self.comment)
# If we create the button here everything works as expected
#self.modal.set_buttons(
# pb := Button('Save', disabled=True),
#)
html = HTML(
Button('Save', _id='show-save-modal'),
self.modal,
)
while True:
input_event = self.await_click(html=html)
# creating the button here triggers the error
self.modal.set_buttons(
pb := Button('Save', disabled=True),
)
self.modal.show()
while True:
input_event = self.await_input_event(html=html)
comment = self.comment.value
if input_event.node == self.comment:
# changes of setting pb.disabled are not propageted
# to the frontend correctly
if comment.strip():
pb.disabled = False
else:
pb.disabled = True
# setting a text here raises the exceptions
pb.set_text("Huba")
app.run(port=8080)
Steps to reproduce:
- Run the server
python3 demo.py - Got to
http://localhost:8080/ - Click
Save. The Widget with the TextArea and a new "Save"-button is displayed as expected. - Enter some text. I would assume that the new "Save"-Button gets enabled. But this does not happen.
- Delete the text in the TextArea. Now I get the following exception:
======== Running on http://localhost:8080 ========
(Press CTRL+C to quit)
MainThread INFO 08:31:02.541475 aiohttp.access 127.0.0.1 [25/Aug/2022:06:31:02 +0000] "GET / HTTP/1.1" 200 873 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0"
MainThread INFO 08:31:02.616687 aiohttp.access 127.0.0.1 [25/Aug/2022:06:31:02 +0000] "GET /static/lona/style.css HTTP/1.1" 200 231 "http://localhost:8080/" "Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0"
MainThread INFO 08:31:02.617115 aiohttp.access 127.0.0.1 [25/Aug/2022:06:31:02 +0000] "GET /static/lona/lona.js HTTP/1.1" 200 250 "http://localhost:8080/" "Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0"
LonaWorker_0 ERROR 08:31:08.892838 lona.view_runtime client error raised:
Lona.LonaDomUpdater/this._insert_node@http://localhost:8080/static/lona/lona.js:417:16
Lona.LonaDomUpdater/this._apply_patch_to_child_nodes@http://localhost:8080/static/lona/lona.js:820:18
Lona.LonaDomUpdater/this._apply_patch@http://localhost:8080/static/lona/lona.js:843:18
Lona.LonaWindow/this._show_html/</<@http://localhost:8080/static/lona/lona.js:1577:44
Lona.LonaWindow/this._show_html/<@http://localhost:8080/static/lona/lona.js:1570:22
Lona.JobQueue/this.add@http://localhost:8080/static/lona/lona.js:70:27
LonaRuntimeWorker_0 ERROR 08:31:08.893554 lona.view_runtime Exception raised while running <__main__.MyView object at 0x7ff17ec78df0>
Traceback (most recent call last):
File "/home/chris/work/Projects/intern/lag-intranet/env/lib/python3.9/site-packages/lona/view_runtime.py", line 318, in start
raw_response_dict = self.view.handle_request(self.request) or ''
File "/home/chris/work/Projects/intern/lag-intranet/demo.py", line 57, in handle_request
input_event = self.await_input_event(html=html)
File "/home/chris/work/Projects/intern/lag-intranet/env/lib/python3.9/site-packages/lona/view.py", line 220, in await_input_event
return self._await_specific_input_event(
File "/home/chris/work/Projects/intern/lag-intranet/env/lib/python3.9/site-packages/lona/view.py", line 203, in _await_specific_input_event
return self._view_runtime.await_input_event(
File "/home/chris/work/Projects/intern/lag-intranet/env/lib/python3.9/site-packages/lona/view_runtime.py", line 646, in await_input_event
return self.server.run_coroutine_sync(_await_input_event())
File "/home/chris/work/Projects/intern/lag-intranet/env/lib/python3.9/site-packages/lona/server.py", line 245, in run_coroutine_sync
return future.result()
File "/usr/lib/python3.9/concurrent/futures/_base.py", line 440, in result
return self.__get_result()
File "/usr/lib/python3.9/concurrent/futures/_base.py", line 389, in __get_result
raise self._exception
File "/home/chris/work/Projects/intern/lag-intranet/env/lib/python3.9/site-packages/lona/view_runtime.py", line 644, in _await_input_event
return await future
lona.errors.ClientError
Without pb.set_text("Huba") the pb.disabled state is simply not reflected into the frontend.
When I declare the button before the first call to self.await_click() (see comment in reproducer) it works as expected.
Tested on lona==1.10.3.
From a user's perspective I would assume that I am not able to break the frontend backend synchronization. I have had a look into that code but could not find anything obvious.
Hi @SmithChart,
thanks for reporting! This is a bug in the JavaScript rendering engine, and it seems to be completely client side. It is reproducible with this even smaller example:
from lona import LonaApp, LonaView
from lona.html import Div
app = LonaApp(__file__)
@app.route('/')
class MyView(LonaView):
def handle_request(self, request):
html = Div(
Div(),
)
self.show(html)
html.nodes = [
Div('div'),
]
self.show()
html.nodes[0].set_text('foo')
self.show()
print(html)
app.run(port=8080)
I debugged a little bit and think i understand the underlying issue: Lona renders a new node, with the node-id '3' in this case, and adds it to the dom. This add operation is an RESET in this case. That's important because in case of RESET, the node cache gets cleared. That means the newly created node gets added to the dom without being accessible to the update mechanism. When a patch for the orphaned node comes in, the node id lookup fails and the client crashes.
I think it should not be that hard to fix, and i will start working on it tomorrow.
Thanks for looking into that!
@SmithChart: I created a PR to resolve this issues