mirror of
https://github.com/django-components/django-components.git
synced 2025-12-23 11:12:57 +00:00
Fix premature cleanup in provide. (#1490)
Co-authored-by: Joey Jurjens <joey@highbiza.nl>
This commit is contained in:
parent
8b131f6eaa
commit
b74f1ab112
2 changed files with 57 additions and 0 deletions
|
|
@ -93,12 +93,20 @@ provide_references: Dict[str, Set[str]] = defaultdict(set)
|
|||
# NOTE: We manually clean up the entries when components are garbage collected.
|
||||
component_provides: Dict[str, Dict[str, str]] = defaultdict(dict)
|
||||
|
||||
# Track which {% provide %} blocks are currently active (rendering).
|
||||
# This prevents premature cache cleanup when components are garbage collected.
|
||||
active_provides: Set[str] = set()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def managed_provide_cache(provide_id: str) -> Generator[None, None, None]:
|
||||
# Mark this provide block as active
|
||||
active_provides.add(provide_id)
|
||||
try:
|
||||
yield
|
||||
except Exception as e:
|
||||
# Mark this provide block as no longer active
|
||||
active_provides.discard(provide_id)
|
||||
# NOTE: In case of an error in within the `{% provide %}` block (e.g. when rendering a component),
|
||||
# we rely on the component finalizer to remove the references.
|
||||
# But we still want to call cleanup in case `{% provide %}` contained no components.
|
||||
|
|
@ -106,11 +114,17 @@ def managed_provide_cache(provide_id: str) -> Generator[None, None, None]:
|
|||
# Forward the error
|
||||
raise e from None
|
||||
|
||||
# Mark this provide block as no longer active
|
||||
active_provides.discard(provide_id)
|
||||
# Cleanup on success
|
||||
_cache_cleanup(provide_id)
|
||||
|
||||
|
||||
def _cache_cleanup(provide_id: str) -> None:
|
||||
# Don't cleanup if the provide block is still active.
|
||||
if provide_id in active_provides:
|
||||
return
|
||||
|
||||
# Remove provided data from the cache, IF there are no more references to it.
|
||||
# A `{% provide %}` will have no reference if:
|
||||
# - It contains no components in its body
|
||||
|
|
|
|||
|
|
@ -1297,3 +1297,46 @@ class TestProvideCache:
|
|||
Root.render()
|
||||
|
||||
_assert_clear_cache()
|
||||
|
||||
@djc_test(parametrize=PARAMETRIZE_CONTEXT_BEHAVIOR)
|
||||
def test_provide_cache_not_cleaned_while_active(self, components_settings):
|
||||
@register("injectee31")
|
||||
class Injectee(Component):
|
||||
template: types.django_html = """
|
||||
<div>{{ value }}</div>
|
||||
"""
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
data = self.inject("my_provide")
|
||||
return {"value": data.value}
|
||||
|
||||
@register("root")
|
||||
class Root(Component):
|
||||
template: types.django_html = """
|
||||
<div>{{ content|safe }}</div>
|
||||
"""
|
||||
|
||||
def get_template_data(self, args, kwargs, slots, context):
|
||||
# Nested synchronous rendering triggers GC between components.
|
||||
nested_template = Template(
|
||||
"""
|
||||
{% load component_tags %}
|
||||
{% for i in "xxxxxxxxxx" %}
|
||||
{% provide "my_provide" value="hello" %}
|
||||
{% component "injectee31" %}{% endcomponent %}
|
||||
{% component "injectee31" %}{% endcomponent %}
|
||||
{% component "injectee31" %}{% endcomponent %}
|
||||
{% endprovide %}
|
||||
{% endfor %}
|
||||
"""
|
||||
)
|
||||
content = nested_template.render(Context({}))
|
||||
return {"content": content}
|
||||
|
||||
_assert_clear_cache()
|
||||
|
||||
rendered = Root.render()
|
||||
|
||||
# 10 iterations * 3 components = 30 occurrences
|
||||
assert rendered.count(">hello</div>") == 30
|
||||
_assert_clear_cache()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue