Skip to content

Commit 000ee13

Browse files
authored
fix: effect re-entrancy (#174)
1 parent 46cac53 commit 000ee13

8 files changed

Lines changed: 101 additions & 5 deletions

File tree

packages/flutter_solidart/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 2.7.4
2+
3+
### Changes from solidart
4+
5+
- **FIX**: Prevent effect re-entrancy when a signal is written during the effect's first run. The write no longer re-enters the running effect mid-execution, which could throw `LateInitializationError`.
6+
17
## 2.7.3
28

39
### Changes from solidart

packages/flutter_solidart/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: flutter_solidart
22
description: A simple State Management solution for Flutter applications inspired by SolidJS
3-
version: 2.7.3
3+
version: 2.7.4
44
repository: https://github.com/nank1ro/solidart
55
documentation: https://solidart.mariuti.com
66
topics:
@@ -18,7 +18,7 @@ dependencies:
1818
flutter:
1919
sdk: flutter
2020
meta: ^1.11.0
21-
solidart: ^2.8.5
21+
solidart: ^2.8.6
2222

2323
dev_dependencies:
2424
disco: ^1.0.0

packages/solidart/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.8.6
2+
3+
- **FIX**: Prevent effect re-entrancy when a signal is written during the effect's first run. The write no longer re-enters the running effect mid-execution, which could throw `LateInitializationError`.
4+
15
## 2.8.5
26

37
- **FIX**: Return up-to-date value from `Computed.untrackedValue` after dependency changes.

packages/solidart/lib/src/core/effect.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ class Effect implements ReactionInterface {
184184
}
185185
final prevSub = reactiveSystem.setCurrentSub(_internalEffect);
186186

187+
reactiveSystem.startBatch();
187188
try {
188189
_internalEffect.run();
189190
} catch (e, s) {
@@ -193,6 +194,8 @@ class Effect implements ReactionInterface {
193194
rethrow;
194195
}
195196
} finally {
197+
reactiveSystem.endBatch();
198+
// ignore: cascade_invocations
196199
reactiveSystem.setCurrentSub(prevSub);
197200
if (SolidartConfig.autoDispose) {
198201
_mayDispose();

packages/solidart/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: solidart
22
description: A simple State Management solution for Dart applications inspired by SolidJS
3-
version: 2.8.5
3+
version: 2.8.6
44
repository: https://github.com/nank1ro/solidart
55
documentation: https://solidart.mariuti.com
66
topics:
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import 'package:solidart/solidart.dart';
2+
import 'package:test/test.dart';
3+
4+
void main() {
5+
group('effect re-entrancy during initial run', () {
6+
test(
7+
'''a signal write during the effect first run does not re-enter the effect callback while it is still on the stack''',
8+
() {
9+
final previousAutoDispose = SolidartConfig.autoDispose;
10+
addTearDown(() => SolidartConfig.autoDispose = previousAutoDispose);
11+
SolidartConfig.autoDispose = false;
12+
final source = Signal(0, name: 'source');
13+
var running = false;
14+
var maxConcurrentDepth = 0;
15+
var runs = 0;
16+
17+
final effect = Effect(() {
18+
runs++;
19+
// Detect re-entrancy: if the callback is invoked while a previous
20+
// invocation is still on the stack, `running` is already true.
21+
expect(running, isFalse, reason: 'effect re-entered its own run');
22+
running = true;
23+
maxConcurrentDepth++;
24+
25+
// Subscribe to `source`, then write it during the FIRST run. With
26+
// synchronous flush this re-enters the callback before `running`
27+
// is reset → the expect above fails.
28+
final value = source.value;
29+
if (runs == 1) {
30+
source.value = value + 1;
31+
}
32+
33+
maxConcurrentDepth--;
34+
running = false;
35+
});
36+
37+
addTearDown(effect.dispose);
38+
expect(maxConcurrentDepth, 0);
39+
// The effect re-runs once (sequentially) for the value change.
40+
expect(runs, 2);
41+
expect(source.value, 1);
42+
},
43+
);
44+
45+
test(
46+
'''a late-final read inside the effect survives a write triggered mid-construction''',
47+
() {
48+
final previousAutoDispose = SolidartConfig.autoDispose;
49+
addTearDown(() => SolidartConfig.autoDispose = previousAutoDispose);
50+
SolidartConfig.autoDispose = false;
51+
// `dep` mimics a controller built lazily during the effect run whose
52+
// constructor writes to a collection signal.
53+
final trigger = Signal(0, name: 'trigger');
54+
late final int lazy;
55+
var lazyInitialized = false;
56+
57+
final effect = Effect(() {
58+
// Read `trigger` (subscribe). On first run, lazily initialize a
59+
// value whose initializer writes `trigger` — a synchronous flush
60+
// would re-enter HERE before `lazy` finishes initializing.
61+
final t = trigger.value;
62+
if (!lazyInitialized) {
63+
lazyInitialized = true;
64+
// Initializer body writes the signal the effect depends on.
65+
trigger.value = t + 1;
66+
lazy = 42;
67+
}
68+
// Reading `lazy` must never throw LateInitializationError.
69+
expect(lazy, 42);
70+
});
71+
72+
addTearDown(effect.dispose);
73+
expect(lazy, 42);
74+
},
75+
);
76+
});
77+
}

packages/solidart_hooks/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 3.1.4
2+
3+
### Changes from solidart
4+
5+
- **FIX**: Prevent effect re-entrancy when a signal is written during the effect's first run. The write no longer re-enters the running effect mid-execution, which could throw `LateInitializationError`.
6+
17
## 3.1.3
28

39
### Changes from solidart

packages/solidart_hooks/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: solidart_hooks
22
description: Flutter Hooks bindings for Solidart, suitable for ephemeral state and for writing less boilerplate.
3-
version: 3.1.3
3+
version: 3.1.4
44
repository: https://github.com/nank1ro/solidart
55
documentation: https://solidart.mariuti.com
66
topics:
@@ -18,7 +18,7 @@ dependencies:
1818
flutter:
1919
sdk: flutter
2020
flutter_hooks: ^0.21.3+1
21-
flutter_solidart: ^2.7.3
21+
flutter_solidart: ^2.7.4
2222

2323
dev_dependencies:
2424
flutter_test:

0 commit comments

Comments
 (0)