Signals: duplicate dependency links when a signal is written mid-computation (regression in 22.0.2)
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 58/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- angular, typescript
- Lĩnh vực
- frontend, performance
Hướng nghiên cứu
Start at the producerAccessed logic in @angular/core/primitives/signals and compare the 22.0.1 and 22.0.2 behavior described in the issue. Run ./run.sh from the linked reproduction, including repro-vue.mjs and repro-effect.mjs, to verify the duplicate-link and over-notification counts. Done means repeated reads remain deduplicated, with two links and one notification for a signal change across the affected versions.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
Yes
Description
Since 22.0.2, a reactive consumer that re-reads a signal it already depends on
creates a duplicate dependency link whenever a signal was written in between.
The producer ends up registered several times with the same consumer, so a single
change notifies that consumer several times.
This hits ordinary component templates, because a view is itself a reactive
consumer and both halves of the trigger are supplied by the framework:
- templates read the same signals many times, interleaved, in an order that
varies with@if/@for; - Angular writes child component inputs in between
(applyValueToInputSignal→signalSetFn→producerIncrementEpoch).
So within a single template pass:
read config() -> link validated at epoch = E
child input binding -> input write -> epoch = E+1
read config() again -> guard fails (E !== E+1) -> duplicate link
A real template from a component library reads config() 15 times and data()
5 times with child bindings throughout. Reproducing that shape with
@angular/core/primitives/signals (createWatch), and counting the consumer's
producer links plus the consumers registered on each producer:
| Angular | links per run | notifications for one config change |
|---|---|---|
| 21.2.5 | 2, 2, 2, 2, 2, 2 | 1 |
| 22.0.0 | 2, 2, 2, 2, 2, 2 | 1 |
| 22.0.1 | 2, 2, 2, 2, 2, 2 | 1 |
| 22.0.2 | 11, 11, 11, 11, 11, 11 | 6 |
| 22.1.7 | 11, 11, 11, 11, 11, 11 | 6 |
Expected on all versions: 2 links, 1 notification.
Each extra notification is an extra change detection pass, which re-runs the
view's effects. On a medium-sized application we measured an effect body running
24 times instead of once on first page load, with a very visible startup
slowdown. Restoring the 22.0.1 guard brings it back to 1.
The symptom is limited to the first render, which the mechanism predicts:
signalSetFn increments epoch only when a value actually changes. On first
render every binding goes from undefined to a value, so epoch moves
constantly in the middle of template passes. Once the screen is stable, bindings
stop changing, epoch stops moving mid-pass, and no further duplicates appear.
There is no application-level workaround: neither the repeated reads nor the
input writes are written by the application.
Root cause
producerAccessed deduplicates in three steps. The two fast paths cover only an
immediate re-read of the same producer, and a replay in the same order. The third
path — the one that catches re-reads arriving at a different position — changed in
f902d1d35e
("perf: detect existing signal dependency without checking all producer links"),
released in 22.0.2:
// 22.0.1
... && (!isRecomputing || isValidLink(prevConsumerLink, activeConsumer))
// 22.0.2
... && (!isRecomputing || prevConsumerLink.knownValidAtEpoch === epoch)
epoch is a module-global counter incremented by every signal write. The link
being checked is still valid; it is merely older than an unrelated write. The
optimisation is sound only if epoch advancing implies the link may be stale, and
that does not hold for any computation during which a signal is written — which,
for a view, is the normal case.
Please provide a link to a minimal reproduction of the bug
https://github.com/medi6/angular-regression.git
Please provide the exception or error you saw
No exception — silent over-notification of views, extra change detection passes,
and effects running several times per change.
Please provide the environment you discovered this bug in (run ng version)
Angular CLI 22.1.8, Angular 22.1.7, Node 22.16.0, TypeScript 6.0.3, zone.js-based change detection.
Anything else?
Reproduction with the repo :
repro-vue.mjs (view-shaped consumer) and repro-effect.mjs (effect writing a
signal). No DOM, no browser, no test runner — only @angular/core/primitives/signals.
./run.sh installs each Angular version side by side and prints the table above.
- Ngôn ngữ chính
- TypeScript
- Star
- 101k
- Fork
- 28.1k
- Merge trung bình
- 2 ngày 3 giờ
- Pull request đã merge (30 ngày)
- 275
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của angular/angular
-
area: docs
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
-
area: forms forms: signals
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
-
area: docs gemini-triaged
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
-
area: forms forms: signals
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
-
area: docs area: forms
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Tất cả issue của angular/angular
Issue tương tự
-
enhancement
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
dennys-bd/agent-hive#184 ·
-
Add: hunch Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
AbdelStark/awesome-typesafe#104 ·
-
ai-observability bug team/ai-observability
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
vicharanashala/fln#563 ·