Ver código fonte

Replace repeated polling in Outbox.loop() with an asyncio event (see #2482) (#2867)

* Add asyncio events to outbox

* Moved event clearing as a minor optimization

* clean up timeout argument

* Change timeout duration

* Handle connection timeout

* Add log import

* remove extraneous whitespace

* code review and improvement

* Add asyncio.TimeoutError for Python < 3.11

* Fix test hangs

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Aaron Fuller 1 ano atrás
pai
commit
a2544c1278
3 arquivos alterados com 24 adições e 6 exclusões
  1. 20 4
      nicegui/outbox.py
  2. 3 2
      tests/test_input.py
  3. 1 0
      tests/test_javascript.py

+ 20 - 4
nicegui/outbox.py

@@ -23,35 +23,51 @@ class Outbox:
         self.updates: Dict[ElementId, Optional[Element]] = {}
         self.messages: Deque[Message] = deque()
         self._should_stop = False
+        self._enqueue_event: Optional[asyncio.Event] = None
         if core.app.is_started:
             background_tasks.create(self.loop(), name=f'outbox loop {client.id}')
         else:
             core.app.on_startup(self.loop)
 
+    def _set_enqueue_event(self) -> None:
+        """Set the enqueue event while accounting for lazy initialization."""
+        if self._enqueue_event:
+            self._enqueue_event.set()
+
     def enqueue_update(self, element: Element) -> None:
         """Enqueue an update for the given element."""
         self.updates[element.id] = element
+        self._set_enqueue_event()
 
     def enqueue_delete(self, element: Element) -> None:
         """Enqueue a deletion for the given element."""
         self.updates[element.id] = None
+        self._set_enqueue_event()
 
     def enqueue_message(self, message_type: MessageType, data: Any, target_id: ClientId) -> None:
         """Enqueue a message for the given client."""
         self.messages.append((target_id, message_type, data))
+        self._set_enqueue_event()
 
     async def loop(self) -> None:
         """Send updates and messages to all clients in an endless loop."""
+        self._enqueue_event = asyncio.Event()
+        self._enqueue_event.set()
+
         while not self._should_stop:
             try:
-                await asyncio.sleep(0.01)
-
-                if not self.updates and not self.messages:
-                    continue
+                if not self._enqueue_event.is_set():
+                    try:
+                        await asyncio.wait_for(self._enqueue_event.wait(), timeout=1.0)
+                    except (TimeoutError, asyncio.TimeoutError):
+                        continue
 
                 if not self.client.has_socket_connection:
+                    await asyncio.sleep(0.1)
                     continue
 
+                self._enqueue_event.clear()
+
                 coros = []
                 data = {
                     element_id: None if element is None else element._to_dict()  # pylint: disable=protected-access

+ 3 - 2
tests/test_input.py

@@ -122,11 +122,12 @@ def test_autocompletion(screen: Screen):
     assert element.get_attribute('value') == 'fx'
     assert input_.value == 'fx'
 
-    input_.set_autocomplete(['one', 'two'])
+    input_.set_autocomplete(['once', 'twice'])
+    screen.wait(0.2)
     element.send_keys(Keys.BACKSPACE)
     element.send_keys(Keys.BACKSPACE)
     element.send_keys('o')
-    screen.should_contain('ne')
+    screen.should_contain('nce')
 
 
 def test_clearable_input(screen: Screen):

+ 1 - 0
tests/test_javascript.py

@@ -40,6 +40,7 @@ def test_run_javascript_before_client_connected(screen: Screen):
     screen.open('/')
     screen.should_contain('before js')
     screen.should_contain('after js')
+    screen.wait(0.5)
     screen.should_contain('New Title')