Multi-diff model teardown re-enters binding disposal and throws
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
Does this issue occur when all extensions are disabled?: N/A — reproduced in isolated browser diagnostics with no extension host; not separately reproduced via the installed app's UI.
- VS Code Version: 1.138.0-insider, `7b7e49c83affacfac726040280da69b4999f3e01` (2026-09-11). Diagnostics used `33a6a088236067e1a16d08481d617dfcf6da2e2c`; affected code is identical.
- OS Version: macOS 26.6.2, arm64; headless Chromium diagnostics.
Steps to Reproduce:
1. In a browser harness, create a real compact `MultiDiffEditorWidget` at 900×640 with 90 two-sided text diffs, real model services, and a synthetic text-model resolver. Collapse all before attaching the view model with `preserveFocus: true`.
2. Collapse/expand five times, allowing two animation frames between operations, then call `widget.setViewModel(undefined)` and dispose the input.
**Observed:** teardown reports `Cannot unbind a diff editor template from a different item` (11 occurrences in the diagnostic run). This was found during investigation; the original Insiders renderer log did not contain this error.
**Cause:** [`ManagedVirtualizedItem._clearBinding`](https://github.com/microsoft/vscode/blob/7b7e49c83affacfac726040280da69b4999f3e01/src/vs/editor/browser/widget/multiDiffEditor/virtualizedItemManager.ts#L225-L247) leaves its template reference/current binding exposed while disposing the binding. The captured synchronous chain is:
```text
_clearBinding -> DiffEditorItemBinding.dispose -> template.unbind
-> setItem(undefined) -> editor.setDiffModel(null)
-> CodeEditorWidget.onEndUpdate -> observable reaction
-> _clearBinding -> same binding.dispose -> template.unbind throws
```
The inner call sees an already-cleared template view model, but binding disposal is not yet complete; the binding's `isDisposed` guard cannot prevent re-entry.
**Expected / fix direction:** release each binding and return its template exactly once, without teardown errors. Make release re-entrancy-safe through an explicit releasing state or atomic lifecycle transition, preserving cleanup ordering rather than suppressing the assertion. A temporary `_clearBinding` re-entrancy guard eliminated all 11 errors.
Add regression coverage using the real model-detachment/observable callback path; a passive mock binding misses the re-entry. This is separate from #206959: the listener-count warning remains after this lifecycle problem is removed.
Relevant lifecycle history: https://github.com/microsoft/vscode/commit/10de1b9343662a4043258b418630481e27c863b1 (@hediet).
Contributor guide
Assessment
This issue has not been assessed yet.