Autoreloading Mechanisms
The autoreloading mechanism in this codebase is designed to provide a seamless development experience by automatically restarting the Django server whenever source code or configuration files are modified. This is achieved through a dual-process architecture where a parent process manages the lifecycle of a child process that runs the actual application.
The Dual-Process Architecture
When the management command runserver is executed with the reloader enabled, it calls autoreload.run_with_reloader(). This function checks for the presence of the RUN_MAIN environment variable (defined as DJANGO_AUTORELOAD_ENV in django/utils/autoreload.py).
- Parent Process: If
RUN_MAINis not set, the process entersrestart_with_reloader(). This function starts a loop that spawns the child process usingsubprocess.run(), settingRUN_MAIN=truein the environment. - Child Process: If
RUN_MAINistrue, the process initializes the reloader and starts the Django application in a separate thread (django-main-thread).
The reloader monitors files and, upon detecting a change, calls sys.exit(3). The parent process catches this specific exit code and restarts the child process, effectively reloading the application.
Reloader Implementations
The system provides two primary reloader implementations, both inheriting from BaseReloader. The choice between them is handled by get_reloader(), which prioritizes the high-performance WatchmanReloader if available, falling back to StatReloader.
StatReloader: The Polling Fallback
StatReloader is the default implementation. It operates by periodically polling the filesystem to check the modification time (mtime) of all watched files.
class StatReloader(BaseReloader):
SLEEP_TIME = 1 # Check for changes once per second.
def tick(self):
mtimes = {}
while True:
for filepath, mtime in self.snapshot_files():
old_time = mtimes.get(filepath)
mtimes[filepath] = mtime
if old_time is None:
continue
elif mtime > old_time:
self.notify_file_changed(filepath)
time.sleep(self.SLEEP_TIME)
yield
While highly compatible across different operating systems, StatReloader can become CPU-intensive in large projects. As the number of files increases, the overhead of calling os.stat() on every file every second grows linearly.
WatchmanReloader: High-Performance Monitoring
For larger projects, WatchmanReloader provides an event-driven alternative using Facebook's Watchman service. Instead of polling, it subscribes to filesystem events, allowing it to scale efficiently regardless of the project size.
It requires the pywatchman library and a running Watchman service (version 4.9 or later). The reloader uses watch-project to monitor roots and subscribe to receive notifications.
class WatchmanReloader(BaseReloader):
def __init__(self):
self.roots = defaultdict(set)
self.processed_request = threading.Event()
self.client_timeout = int(os.environ.get("DJANGO_WATCHMAN_TIMEOUT", 5))
super().__init__()
def tick(self):
request_finished.connect(self.request_processed)
self.update_watches()
while True:
if self.processed_request.is_set():
self.update_watches()
self.processed_request.clear()
try:
self.client.receive()
except pywatchman.SocketTimeout:
pass
# ... process subscriptions ...
yield
time.sleep(0.1)
A notable optimization in WatchmanReloader is its integration with the request_finished signal. It sets an internal event to update its watches only after a request has been processed, ensuring that any modules dynamically loaded during the request are added to the watch list without interrupting the request itself.
Extending the Reloader
The reloader is not limited to Python modules. It provides a signal-based API that allows other components to register additional files or directories for monitoring.
The autoreload_started Signal
When the reloader starts, it sends the autoreload_started signal. Applications can connect to this signal to register non-Python files, such as templates or translation files.
For example, the template system uses this to watch for changes in template directories:
# django/template/autoreload.py
@receiver(autoreload_started, dispatch_uid="template_loaders_watch_changes")
def watch_for_template_changes(sender, **kwargs):
for directory in get_template_directories():
sender.watch_dir(directory, "**/*")
The file_changed Signal
When a change is detected, the reloader sends the file_changed signal before triggering a full restart. Receivers can perform specific actions (like clearing internal caches) and return True to signal that they have handled the change, which prevents the reloader from restarting the entire process.
The translation system utilizes this to clear the translation cache when .mo files change without requiring a full server restart:
# django/utils/translation/reloader.py
def translation_file_changed(sender, file_path, **kwargs):
if file_path.suffix == ".mo":
# ... clear internal translation caches ...
return True
Configuration and Constraints
- Watchman Timeout: The timeout for Watchman queries can be configured via the
DJANGO_WATCHMAN_TIMEOUTenvironment variable (defaulting to 5 seconds). - File Discovery:
BaseReloader.watched_files()automatically includes all loaded Python modules by iterating throughsys.modules, as well as any files manually added viaextra_filesorwatch_dir(). - Error Handling: If a module fails to import due to a syntax error, the reloader tracks these in
_error_filesto ensure the server still watches them and can recover once the error is fixed.