Skip to content

Commit 8ee5443

Browse files
committed
Fixes bug in observable utility
1 parent 5c44e81 commit 8ee5443

File tree

2 files changed

+42
-1
lines changed

2 files changed

+42
-1
lines changed

src/vs/base/common/observableImpl/derived.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
import { BugIndicatingError } from 'vs/base/common/errors';
67
import { IReader, IObservable, BaseObservable, IObserver, _setDerived } from 'vs/base/common/observableImpl/base';
78
import { getLogger } from 'vs/base/common/observableImpl/logging';
89

@@ -167,10 +168,15 @@ export class Derived<T> extends BaseObservable<T, void> implements IReader, IObs
167168
public endUpdate(): void {
168169
this.updateCount--;
169170
if (this.updateCount === 0) {
170-
for (const r of this.observers) {
171+
// End update could change the observer list.
172+
const observers = [...this.observers];
173+
for (const r of observers) {
171174
r.endUpdate(this);
172175
}
173176
}
177+
if (this.updateCount < 0) {
178+
throw new BugIndicatingError();
179+
}
174180
}
175181

176182
public handlePossibleChange<T>(observable: IObservable<T, unknown>): void {

src/vs/base/test/common/observable.test.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,41 @@ suite('observables', () => {
700700
assert.deepStrictEqual(log.getAndClearEntries(), ([]));
701701
});
702702

703+
test('changing observables in endUpdate', () => {
704+
const log = new Log();
705+
706+
const myObservable1 = new LoggingObservableValue('myObservable1', 0, log);
707+
const myObservable2 = new LoggingObservableValue('myObservable2', 0, log);
708+
709+
const myDerived1 = derived('myDerived1', (reader) => {
710+
const val = myObservable1.read(reader);
711+
log.log(`myDerived1.read(myObservable: ${val})`);
712+
return val;
713+
});
714+
715+
const myDerived2 = derived('myDerived2', (reader) => {
716+
const val = myObservable2.read(reader);
717+
if (val === 1) {
718+
myDerived1.read(reader);
719+
}
720+
log.log(`myDerived2.read(myObservable: ${val})`);
721+
return val;
722+
});
723+
724+
autorun('myAutorun', (reader) => {
725+
const myDerived1Val = myDerived1.read(reader);
726+
const myDerived2Val = myDerived2.read(reader);
727+
log.log(`myAutorun.run(myDerived1: ${myDerived1Val}, myDerived2: ${myDerived2Val})`);
728+
});
729+
730+
transaction(tx => {
731+
myObservable2.set(1, tx);
732+
// end update of this observable will trigger endUpdate of myDerived1 and
733+
// the autorun and the autorun will add myDerived2 as observer to myDerived1
734+
myObservable1.set(1, tx);
735+
});
736+
});
737+
703738
test('set dependency in derived', () => {
704739
const log = new Log();
705740

0 commit comments

Comments
 (0)