Skip to content

GridSpec never cleans up removed children: in-place objects mutation defeats the diff in _get_objects #8710

Description

@SimonHeybrock

Description

Removing or replacing a child of a rendered GridSpec never calls the child's _cleanup(root). For panes with document/stream hooks this leaks live objects: a pn.pane.HoloViews wrapping a stream-driven DynamicMap keeps its rendered plot subscribed to the stream, so each slot replacement permanently adds one more plot doing full render work on every stream event.

Cause: GridSpec.__setitem__/__delitem__ mutate self.objects in place and then self.param.trigger('objects'). The removed-child cleanup in GridSpec._get_objects diffs old_objects against the current objects — but after an in-place mutation the event's old value is the same dict, so the diff is always empty and no removed child is ever cleaned. Other layouts reassign objects and are unaffected (control below). Present in 1.9.3; the mutation pattern is unchanged on main.

Complete, minimal, self-contained example

import holoviews as hv, panel as pn
hv.extension('bokeh')

pipe = hv.streams.Pipe(data=hv.Curve([1, 2, 3]))
dmap = hv.DynamicMap(lambda data: hv.Curve([]), streams=[pipe])

grid = pn.GridSpec()
grid[0, 0] = pn.pane.HoloViews(dmap)
root = grid.get_root()
print(len(pipe.subscribers))  # 1

del grid[0, 0]
print(len(pipe.subscribers))  # 1  <- removed pane was never cleaned up

grid[0, 0] = pn.pane.HoloViews(dmap)
print(len(pipe.subscribers))  # 2  <- one leaked live plot per replacement

Control showing Column cleans up correctly (only GridSpec is affected):

pipe2 = hv.streams.Pipe(data=hv.Curve([1, 2, 3]))
dmap2 = hv.DynamicMap(lambda data: hv.Curve([]), streams=[pipe2])
col = pn.Column(pn.pane.HoloViews(dmap2))
root2 = col.get_root()
print(len(pipe2.subscribers))  # 1
col[:] = []
print(len(pipe2.subscribers))  # 0

Versions: panel 1.9.3, bokeh 3.9.2, holoviews 1.23.1, Python 3.12, Linux.

Found while chasing a production leak (scipp/esslivedata#1224): each rebuild of a grid cell hosting a live plot permanently doubled the render work for that cell's streams.

Related: holoviz/holoviews#6988Plot.cleanup() currently severs all weakly-wrapped subscribers on shared streams, which matters for how a fix orders cleanup vs. rendering of a replacement child.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions