diff --git a/CHANGELOG.md b/CHANGELOG.md index 060a9cfc..5127e666 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,19 @@ Using the following categories, list your changes in this order: - Nothing (yet)! +## [3.3.2] - 2023-08-13 + +### Added + +- ReactPy Websocket will now decode messages via `orjson` resulting in an ~6% overall performance improvement. +- Built-in `asyncio` event loops are now patched via `nest_asyncio`, resulting in an ~10% overall performance improvement. This has no performance impact if you are running your webserver with `uvloop`. + +### Fixed + +- Fix bug where `REACTPY_WEBSOCKET_URL` always generates a warning if unset. +- Fixed bug on Windows where `assert f is self._write_fut` would be raised by `uvicorn` when `REACTPY_BACKHAUL_THREAD = True`. +- Fixed bug on Windows where rendering behavior would be jittery with `daphne` when `REACTPY_BACKHAUL_THREAD = True`. + ## [3.3.1] - 2023-08-08 ### Added diff --git a/docs/python/settings.py b/docs/python/settings.py index d5cfc4ae..c9a26f5a 100644 --- a/docs/python/settings.py +++ b/docs/python/settings.py @@ -27,6 +27,6 @@ # 3. Your Django user model does not define a `backend` attribute REACTPY_AUTH_BACKEND = "django.contrib.auth.backends.ModelBackend" -# Whether to enable rendering ReactPy via a dedicated backhaul thread -# This allows the webserver to process traffic while during ReactPy rendering +# Whether to enable rendering ReactPy via a dedicated backhaul thread. +# This allows the webserver to process traffic while during ReactPy rendering. REACTPY_BACKHAUL_THREAD = False diff --git a/requirements/pkg-deps.txt b/requirements/pkg-deps.txt index 75758695..c5958398 100644 --- a/requirements/pkg-deps.txt +++ b/requirements/pkg-deps.txt @@ -3,4 +3,6 @@ django >=4.1.0 reactpy >=1.0.0, <1.1.0 aiofile >=3.0 dill >=0.3.5 +orjson >=3.6.0 +nest_asyncio >=1.5.0 typing_extensions diff --git a/src/reactpy_django/__init__.py b/src/reactpy_django/__init__.py index de84506e..44e04bc1 100644 --- a/src/reactpy_django/__init__.py +++ b/src/reactpy_django/__init__.py @@ -1,3 +1,7 @@ +import contextlib + +import nest_asyncio + from reactpy_django import checks, components, decorators, hooks, types, utils from reactpy_django.websocket.paths import REACTPY_WEBSOCKET_PATH @@ -11,3 +15,9 @@ "utils", "checks", ] +# Built-in asyncio event loops can create `assert f is self._write_fut` exceptions +# while we are using our backhaul thread with Uvicorn, so we use this patch to fix this. +# This also resolves jittery rendering behaviors within Daphne. Can be demonstrated +# using our "Renders Per Second" test page. +with contextlib.suppress(ValueError): + nest_asyncio.apply() diff --git a/src/reactpy_django/checks.py b/src/reactpy_django/checks.py index b60be689..fc5a89a6 100644 --- a/src/reactpy_django/checks.py +++ b/src/reactpy_django/checks.py @@ -97,7 +97,7 @@ def reactpy_warnings(app_configs, **kwargs): ) # Check if REACTPY_WEBSOCKET_URL doesn't end with a slash - REACTPY_WEBSOCKET_URL = getattr(settings, "REACTPY_WEBSOCKET_URL", "") + REACTPY_WEBSOCKET_URL = getattr(settings, "REACTPY_WEBSOCKET_URL", "reactpy/") if isinstance(REACTPY_WEBSOCKET_URL, str): if not REACTPY_WEBSOCKET_URL or not REACTPY_WEBSOCKET_URL.endswith("/"): warnings.append( diff --git a/src/reactpy_django/websocket/consumer.py b/src/reactpy_django/websocket/consumer.py index 279d0f0d..579f351b 100644 --- a/src/reactpy_django/websocket/consumer.py +++ b/src/reactpy_django/websocket/consumer.py @@ -11,6 +11,7 @@ from typing import Any, MutableMapping, Sequence import dill as pickle +import orjson from channels.auth import login from channels.db import database_sync_to_async from channels.generic.websocket import AsyncJsonWebsocketConsumer @@ -46,7 +47,7 @@ async def connect(self) -> None: await super().connect() # Authenticate the user, if possible - user: Any = self.scope.get("user") + user = self.scope.get("user") if user and user.is_authenticated: try: await login(self.scope, user, backend=REACTPY_AUTH_BACKEND) @@ -105,6 +106,14 @@ async def receive_json(self, content: Any, **_) -> None: else: await self.recv_queue.put(content) + @classmethod + async def decode_json(cls, text_data): + return orjson.loads(text_data) + + @classmethod + async def encode_json(cls, content): + return orjson.dumps(content).decode() + async def run_dispatcher(self): """Runs the main loop that performs component rendering tasks.""" from reactpy_django import models diff --git a/tests/test_app/templates/events_renders_per_second.html b/tests/test_app/templates/events_renders_per_second.html index e2c779cb..cb1ed5c8 100644 --- a/tests/test_app/templates/events_renders_per_second.html +++ b/tests/test_app/templates/events_renders_per_second.html @@ -17,6 +17,7 @@
Total Active Components:
Time To Load:
Event Renders Per Second:
+Event Renders Per Second (Estimated Minimum):
Average Round-Trip Time:
@@ -50,6 +51,16 @@Total Active Components:
Time To Load:
Total Renders Per Second:
+Total Renders Per Second (Estimated Minimum):