Skip to content

Commit 50fa5a8

Browse files
authored
fix: disposable freeze error (bytedance#377)
1 parent b6433f7 commit 50fa5a8

File tree

5 files changed

+96
-96
lines changed

5 files changed

+96
-96
lines changed
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Emitter, Event } from './event';
2+
import { Disposable } from './disposable';
3+
4+
export class DisposableImpl implements Disposable {
5+
readonly toDispose = new DisposableCollection();
6+
7+
dispose(): void {
8+
this.toDispose.dispose();
9+
}
10+
11+
get disposed(): boolean {
12+
return this.toDispose.disposed;
13+
}
14+
15+
get onDispose(): Event<void> {
16+
return this.toDispose.onDispose;
17+
}
18+
}
19+
20+
export class DisposableCollection implements Disposable {
21+
protected readonly disposables: Disposable[] = [];
22+
23+
protected readonly onDisposeEmitter = new Emitter<void>();
24+
25+
private _disposed = false;
26+
27+
constructor(...toDispose: Disposable[]) {
28+
toDispose.forEach((d) => this.push(d));
29+
}
30+
31+
get length() {
32+
return this.disposables.length;
33+
}
34+
35+
get onDispose(): Event<void> {
36+
return this.onDisposeEmitter.event;
37+
}
38+
39+
get disposed(): boolean {
40+
return this._disposed;
41+
}
42+
43+
dispose(): void {
44+
if (this.disposed) {
45+
return;
46+
}
47+
this._disposed = true;
48+
this.disposables
49+
.slice()
50+
.reverse()
51+
.forEach((disposable) => {
52+
try {
53+
disposable.dispose();
54+
} catch (e) {
55+
console.error(e);
56+
}
57+
});
58+
this.onDisposeEmitter.fire(undefined);
59+
this.onDisposeEmitter.dispose();
60+
}
61+
62+
push(disposable: Disposable): Disposable {
63+
if (this.disposed) return Disposable.NULL;
64+
if (disposable === Disposable.NULL) {
65+
return Disposable.NULL;
66+
}
67+
const { disposables } = this;
68+
if (disposables.find((d) => d === disposable)) {
69+
return Disposable.NULL;
70+
}
71+
const originalDispose = disposable.dispose;
72+
const toRemove = Disposable.create(() => {
73+
const index = disposables.indexOf(disposable);
74+
if (index !== -1) {
75+
disposables.splice(index, 1);
76+
}
77+
disposable.dispose = originalDispose;
78+
});
79+
disposable.dispose = () => {
80+
toRemove.dispose();
81+
disposable.dispose();
82+
};
83+
disposables.push(disposable);
84+
return toRemove;
85+
}
86+
87+
pushAll(disposables: Disposable[]): Disposable[] {
88+
return disposables.map((disposable) => this.push(disposable));
89+
}
90+
}

packages/common/utils/src/disposable.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, test, expect } from 'vitest';
22

3-
import { Disposable, DisposableCollection, DisposableImpl } from './disposable';
3+
import { DisposableCollection, DisposableImpl } from './disposable-collection';
4+
import { Disposable } from './disposable';
45

56
describe('disposable', () => {
67
test('Disposable', async () => {
Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { Emitter, type Event } from './event';
2-
31
/**
42
* An object that performs a cleanup operation when `.dispose()` is called.
53
*
@@ -29,91 +27,3 @@ export namespace Disposable {
2927

3028
export const NULL = Object.freeze(create(() => {}));
3129
}
32-
33-
export class DisposableImpl implements Disposable {
34-
readonly toDispose = new DisposableCollection();
35-
36-
dispose(): void {
37-
this.toDispose.dispose();
38-
}
39-
40-
get disposed(): boolean {
41-
return this.toDispose.disposed;
42-
}
43-
44-
get onDispose(): Event<void> {
45-
return this.toDispose.onDispose;
46-
}
47-
}
48-
49-
export class DisposableCollection implements Disposable {
50-
protected readonly disposables: Disposable[] = [];
51-
52-
protected readonly onDisposeEmitter = new Emitter<void>();
53-
54-
private _disposed = false;
55-
56-
constructor(...toDispose: Disposable[]) {
57-
toDispose.forEach((d) => this.push(d));
58-
}
59-
60-
get length() {
61-
return this.disposables.length;
62-
}
63-
64-
get onDispose(): Event<void> {
65-
return this.onDisposeEmitter.event;
66-
}
67-
68-
get disposed(): boolean {
69-
return this._disposed;
70-
}
71-
72-
dispose(): void {
73-
if (this.disposed) {
74-
return;
75-
}
76-
this._disposed = true;
77-
this.disposables
78-
.slice()
79-
.reverse()
80-
.forEach((disposable) => {
81-
try {
82-
disposable.dispose();
83-
} catch (e) {
84-
console.error(e);
85-
}
86-
});
87-
this.onDisposeEmitter.fire(undefined);
88-
this.onDisposeEmitter.dispose();
89-
}
90-
91-
push(disposable: Disposable): Disposable {
92-
if (this.disposed) return Disposable.NULL;
93-
if (disposable === Disposable.NULL) {
94-
return Disposable.NULL;
95-
}
96-
const { disposables } = this;
97-
if (disposables.find((d) => d === disposable)) {
98-
return Disposable.NULL;
99-
}
100-
const originalDispose = disposable.dispose;
101-
const toRemove = Disposable.create(() => {
102-
const index = disposables.indexOf(disposable);
103-
if (index !== -1) {
104-
disposables.splice(index, 1);
105-
}
106-
disposable.dispose = originalDispose;
107-
});
108-
disposable.dispose = () => {
109-
toRemove.dispose();
110-
disposable.dispose();
111-
};
112-
disposables.push(disposable);
113-
return toRemove;
114-
}
115-
116-
pushAll(disposables: Disposable[]): Disposable[] {
117-
return disposables.map((disposable) => this.push(disposable));
118-
}
119-
}

packages/common/utils/src/event.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { NOOP } from './objects';
2-
import { type Disposable } from './disposable';
3-
4-
const DisposableNULL: Disposable = Object.freeze({ dispose: NOOP });
2+
import { Disposable } from './disposable';
53

64
export interface EventListener<T> {
75
(args: T): void;
@@ -12,7 +10,7 @@ export interface Event<T> {
1210
}
1311

1412
export namespace Event {
15-
export const None: Event<any> = () => DisposableNULL;
13+
export const None: Event<any> = () => Disposable.NULL;
1614
}
1715

1816
export class Emitter<T = any> {
@@ -28,7 +26,7 @@ export class Emitter<T = any> {
2826
if (!this._event) {
2927
this._event = (listener: EventListener<T>, thisArgs?: any) => {
3028
if (this._disposed) {
31-
return DisposableNULL;
29+
return Disposable.NULL;
3230
}
3331
if (!this._listeners) {
3432
this._listeners = [];

packages/common/utils/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from './objects';
33
export * from './types';
44
export * from './event';
55
export * from './disposable';
6+
export * from './disposable-collection';
67
export * from './cancellation';
78
export * from './promise-util';
89
export * from './cache';

0 commit comments

Comments
 (0)