prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Auto reloader consumes full CPU core
  **Describe the bug**
  
  Enabling the auto reloader (`auto_reload=True` or `--auto-reload`) will consume a full CPU core, because it is looping over all files in all loaded modules and `stat`-ing every file. 100% usage on a single core is observed on macOS and Linux.
  
  The problematic loop is here:
  
  https://github.com/sanic-org/sanic/blob/f891995b487f01ff1207afcd241ae359725a8e3c/sanic/worker/reloader.py#L46-L64
  
  **Code snippet**
  
  Run the following minimal example with `python3 test.py` or `sanic test:app --auto-reload` and watch CPU usage
  
  ```python
  from sanic import Sanic, text
  
  app = Sanic("test")
  
  
  @app.route("/")
  def hello_world(request):
      return text("hello, world")
  
  
  if __name__ == "__main__":
      app.run(auto_reload=True)
  ```
  
  
  **Expected behavior**
  
  The reloader should not consume a full CPU core, but should watch the file system for changes more intelligently, using platform-specific methods (inotify, FSEvents, kqueue, etc). See the [watchdog](https://github.com/gorakhargosh/watchdog/) project for inspiration. Maybe `watchdog` could be included as an optional dependency and used if available?
  
  For instance, [another popular framework has implemented two reloader loops](https://github.com/pallets/werkzeug/blob/2.2.2/src/werkzeug/_reloader.py) and will select the `watchdog` loop if the package is available.
  
  Alternatively, simply add a short sleep step to the reloader loop. I think a one second delay in reloading is acceptable.
  
  **Environment (please complete the following information):**
   - OS: macOS 12.6, Ubuntu Linux 22.04 (likely also Windows, but not tested)
   - Sanic Version: 22.9.0
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `sanic/worker/reloader.py`
  Content:
  ```
  1 from __future__ import annotations
  2 
  3 import os
  4 import sys
  5 
  6 from asyncio import new_event_loop
  7 from itertools import chain
  8 from multiprocessing.connection import Connection
  9 from pathlib import Path
  10 from signal import SIGINT, SIGTERM
  11 from signal import signal as signal_func
  12 from typing import Dict, Set
  13 
  14 from sanic.server.events import trigger_events
  15 from sanic.worker.loader import AppLoader
  16 
  17 
  18 class Reloader:
  19     def __init__(
  20         self,
  21         publisher: Connection,
  22         interval: float,
  23         reload_dirs: Set[Path],
  24         app_loader: AppLoader,
  25     ):
  26         self._publisher = publisher
  27         self.interval = interval
  28         self.reload_dirs = reload_dirs
  29         self.run = True
  30         self.app_loader = app_loader
  31 
  32     def __call__(self) -> None:
  33         app = self.app_loader.load()
  34         signal_func(SIGINT, self.stop)
  35         signal_func(SIGTERM, self.stop)
  36         mtimes: Dict[str, float] = {}
  37 
  38         reloader_start = app.listeners.get("reload_process_start")
  39         reloader_stop = app.listeners.get("reload_process_stop")
  40         before_trigger = app.listeners.get("before_reload_trigger")
  41         after_trigger = app.listeners.get("after_reload_trigger")
  42         loop = new_event_loop()
  43         if reloader_start:
  44             trigger_events(reloader_start, loop, app)
  45 
  46         while self.run:
  47             changed = set()
  48             for filename in self.files():
  49                 try:
  50                     if self.check_file(filename, mtimes):
  51                         path = (
  52                             filename
  53                             if isinstance(filename, str)
  54                             else filename.resolve()
  55                         )
  56                         changed.add(str(path))
  57                 except OSError:
  58                     continue
  59             if changed:
  60                 if before_trigger:
  61                     trigger_events(before_trigger, loop, app)
  62                 self.reload(",".join(changed) if changed else "unknown")
  63                 if after_trigger:
  64                     trigger_events(after_trigger, loop, app)
  65         else:
  66             if reloader_stop:
  67                 trigger_events(reloader_stop, loop, app)
  68 
  69     def stop(self, *_):
  70         self.run = False
  71 
  72     def reload(self, reloaded_files):
  73         message = f"__ALL_PROCESSES__:{reloaded_files}"
  74         self._publisher.send(message)
  75 
  76     def files(self):
  77         return chain(
  78             self.python_files(),
  79             *(d.glob("**/*") for d in self.reload_dirs),
  80         )
  81 
  82     def python_files(self):  # no cov
  83         """This iterates over all relevant Python files.
  84 
  85         It goes through all
  86         loaded files from modules, all files in folders of already loaded
  87         modules as well as all files reachable through a package.
  88         """
  89         # The list call is necessary on Python 3 in case the module
  90         # dictionary modifies during iteration.
  91         for module in list(sys.modules.values()):
  92             if module is None:
  93                 continue
  94             filename = getattr(module, "__file__", None)
  95             if filename:
  96                 old = None
  97                 while not os.path.isfile(filename):
  98                     old = filename
  99                     filename = os.path.dirname(filename)
  100                     if filename == old:
  101                         break
  102                 else:
  103                     if filename[-4:] in (".pyc", ".pyo"):
  104                         filename = filename[:-1]
  105                     yield filename
  106 
  107     @staticmethod
  108     def check_file(filename, mtimes) -> bool:
  109         need_reload = False
  110 
  111         mtime = os.stat(filename).st_mtime
  112         old_time = mtimes.get(filename)
  113         if old_time is None:
  114             mtimes[filename] = mtime
  115         elif mtime > old_time:
  116             mtimes[filename] = mtime
  117             need_reload = True
  118 
  119         return need_reload
  120
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/sanic/worker/reloader.py b/sanic/worker/reloader.py
  --- a/sanic/worker/reloader.py
  +++ b/sanic/worker/reloader.py
  @@ -9,6 +9,7 @@
   from pathlib import Path
   from signal import SIGINT, SIGTERM
   from signal import signal as signal_func
  +from time import sleep
   from typing import Dict, Set
   
   from sanic.server.events import trigger_events
  @@ -62,6 +63,7 @@
                   self.reload(",".join(changed) if changed else "unknown")
                   if after_trigger:
                       trigger_events(after_trigger, loop, app)
  +            sleep(self.interval)
           else:
               if reloader_stop:
                   trigger_events(reloader_stop, loop, app)
  
verification_info: {"golden_diff": "diff --git a/sanic/worker/reloader.py b/sanic/worker/reloader.py\n--- a/sanic/worker/reloader.py\n+++ b/sanic/worker/reloader.py\n@@ -9,6 +9,7 @@\n from pathlib import Path\n from signal import SIGINT, SIGTERM\n from signal import signal as signal_func\n+from time import sleep\n from typing import Dict, Set\n \n from sanic.server.events import trigger_events\n@@ -62,6 +63,7 @@\n                 self.reload(\",\".join(changed) if changed else \"unknown\")\n                 if after_trigger:\n                     trigger_events(after_trigger, loop, app)\n+            sleep(self.interval)\n         else:\n             if reloader_stop:\n                 trigger_events(reloader_stop, loop, app)\n", "issue": "Auto reloader consumes full CPU core\n**Describe the bug**\r\n\r\nEnabling the auto reloader (`auto_reload=True` or `--auto-reload`) will consume a full CPU core, because it is looping over all files in all loaded modules and `stat`-ing every file. 100% usage on a single core is observed on macOS and Linux.\r\n\r\nThe problematic loop is here:\r\n\r\nhttps://github.com/sanic-org/sanic/blob/f891995b487f01ff1207afcd241ae359725a8e3c/sanic/worker/reloader.py#L46-L64\r\n\r\n**Code snippet**\r\n\r\nRun the following minimal example with `python3 test.py` or `sanic test:app --auto-reload` and watch CPU usage\r\n\r\n```python\r\nfrom sanic import Sanic, text\r\n\r\napp = Sanic(\"test\")\r\n\r\n\r\n@app.route(\"/\")\r\ndef hello_world(request):\r\n    return text(\"hello, world\")\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    app.run(auto_reload=True)\r\n```\r\n\r\n\r\n**Expected behavior**\r\n\r\nThe reloader should not consume a full CPU core, but should watch the file system for changes more intelligently, using platform-specific methods (inotify, FSEvents, kqueue, etc). See the [watchdog](https://github.com/gorakhargosh/watchdog/) project for inspiration. Maybe `watchdog` could be included as an optional dependency and used if available?\r\n\r\nFor instance, [another popular framework has implemented two reloader loops](https://github.com/pallets/werkzeug/blob/2.2.2/src/werkzeug/_reloader.py) and will select the `watchdog` loop if the package is available.\r\n\r\nAlternatively, simply add a short sleep step to the reloader loop. I think a one second delay in reloading is acceptable.\r\n\r\n**Environment (please complete the following information):**\r\n - OS: macOS 12.6, Ubuntu Linux 22.04 (likely also Windows, but not tested)\r\n - Sanic Version: 22.9.0\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport os\nimport sys\n\nfrom asyncio import new_event_loop\nfrom itertools import chain\nfrom multiprocessing.connection import Connection\nfrom pathlib import Path\nfrom signal import SIGINT, SIGTERM\nfrom signal import signal as signal_func\nfrom typing import Dict, Set\n\nfrom sanic.server.events import trigger_events\nfrom sanic.worker.loader import AppLoader\n\n\nclass Reloader:\n    def __init__(\n        self,\n        publisher: Connection,\n        interval: float,\n        reload_dirs: Set[Path],\n        app_loader: AppLoader,\n    ):\n        self._publisher = publisher\n        self.interval = interval\n        self.reload_dirs = reload_dirs\n        self.run = True\n        self.app_loader = app_loader\n\n    def __call__(self) -> None:\n        app = self.app_loader.load()\n        signal_func(SIGINT, self.stop)\n        signal_func(SIGTERM, self.stop)\n        mtimes: Dict[str, float] = {}\n\n        reloader_start = app.listeners.get(\"reload_process_start\")\n        reloader_stop = app.listeners.get(\"reload_process_stop\")\n        before_trigger = app.listeners.get(\"before_reload_trigger\")\n        after_trigger = app.listeners.get(\"after_reload_trigger\")\n        loop = new_event_loop()\n        if reloader_start:\n            trigger_events(reloader_start, loop, app)\n\n        while self.run:\n            changed = set()\n            for filename in self.files():\n                try:\n                    if self.check_file(filename, mtimes):\n                        path = (\n                            filename\n                            if isinstance(filename, str)\n                            else filename.resolve()\n                        )\n                        changed.add(str(path))\n                except OSError:\n                    continue\n            if changed:\n                if before_trigger:\n                    trigger_events(before_trigger, loop, app)\n                self.reload(\",\".join(changed) if changed else \"unknown\")\n                if after_trigger:\n                    trigger_events(after_trigger, loop, app)\n        else:\n            if reloader_stop:\n                trigger_events(reloader_stop, loop, app)\n\n    def stop(self, *_):\n        self.run = False\n\n    def reload(self, reloaded_files):\n        message = f\"__ALL_PROCESSES__:{reloaded_files}\"\n        self._publisher.send(message)\n\n    def files(self):\n        return chain(\n            self.python_files(),\n            *(d.glob(\"**/*\") for d in self.reload_dirs),\n        )\n\n    def python_files(self):  # no cov\n        \"\"\"This iterates over all relevant Python files.\n\n        It goes through all\n        loaded files from modules, all files in folders of already loaded\n        modules as well as all files reachable through a package.\n        \"\"\"\n        # The list call is necessary on Python 3 in case the module\n        # dictionary modifies during iteration.\n        for module in list(sys.modules.values()):\n            if module is None:\n                continue\n            filename = getattr(module, \"__file__\", None)\n            if filename:\n                old = None\n                while not os.path.isfile(filename):\n                    old = filename\n                    filename = os.path.dirname(filename)\n                    if filename == old:\n                        break\n                else:\n                    if filename[-4:] in (\".pyc\", \".pyo\"):\n                        filename = filename[:-1]\n                    yield filename\n\n    @staticmethod\n    def check_file(filename, mtimes) -> bool:\n        need_reload = False\n\n        mtime = os.stat(filename).st_mtime\n        old_time = mtimes.get(filename)\n        if old_time is None:\n            mtimes[filename] = mtime\n        elif mtime > old_time:\n            mtimes[filename] = mtime\n            need_reload = True\n\n        return need_reload\n", "path": "sanic/worker/reloader.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport os\nimport sys\n\nfrom asyncio import new_event_loop\nfrom itertools import chain\nfrom multiprocessing.connection import Connection\nfrom pathlib import Path\nfrom signal import SIGINT, SIGTERM\nfrom signal import signal as signal_func\nfrom time import sleep\nfrom typing import Dict, Set\n\nfrom sanic.server.events import trigger_events\nfrom sanic.worker.loader import AppLoader\n\n\nclass Reloader:\n    def __init__(\n        self,\n        publisher: Connection,\n        interval: float,\n        reload_dirs: Set[Path],\n        app_loader: AppLoader,\n    ):\n        self._publisher = publisher\n        self.interval = interval\n        self.reload_dirs = reload_dirs\n        self.run = True\n        self.app_loader = app_loader\n\n    def __call__(self) -> None:\n        app = self.app_loader.load()\n        signal_func(SIGINT, self.stop)\n        signal_func(SIGTERM, self.stop)\n        mtimes: Dict[str, float] = {}\n\n        reloader_start = app.listeners.get(\"reload_process_start\")\n        reloader_stop = app.listeners.get(\"reload_process_stop\")\n        before_trigger = app.listeners.get(\"before_reload_trigger\")\n        after_trigger = app.listeners.get(\"after_reload_trigger\")\n        loop = new_event_loop()\n        if reloader_start:\n            trigger_events(reloader_start, loop, app)\n\n        while self.run:\n            changed = set()\n            for filename in self.files():\n                try:\n                    if self.check_file(filename, mtimes):\n                        path = (\n                            filename\n                            if isinstance(filename, str)\n                            else filename.resolve()\n                        )\n                        changed.add(str(path))\n                except OSError:\n                    continue\n            if changed:\n                if before_trigger:\n                    trigger_events(before_trigger, loop, app)\n                self.reload(\",\".join(changed) if changed else \"unknown\")\n                if after_trigger:\n                    trigger_events(after_trigger, loop, app)\n            sleep(self.interval)\n        else:\n            if reloader_stop:\n                trigger_events(reloader_stop, loop, app)\n\n    def stop(self, *_):\n        self.run = False\n\n    def reload(self, reloaded_files):\n        message = f\"__ALL_PROCESSES__:{reloaded_files}\"\n        self._publisher.send(message)\n\n    def files(self):\n        return chain(\n            self.python_files(),\n            *(d.glob(\"**/*\") for d in self.reload_dirs),\n        )\n\n    def python_files(self):  # no cov\n        \"\"\"This iterates over all relevant Python files.\n\n        It goes through all\n        loaded files from modules, all files in folders of already loaded\n        modules as well as all files reachable through a package.\n        \"\"\"\n        # The list call is necessary on Python 3 in case the module\n        # dictionary modifies during iteration.\n        for module in list(sys.modules.values()):\n            if module is None:\n                continue\n            filename = getattr(module, \"__file__\", None)\n            if filename:\n                old = None\n                while not os.path.isfile(filename):\n                    old = filename\n                    filename = os.path.dirname(filename)\n                    if filename == old:\n                        break\n                else:\n                    if filename[-4:] in (\".pyc\", \".pyo\"):\n                        filename = filename[:-1]\n                    yield filename\n\n    @staticmethod\n    def check_file(filename, mtimes) -> bool:\n        need_reload = False\n\n        mtime = os.stat(filename).st_mtime\n        old_time = mtimes.get(filename)\n        if old_time is None:\n            mtimes[filename] = mtime\n        elif mtime > old_time:\n            mtimes[filename] = mtime\n            need_reload = True\n\n        return need_reload\n", "path": "sanic/worker/reloader.py"}]}