reflex icon indicating copy to clipboard operation
reflex copied to clipboard

[REF-1561] [py312] Reflex server no longer running after hot reload on Windows

Open SergioGal100 opened this issue 2 years ago • 10 comments

I install and download reflex as the tutorial sugested it and give the reflex run command on the terminal, but when i made changes to the initial code the auto refresh load after a while but the local host 3000 can´t be found in the browser.

To Reproduce Steps to reproduce the behavior: Modify the example file:

def index() -> rx.Component:
    return rx.fragment(
        rx.color_mode_button(rx.color_mode_icon(), float="right"),
        rx.vstack(
            rx.heading("Hi my project!", font_size="2em"),
            rx.box("Get started by editing ", rx.code(filename, font_size="1em")),
            rx.link(
                "Check out our docs!",
                href=docs_url,
                border="0.1em solid",
                padding="0.5em",
                border_radius="0.5em",
                _hover={
                    "color": rx.color_mode_cond(
                        light="rgb(107,99,246)",
                        dark="rgb(179, 175, 255)",
                    )
                },
            ),
            spacing="1.5em",
            font_size="2em",
            padding_top="10%",
        ),
    )

Expected behavior if you modify that after runing reflex it should refresh the compiler, it does, but it can´t acces to the local host 3000 after that.

Captura de pantalla 2023-12-24 142021

Specifics (please complete the following information):

  • Python Version: 3.12
  • Reflex Version: 0.3.7
  • OS: Windows
  • Browser (Optional): Edge

From SyncLinear.com | REF-1561

SergioGal100 avatar Dec 24 '23 18:12 SergioGal100

this is the log i get when i run the reflex run --loglevel debug

ERROR:    Traceback (most recent call last):
  File ".\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 664, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\server.py", line 78, in serve
    await self.startup(sockets=sockets)
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\server.py", line 89, in startup
    await self.lifespan.startup()
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\lifespan\on.py", line 54, in startup
    await self.startup_event.wait()
  File ".\Local\Programs\Python\Python312\Lib\asyncio\locks.py", line 212, in wait
    await fut
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File ".\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 123, in run
    raise KeyboardInterrupt()
KeyboardInterrupt

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".\\Python web course\.venv\Lib\site-packages\starlette\routing.py", line 686, in lifespan
    await receive()
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\lifespan\on.py", line 137, in receive
    return await self.receive_queue.get()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".\Local\Programs\Python\Python312\Lib\asyncio\queues.py", line 158, in get
    await getter
asyncio.exceptions.CancelledError

Process SpawnProcess-2:
Traceback (most recent call last):
  File ".\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".\Local\Programs\Python\Python312\Lib\asyncio\base_events.py", line 664, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\server.py", line 78, in serve
    await self.startup(sockets=sockets)
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\server.py", line 89, in startup
    await self.lifespan.startup()
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\lifespan\on.py", line 54, in startup
    await self.startup_event.wait()
  File ".\Local\Programs\Python\Python312\Lib\asyncio\locks.py", line 212, in wait
    await fut
asyncio.exceptions.CancelledError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File ".\Local\Programs\Python\Python312\Lib\multiprocessing\process.py", line 314, in _bootstrap
    self.run()
  File ".\Local\Programs\Python\Python312\Lib\multiprocessing\process.py", line 108, in run
    self._target(*self._args, **self._kwargs)
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\_subprocess.py", line 76, in subprocess_started
    target(sockets=sockets)
  File ".\\Python web course\.venv\Lib\site-packages\uvicorn\server.py", line 61, in run
    return asyncio.run(self.serve(sockets=sockets))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 194, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File ".\Local\Programs\Python\Python312\Lib\asyncio\runners.py", line 123, in run
    raise KeyboardInterrupt()
KeyboardInterrupt

SergioGal100 avatar Dec 24 '23 18:12 SergioGal100

i'm able to reproduce this issue with python 3.12 on windows 11;

it seems like the hot reload is getting stuck in npm trying to install/check packages and hanging

masenf avatar Jan 02 '24 23:01 masenf

Confirm, this issue is affecting python 3.12 going back to at least 0.3.5;

Python 3.11 does not seem to exhibit this problem. As a workaround, use python 3.11 on windows.

masenf avatar Jan 03 '24 01:01 masenf

Hi, yes, I recently realized that the hot reload feature of Uvicorn is encountering an error. It appears that the files aren't reloading and are getting lost. I'll try using an older version of Python. I'm experiencing issues with Uvicorn in this new Python version; it reloads automatically and not when I save the main file.

