From 75c5ced013c72a066be754074ee1d1471b8795b9 Mon Sep 17 00:00:00 2001 From: Akash Verma Date: Wed, 30 Oct 2024 14:00:49 -0700 Subject: [PATCH] Upgrade Tornado to v6, and fix servers accordingly. --- openhtf/output/servers/station_server.py | 2 + openhtf/output/servers/web_gui_server.py | 51 ++++++++++++++++-------- openhtf/output/web_gui/README.md | 15 ++++++- pyproject.toml | 4 +- 4 files changed, 52 insertions(+), 20 deletions(-) diff --git a/openhtf/output/servers/station_server.py b/openhtf/output/servers/station_server.py index 756f0bf39..29d975c9b 100644 --- a/openhtf/output/servers/station_server.py +++ b/openhtf/output/servers/station_server.py @@ -19,6 +19,7 @@ aggregate info from multiple station servers with a single frontend. """ +import asyncio import contextlib import itertools import json @@ -166,6 +167,7 @@ def __init__(self, update_callback): def run(self): """Call self._poll_for_update() in a loop and handle errors.""" + asyncio.set_event_loop(asyncio.new_event_loop()) while True: try: self._poll_for_update() diff --git a/openhtf/output/servers/web_gui_server.py b/openhtf/output/servers/web_gui_server.py index a066cdc5e..02346f6ce 100644 --- a/openhtf/output/servers/web_gui_server.py +++ b/openhtf/output/servers/web_gui_server.py @@ -13,9 +13,9 @@ # limitations under the License. """Extensible HTTP server serving the OpenHTF Angular frontend.""" +import asyncio import os import threading -import time import tornado.httpclient import tornado.httpserver @@ -112,6 +112,8 @@ class WebGuiServer(threading.Thread): def __init__(self, additional_routes, port, sockets=None): super(WebGuiServer, self).__init__(name=type(self).__name__) + self.ts_event = threading.Event() + self._running = asyncio.Event() # Set up routes. routes = [ @@ -121,24 +123,17 @@ def __init__(self, additional_routes, port, sockets=None): }), ] routes.extend(additional_routes) - - if sockets is None: - sockets, self.port = bind_port(port) - else: - if not port: - raise ValueError('When sockets are passed to the server, port must be ' - 'specified and nonzero.') - self.port = port + self._sockets = sockets + self.port = port + self._loop = None # Configure the Tornado application. - application = tornado.web.Application( + self.application = tornado.web.Application( routes, default_handler_class=DefaultHandler, template_loader=TemplateLoader(STATIC_FILES_ROOT), static_path=STATIC_FILES_ROOT, ) - self.server = tornado.httpserver.HTTPServer(application) - self.server.add_sockets(sockets) def __enter__(self): self.start() @@ -147,14 +142,38 @@ def __enter__(self): def __exit__(self, *unused_args): self.stop() + async def run_app(self): + """Runs the station server application.""" + self.ts_watchdog_task = asyncio.create_task(self._stop_threadsafe()) + if self._sockets is None: + self._sockets, self.port = bind_port(self.port) + else: + if not self.port: + raise ValueError( + 'When sockets are passed to the server, port must be ' + 'specified and nonzero.' + ) + self.server = tornado.httpserver.HTTPServer(self.application) + self.server.add_sockets(self._sockets) + await self._running.wait() + await self.ts_watchdog_task + await self.server.close_all_connections() + + async def _stop_threadsafe(self): + """Handles stopping the server in a threadsafe manner.""" + while not self.ts_event.is_set(): + await asyncio.sleep(0.1) + self._running.set() + def _get_config(self): """Override this to configure the Angular app.""" return {} def run(self): - tornado.ioloop.IOLoop.instance().start() # Blocking IO loop. + """Runs the station server.""" + asyncio.run(self.run_app()) def stop(self): - self.server.stop() - ioloop = tornado.ioloop.IOLoop.instance() - ioloop.add_timeout(time.time() + _SERVER_SHUTDOWN_BUFFER_S, ioloop.stop) + """Stops the station server. Method is threadsafe.""" + self.ts_event.set() + diff --git a/openhtf/output/web_gui/README.md b/openhtf/output/web_gui/README.md index 813b4f28e..b30bd207a 100644 --- a/openhtf/output/web_gui/README.md +++ b/openhtf/output/web_gui/README.md @@ -1,8 +1,13 @@ # OpenHTF web GUI client +## Prerequisites + +Follow the steps from [CONTRIBUTING.md](../../../CONTRIBUTING.md) to set up +the Python virtual environment. Make sure you have done an "editable" install. + ## Development -First, start the dashboard server on the default port by running the following +Start the dashboard server on the default port by running the following from the project root directory: ``` @@ -22,4 +27,10 @@ the dashboard server and should reload automatically when changes are made to frontend files. With the dashboard running, you can run OpenHTF tests separately, and they -should be picked up over multicast and appear on the dashboard. +should be picked up over multicast and appear on the dashboard. To test things +out, you can run the frontend example test: + +``` +python3 examples/frontend_example.py +``` + diff --git a/pyproject.toml b/pyproject.toml index 74eef29fc..4a70f4544 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "openhtf" -version = "1.6.0" +version = "1.6.1" authors = [ { name="The OpenHTF Authors"}, ] @@ -23,7 +23,7 @@ dependencies = [ "pyOpenSSL>=17.1.0", "requests>=2.27.1", "sockjs_tornado>=1.0.7", - "tornado>=4.3,<5.0", + "tornado>=6,<6.3", "typing_extensions>=4.12.2", ] license = {file = "LICENSE"}