Changes between Version 1 and Version 2 of Ticket #36653


Ignore:
Timestamp:
Oct 9, 2025, 4:11:51 PM (19 hours ago)
Author:
Brian Helba
Comment:

Legend:

Unmodified
Added
Removed
Modified
  • Ticket #36653 – Description

    v1 v2  
    2323I believe that I understand the exact cause of the bug. Note, my use of some lifecycle events and specific thread names may be limited to the runserver case, but the exact same behaviors manifest with WSGI (and I believe that equivalent things are occurring with a multiprocess lifecycle).
    2424
    25 1. `django.setup()` is [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/core/management/__init__.py#L395 called very early by runserver]
    26 2. `django.setup()` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/__init__.py#L20-L23 calls `set_script_prefix`]
     251. `django.setup()` is [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/core/management/__init__.py#L395 called very early] by runserver
     262. `django.setup()` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/__init__.py#L20-L23 calls] `set_script_prefix`
    27273. `set_script_prefix` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/urls/base.py#L120C5-L126C20 correctly sets] `django.urls.base._prefixes.value`, but only for the current thread (since `_prefixes` is a thread-local object)
    28284. a new thread, `django-main-thread`, [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/utils/autoreload.py#L666-L670 is spawned]; again, I believe (and can locate if necessary) that an equivalent event also happens in a WSGI lifecycle
    29 5. `check_url_settings` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/core/checks/urls.py#L108-L109 runs and accesses `settings.STATIC_URL`]; importantly, this is the first time in the startup lifecycle that `settings.STATIC_URL` has ever been accessed
    30 6. `django.conf.__getattr__` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/conf/__init__.py#L82-L83 has special logic for `STATIC_URL`], so it calls the staticmethod `LazySettings._add_script_prefix`
    31 7. `LazySettings._add_script_prefix` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/conf/__init__.py#L134 calls `get_script_prefix`]
    32 8. `get_script_prefix` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/urls/base.py#L129-L135 looks at `django.urls.base._prefixes.value`], but it's running in a new thread (step 4), so it doesn't contain the `FORCE_SCRIPT_NAME` value (step 3) and returns `"/"`
     295. `check_url_settings` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/core/checks/urls.py#L108-L109 runs and accesses] `settings.STATIC_URL`; importantly, this is the first time in the startup lifecycle that `settings.STATIC_URL` has ever been accessed
     306. `django.conf.__getattr__` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/conf/__init__.py#L82-L83 has special logic] for `STATIC_URL`, so it calls the staticmethod `LazySettings._add_script_prefix`
     317. `LazySettings._add_script_prefix` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/conf/__init__.py#L134 calls] `get_script_prefix`
     328. `get_script_prefix` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/urls/base.py#L129-L135 looks at] `django.urls.base._prefixes.value`, but it's running in a new thread (step 4), so it doesn't contain the `FORCE_SCRIPT_NAME` value (step 3) and returns `"/"`
    33339. `django.conf.__getattr__` (step 6) permanently caches the incorrect value of `settings.STATIC_URL` in `LazySettings.__dict__` (which is not thread-local); all future requests for `settings.STATIC_URL` will receive the incorrect value (`"/"`, instead of `settings.FORCE_SCRIPT_NAME`)
    343410. The first HTTP request comes in
     
    373713. `WSGIHandler.__call__` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/core/handlers/wsgi.py#L121 calls] `set_script_prefix` with the correct argument (the value `settings.FORCE_SCRIPT_NAME`)
    383814. `set_script_prefix` (just as in step 3) [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/urls/base.py#L120C5-L126C20 finally sets] `django.urls.base._prefixes.value` (which, remember, is a thread-local variable, but will persist for at least the remainder of this HTTP request) with the correct value
    39 15. While rendering the HTTP response, a template or some code calls [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/templatetags/static.py#L174-L179 `django.templatetags.static`]; assume that the app `"django.contrib.staticfiles"` is installed and configured to use some subclass of `StaticFilesStorage` (which is Django's typical configuration)
     3915. While rendering the HTTP response, a template or some code calls [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/templatetags/static.py#L174-L179 the function] `django.templatetags.static`; assume that the app `"django.contrib.staticfiles"` is installed and configured to use some subclass of `StaticFilesStorage` (which is Django's typical configuration)
    404016. `django.contrib.staticfiles.storage.staticfiles_storage.url()` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/templatetags/static.py#L129 is called]
    414117. `staticfiles_storage` (an instance of `StaticFilesStorage`) [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/core/files/storage/handler.py#L46 is lazily constructed]
    424218. `StaticFilesStorage.__init__` is called; assume it has no arguments from `settings.STORAGES["staticfiles"]["OPTIONS"]` ([https://docs.djangoproject.com/en/5.2/ref/settings/#storages this is Django's default])
    43 19. `StaticFilesStorage.__init__` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/contrib/staticfiles/storage.py#L27-L30 defaults to set its `self._base_url`] to `settings.STATIC_URL`, but `settings.STATIC_URL` returns an incorrect value (step 9)
     4319. `StaticFilesStorage.__init__` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/contrib/staticfiles/storage.py#L27-L30 defaults to set] its `self._base_url` to `settings.STATIC_URL`, but `settings.STATIC_URL` returns an incorrect value (step 9)
    444420. Continuing the call in step 16, `FilesystemStorage.url` [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/core/files/storage/filesystem.py#L212 forms the actual URL] from `self.base_url`
    454521. `self.base_url`, [https://github.com/django/django/blob/1167cd1d639c3fee69dbdef351d31e8a17d1fedf/django/core/files/storage/filesystem.py#L47-L51 a cached property], relies on the incorrect `self._base_url`
Back to Top