From 9fc18946939b70d04e90660ab3ef84f924be43fd Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 8 Feb 2023 16:09:56 -0800 Subject: [PATCH 1/3] Hot reload is working on macOS/Linux --- .../packages/flet/src/flet/cli/commands/run.py | 14 ++++++++++---- .../flet/src/flet/sync_local_socket_connection.py | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/sdk/python/packages/flet/src/flet/cli/commands/run.py b/sdk/python/packages/flet/src/flet/cli/commands/run.py index 4c6d52af9..b835de11c 100644 --- a/sdk/python/packages/flet/src/flet/cli/commands/run.py +++ b/sdk/python/packages/flet/src/flet/cli/commands/run.py @@ -1,5 +1,6 @@ import argparse import os +import signal import subprocess import sys import threading @@ -8,7 +9,7 @@ from flet.cli.commands.base import BaseCommand from flet.flet import close_flet_view, open_flet_view -from flet.utils import get_free_tcp_port, open_in_browser +from flet.utils import get_free_tcp_port, is_windows, open_in_browser from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer @@ -187,12 +188,17 @@ def open_flet_view_and_wait(self): self.page_url, self.assets_dir, self.hidden ) self.fvp.wait() - self.p.kill() + if is_windows(): + self.p.kill() + else: + self.p.send_signal(signal.SIGTERM) self.terminate.set() def restart_program(self): self.is_running = False - self.p.kill() + if is_windows(): + self.p.kill() + else: + self.p.send_signal(signal.SIGTERM) self.p.wait() - time.sleep(0.5) self.start_process() diff --git a/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py b/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py index 84a7adce6..34ef0ecc1 100644 --- a/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py +++ b/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py @@ -42,6 +42,7 @@ def connect(self): self.page_url = f"tcp://localhost:{port}" server_address = ("localhost", port) self.__sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) logging.info(f"Starting up TCP server on {server_address}") self.__sock.bind(server_address) else: From 78124c9de67500a49a2b7f78517a90a12ac550fa Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 8 Feb 2023 16:51:23 -0800 Subject: [PATCH 2/3] Fix async local connection hot-reload + use UDS Fix #997 --- .../src/flet/async_local_socket_connection.py | 20 ++++++++++++------- .../flet/src/flet/cli/commands/run.py | 16 +++++++++++++-- sdk/python/packages/flet/src/flet/flet.py | 8 +++++++- .../src/flet/sync_local_socket_connection.py | 12 ++++++----- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/sdk/python/packages/flet/src/flet/async_local_socket_connection.py b/sdk/python/packages/flet/src/flet/async_local_socket_connection.py index 8e721c8a2..92149a3bb 100644 --- a/sdk/python/packages/flet/src/flet/async_local_socket_connection.py +++ b/sdk/python/packages/flet/src/flet/async_local_socket_connection.py @@ -7,7 +7,7 @@ import tempfile import threading from pathlib import Path -from typing import List +from typing import List, Optional from flet.utils import get_free_tcp_port, is_windows from flet_core.local_connection import LocalConnection @@ -27,18 +27,19 @@ class AsyncLocalSocketConnection(LocalConnection): def __init__( self, port: int = 0, + uds_path: Optional[str] = None, on_event=None, on_session_created=None, ): super().__init__() self.__send_queue = asyncio.Queue(1) self.__port = port + self.__uds_path = uds_path self.__on_event = on_event self.__on_session_created = on_session_created async def connect(self): self.__connected = False - self.__uds_path = None if is_windows() or self.__port > 0: # TCP host = "localhost" @@ -49,9 +50,10 @@ async def connect(self): self.__server = asyncio.create_task(server.serve_forever()) else: # UDS - self.__uds_path = str( - Path(tempfile.gettempdir()).joinpath(random_string(10)) - ) + if not self.__uds_path: + self.__uds_path = str( + Path(tempfile.gettempdir()).joinpath(random_string(10)) + ) self.page_url = self.__uds_path logging.info(f"Starting up UDS server on {self.__uds_path}") server = await asyncio.start_unix_server( @@ -65,8 +67,8 @@ async def handle_connection( if not self.__connected: self.__connected = True logging.debug("Connected new TCP client") - asyncio.create_task(self.__receive_loop(reader)) - asyncio.create_task(self.__send_loop(writer)) + self.__receive_loop_task = asyncio.create_task(self.__receive_loop(reader)) + self.__send_loop_task = asyncio.create_task(self.__send_loop(writer)) async def __receive_loop(self, reader: asyncio.StreamReader): while True: @@ -156,6 +158,10 @@ async def __send(self, message: ClientMessage): async def close(self): logging.debug("Closing connection...") # close socket + if self.__receive_loop_task: + self.__receive_loop_task.cancel() + if self.__send_loop_task: + self.__send_loop_task.cancel() if self.__server: self.__server.cancel() diff --git a/sdk/python/packages/flet/src/flet/cli/commands/run.py b/sdk/python/packages/flet/src/flet/cli/commands/run.py index b835de11c..40538c4f8 100644 --- a/sdk/python/packages/flet/src/flet/cli/commands/run.py +++ b/sdk/python/packages/flet/src/flet/cli/commands/run.py @@ -3,6 +3,7 @@ import signal import subprocess import sys +import tempfile import threading import time from pathlib import Path @@ -10,6 +11,7 @@ from flet.cli.commands.base import BaseCommand from flet.flet import close_flet_view, open_flet_view from flet.utils import get_free_tcp_port, is_windows, open_in_browser +from flet_core.utils import random_string from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer @@ -83,9 +85,13 @@ def handle(self, options: argparse.Namespace) -> None: script_dir = os.path.dirname(script_path) port = options.port - if options.port is None: + if port is None and is_windows(): port = get_free_tcp_port() + uds_path = None + if port is None and not is_windows(): + uds_path = str(Path(tempfile.gettempdir()).joinpath(random_string(10))) + assets_dir = options.assets_dir if assets_dir and not Path(assets_dir).is_absolute(): assets_dir = str( @@ -96,6 +102,7 @@ def handle(self, options: argparse.Namespace) -> None: [sys.executable, "-u", script_path], None if options.directory or options.recursive else script_path, port, + uds_path, options.web, options.hidden, assets_dir, @@ -118,11 +125,14 @@ def handle(self, options: argparse.Namespace) -> None: class Handler(FileSystemEventHandler): - def __init__(self, args, script_path, port, web, hidden, assets_dir) -> None: + def __init__( + self, args, script_path, port, uds_path, web, hidden, assets_dir + ) -> None: super().__init__() self.args = args self.script_path = script_path self.port = port + self.uds_path = uds_path self.web = web self.hidden = hidden self.assets_dir = assets_dir @@ -141,6 +151,8 @@ def start_process(self): p_env["FLET_FORCE_WEB_VIEW"] = "true" if self.port is not None: p_env["FLET_SERVER_PORT"] = str(self.port) + if self.uds_path is not None: + p_env["FLET_SERVER_UDS_PATH"] = self.uds_path p_env["FLET_DISPLAY_URL_PREFIX"] = self.page_url_prefix p_env["PYTHONIOENCODING"] = "utf-8" diff --git a/sdk/python/packages/flet/src/flet/flet.py b/sdk/python/packages/flet/src/flet/flet.py index 5bc5d7864..0fb311fc8 100644 --- a/sdk/python/packages/flet/src/flet/flet.py +++ b/sdk/python/packages/flet/src/flet/flet.py @@ -205,7 +205,7 @@ async def app_async( def exit_gracefully(signum, frame): logging.debug("Gracefully terminating Flet app...") - terminate.set() + asyncio.get_running_loop().call_soon_threadsafe(terminate.set) signal.signal(signal.SIGINT, exit_gracefully) signal.signal(signal.SIGTERM, exit_gracefully) @@ -270,6 +270,8 @@ def __connect_internal_sync( if env_port is not None and env_port: port = int(env_port) + uds_path = os.getenv("FLET_SERVER_UDS_PATH") + is_desktop = view == FLET_APP or view == FLET_APP_HIDDEN if server is None and not is_desktop: server = __start_flet_server( @@ -308,6 +310,7 @@ def on_session_created(conn, session_data): if is_desktop: conn = SyncLocalSocketConnection( port, + uds_path, on_event=on_event, on_session_created=on_session_created, ) @@ -342,6 +345,8 @@ async def __connect_internal_async( if env_port is not None and env_port: port = int(env_port) + uds_path = os.getenv("FLET_SERVER_UDS_PATH") + is_desktop = view == FLET_APP or view == FLET_APP_HIDDEN if server is None and not is_desktop: server = __start_flet_server( @@ -382,6 +387,7 @@ async def on_session_created(session_data): if is_desktop: conn = AsyncLocalSocketConnection( port, + uds_path, on_event=on_event, on_session_created=on_session_created, ) diff --git a/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py b/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py index 34ef0ecc1..4f6fec6e0 100644 --- a/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py +++ b/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py @@ -6,7 +6,7 @@ import tempfile import threading from pathlib import Path -from typing import List +from typing import List, Optional from flet.utils import get_free_tcp_port, is_windows from flet_core.local_connection import LocalConnection @@ -26,16 +26,17 @@ class SyncLocalSocketConnection(LocalConnection): def __init__( self, port: int = 0, + uds_path: Optional[str] = None, on_event=None, on_session_created=None, ): super().__init__() self.__port = port + self.__uds_path = uds_path self.__on_event = on_event self.__on_session_created = on_session_created def connect(self): - self.__uds_path = None if is_windows() or self.__port > 0: # TCP port = self.__port if self.__port > 0 else get_free_tcp_port() @@ -47,9 +48,10 @@ def connect(self): self.__sock.bind(server_address) else: # UDS - self.__uds_path = str( - Path(tempfile.gettempdir()).joinpath(random_string(10)) - ) + if not self.__uds_path: + self.__uds_path = str( + Path(tempfile.gettempdir()).joinpath(random_string(10)) + ) self.page_url = self.__uds_path self.__sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) logging.info(f"Starting up UDS server on {self.__uds_path}") From 4f0f85ae2e1a851336448c50e0ea164175411889 Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 8 Feb 2023 19:53:57 -0800 Subject: [PATCH 3/3] Some fixes for Windows --- sdk/python/packages/flet/src/flet/cli/commands/run.py | 10 ++-------- .../flet/src/flet/sync_local_socket_connection.py | 5 ++++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/sdk/python/packages/flet/src/flet/cli/commands/run.py b/sdk/python/packages/flet/src/flet/cli/commands/run.py index 40538c4f8..8f5f24dcf 100644 --- a/sdk/python/packages/flet/src/flet/cli/commands/run.py +++ b/sdk/python/packages/flet/src/flet/cli/commands/run.py @@ -200,17 +200,11 @@ def open_flet_view_and_wait(self): self.page_url, self.assets_dir, self.hidden ) self.fvp.wait() - if is_windows(): - self.p.kill() - else: - self.p.send_signal(signal.SIGTERM) + self.p.send_signal(signal.SIGTERM) self.terminate.set() def restart_program(self): self.is_running = False - if is_windows(): - self.p.kill() - else: - self.p.send_signal(signal.SIGTERM) + self.p.send_signal(signal.SIGTERM) self.p.wait() self.start_process() diff --git a/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py b/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py index 4f6fec6e0..280a44f8f 100644 --- a/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py +++ b/sdk/python/packages/flet/src/flet/sync_local_socket_connection.py @@ -176,7 +176,10 @@ def __recvall(self, sock, n): # Helper function to recv n bytes or return None if EOF is hit data = bytearray() while len(data) < n: - packet = sock.recv(n - len(data)) + try: + packet = sock.recv(n - len(data)) + except: + return None # print("packet received:", len(packet)) if not packet: return None