SergioGal100 avatar Jan 09 '24 12:01 SergioGal100

I have the same issue with Reflex 0.4.3 on Windows 10 (no WSL) with Python 3.12. Using Python 3.11 indeed fixes the problem, but would still be nice to have Reflex working on the most recent version of Python.

LouisDeconinck avatar Mar 10 '24 08:03 LouisDeconinck

The reflex team would love some help on this issue. I think the problem lies in our upstream dependency, uvicorn. Uvicorn hot reload by itself seems to work in windows with py3.12, but it does not work with Reflex 😢. Some investigation and debugging will be needed, and the team doesn't have the bandwidth to take it on at the moment.

masenf avatar Mar 13 '24 21:03 masenf

Hey I'm new to the repo but I'd love to help with this.

As far as I can tell the issue comes from the uvicorn change (https://github.com/encode/uvicorn/pull/1584/). As part of their reloading process where they shut down the server and start it again, they shifted from using multiprocessing.Process.terminate() to sending a signal.CTRL_C_EVENT using os.kill(). It seems like this Ctrl_C_Event is somehow reaching the npm frontend and causing it to stop.

I'll investigate this further and update

sid-38 avatar Mar 25 '24 02:03 sid-38

Update: Dived a little deeper and figured out that the issue boils down to a bug in Microsoft's GenerateConsoleCtrlEvent function. The links regarding this bug are the following:

  1. https://bugs.python.org/issue42962
  2. https://github.com/microsoft/terminal/issues/335

Summarizing from the links, os.kill(pgid, signal.CTRL_C_EVENT) is supposed to be called on process group ids and if instead it is called on pids (which is what uvicorn does, I think) it leads to a not well documented behavior where Ctrl + C is wrongfully called on all the processes that are part of the console, including the npm frontend shell.

So, unless uvicorn makes a fix at their end it'll be hard to avoid Ctrl+C from reaching the npm frontend. We could pin uvicorn to v0.21.1 in reflex which should fix the hot reload issue. And in parallel, I'll try and make a simpler reproducible version of this issue to submit in the uvicorn repo and try and fix it.

Or we could somehow try to capture the Ctrl+C event at the frontend and ignore it when it is initiated by the hot reload process.

sid-38 avatar Mar 25 '24 06:03 sid-38

@sid-38 Great investigation thanks for finding this. I'd be open to pinning uvicorn - my only concern would be if this strict requirement may have conflicts with other packages that people use with Reflex.

picklelo avatar Mar 26 '24 22:03 picklelo

So I found an alternative that would let us use the latest uvicorn. When we are creating the python subprocess for npm, we could add the creation flag - CREATE_NEW_PROCESS_GROUP. This would make the npm frontend part of a new process group which means that even if the uvicorn bug tries to Ctrl+C everything it will not reach the frontend. Internally, when python creates a sub process with the CREATE_NEW_PROCESS_GROUP flag, it'll turn off the Ctrl+C handler in the new process which is why uvicorn won't be able to accidentally kill frontend.

This means, hot reload will no longer cause frontend to stop. However this also means that when we actually want to quit reflex, and we intentionally do Ctrl+C on the console, that will not reach the frontend either (coz frontend no longer responds to Ctrl+C). Uvicorn will see the Ctrl+C and stop, but then reflex main thread would just wait for frontend to finish which it never does. A workaround to this would be to add the code in reflex to kill frontend, once uvicorn exits. I've tested this out and it does work.

Now, to kill the frontend, reflex cannot use a CTRL_C_EVENT for the reasons mentioned above. I'm trying to see if there is a way to enable the Ctrl C handler for frontend once uvicorn ends but that leads to using ctypes.kernel and it gets complicated. Reflex could send a SIGTERM or a CTRL_BREAK _EVENT to frontend both of which causes frontend to quit. But it prints out an error log for the frontend.

Screenshot 2024-03-27 121224

So in short, there is a workaround that makes everything work with latest uvicorn but it involves sending SIGTERM or CTRL+BREAK to frontend both of which output error messages to the terminal. We can either let the error messages be, or in reflex.processes.stream_logs we can filter out the SIGTERM error code to not print out the entire error log.

sid-38 avatar Mar 27 '24 17:03 sid-38

Fixed in 0.4.7

masenf avatar Apr 26 '24 21:04 masenf