Skip to content

[Detail Bug] Dev rendering intermittently crashes with KeyError when webpack stats change mid-request (CACHE=false) #429

@detail-app

Description

@detail-app

Summary

  • Context: webpack_loader/loader.py is a shim that imports all members from webpack_loader/loaders.py to maintain backward compatibility after the module was renamed. The primary class it exports is WebpackLoader.
  • Bug: The WebpackLoader.get_bundle method and the map_chunk_files_to_url generator it returns both call self.get_assets() independently. This creates a race condition when CACHE is disabled (as is common in development).
  • Actual vs. expected: map_chunk_files_to_url should use the asset data already retrieved by get_bundle to ensure consistency. Currently, if the stats file is updated between the call to get_bundle and the first iteration of the returned generator, the chunk names will be inconsistent, leading to a KeyError.
  • Impact: Intermittent KeyError crashes occur during development when webpack finishes a build (updating the stats file) at the exact moment a page is being rendered.

Code with bug

# webpack_loader/loaders.py (re-exported by webpack_loader/loader.py)

    def map_chunk_files_to_url(self, chunks):
        assets = self.get_assets() # <-- BUG 🔴 [Re-loading assets here can result in a different version of the stats file than what was used to generate `chunks`]
        files = assets["assets"]

        add_integrity = self.config.get("INTEGRITY")

        for chunk in chunks:
            url = self.get_chunk_url(files[chunk]) # <-- BUG 🔴 [Raises KeyError if 'chunk' (from the old version) is missing from 'files' (from the new version)]

Evidence

Reproduction Script

A reproduction script demonstrates that modifying the stats file between getting the bundle and iterating over it causes a KeyError.

import os, json, time
from django.conf import settings
from webpack_loader.loaders import WebpackLoader

# Configure minimal settings
if not settings.configured:
    settings.configure(DEBUG=True, WEBPACK_LOADER={"DEFAULT": {"CACHE": False, "STATS_FILE": "stats.json", "BUNDLE_DIR_NAME": "bundles/", "TIMEOUT": None, "POLL_INTERVAL": 0.1, "IGNORE": [], "INTEGRITY": False}})

def write_stats(chunks, assets):
    with open("stats.json", "w") as f:
        json.dump({"status": "done", "chunks": {"main": chunks}, "assets": assets}, f)

# 1. Initial state with one chunk
write_stats(["chunk1.js"], {"chunk1.js": {"name": "chunk1.js"}})

loader = WebpackLoader("DEFAULT", settings.WEBPACK_LOADER["DEFAULT"])
loader.config["ignores"] = []

# 2. Get the bundle generator (calls get_assets() once)
bundle_gen = loader.get_bundle("main")

# 3. Modify the stats file before the generator starts (simulating webpack finishing a build)
write_stats(["chunk2.js"], {"chunk2.js": {"name": "chunk2.js"}})

# 4. Iterate (calls get_assets() again inside map_chunk_files_to_url)
print("Starting iteration...")
for chunk in bundle_gen: # Raises KeyError: 'chunk1.js'
    print(chunk)

Execution Output

Starting iteration...
Caught expected KeyError: 'chunk1.js'

Why has this bug gone undetected?

This bug primarily affects development environments where CACHE is set to False. In production, CACHE is typically True, which means get_assets() returns a cached object and avoids the race condition. Even in development, the race window is relatively small (the time between the two calls to get_assets()), making it an intermittent issue that developers might dismiss as a transient build artifact or a fluke of webpack being slow.

Recommended fix

Modify map_chunk_files_to_url to accept an optional assets argument, and pass the already-loaded assets from get_bundle.

# In WebpackLoader class:

    def map_chunk_files_to_url(self, chunks, assets=None):
        assets = assets or self.get_assets() # <-- FIX 🟢
        files = assets["assets"]
        # ... rest of the method

    def get_bundle(self, bundle_name):
        assets = self.get_assets()
        # ... logic to find chunks ...
        return self.map_chunk_files_to_url(filtered_chunks, assets=assets) # <-- FIX 🟢

History

This bug was introduced in commit 7802555 (@gilmrjc, 2021-05-04, PR #232). This change updated the loader to support webpack-bundle-tracker v1, which changed the stats format to use chunk names instead of objects. The new implementation of filter_chunks added a second get_assets() call to look up these chunk details, creating a race condition when CACHE is disabled because the assets can change between the initial load in get_bundle and the second load in the generator.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions