Skip to content

Commit d5ae437

Browse files
committed
fix more mem leak + cleanups
1 parent 5efff43 commit d5ae437

File tree

2 files changed

+36
-37
lines changed

2 files changed

+36
-37
lines changed

addons/addon-image/src/kitty/KittyGraphicsHandler.ts

Lines changed: 25 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
8181
}
8282

8383
public reset(): void {
84-
for (const pending of this._pendingTransmissions.values()) {
85-
pending.decoder.release();
86-
}
87-
this._pendingTransmissions.clear();
88-
this._lastPendingKey = undefined;
84+
this._cleanupAllPending();
8985
if (this._activeDecoder) {
9086
this._activeDecoder.release();
9187
this._activeDecoder = null;
@@ -97,6 +93,21 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
9793
this.reset();
9894
}
9995

96+
private _removePendingEntry(key: number): void {
97+
this._pendingTransmissions.delete(key);
98+
if (this._lastPendingKey === key) {
99+
this._lastPendingKey = undefined;
100+
}
101+
}
102+
103+
private _cleanupAllPending(): void {
104+
for (const pending of this._pendingTransmissions.values()) {
105+
pending.decoder.release();
106+
}
107+
this._pendingTransmissions.clear();
108+
this._lastPendingKey = undefined;
109+
}
110+
100111
public start(): void {
101112
this._aborted = false;
102113
this._decodeError = false;
@@ -178,10 +189,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
178189
}
179190
this._activeDecoder = null;
180191
if (pending) {
181-
this._pendingTransmissions.delete(pendingKey);
182-
if (this._lastPendingKey === pendingKey) {
183-
this._lastPendingKey = undefined;
184-
}
192+
this._removePendingEntry(pendingKey);
185193
}
186194
this._aborted = true;
187195
return;
@@ -202,10 +210,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
202210
this._activeDecoder = null;
203211
this._decodeError = true;
204212
if (pending) {
205-
this._pendingTransmissions.delete(pendingKey);
206-
if (this._lastPendingKey === pendingKey) {
207-
this._lastPendingKey = undefined;
208-
}
213+
this._removePendingEntry(pendingKey);
209214
}
210215
}
211216
}
@@ -404,14 +409,8 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
404409
return true;
405410
}
406411

407-
const pendingKey = cmd.id ?? 0;
408-
409412
this._handleTransmit(cmd, bytes, decodeError);
410413

411-
// If still accumulating chunks, don't display yet
412-
if (this._pendingTransmissions.has(pendingKey)) return true;
413-
414-
// Display the completed image
415414
const id = cmd.id ?? this._kittyStorage.lastImageId;
416415
const image = this._kittyStorage.getImage(id);
417416
if (image) {
@@ -488,26 +487,17 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
488487
switch (selector) {
489488
case 'a':
490489
case 'A':
491-
// Delete all — also abort all in-flight uploads
492-
for (const pending of this._pendingTransmissions.values()) {
493-
pending.decoder.release();
494-
}
495-
this._pendingTransmissions.clear();
496-
this._lastPendingKey = undefined;
490+
this._cleanupAllPending();
497491
this._kittyStorage.deleteAll();
498492
break;
499493
case 'i':
500494
case 'I':
501-
// Delete by image ID — only abort the targeted upload
502495
if (cmd.id !== undefined) {
503496
const pending = this._pendingTransmissions.get(cmd.id);
504497
if (pending) {
505498
pending.decoder.release();
506-
this._pendingTransmissions.delete(cmd.id);
507-
if (this._lastPendingKey === cmd.id) {
508-
this._lastPendingKey = undefined;
509-
}
510499
}
500+
this._removePendingEntry(cmd.id);
511501
this._kittyStorage.deleteById(cmd.id);
512502
}
513503
break;
@@ -574,6 +564,7 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
574564
const zIndex = cmd.zIndex ?? 0;
575565
if (w !== bitmap.width || h !== bitmap.height) {
576566
const resized = await createImageBitmap(bitmap, { resizeWidth: w, resizeHeight: h });
567+
bitmap.close();
577568
this._kittyStorage.addImage(image.id, resized, true, layer, zIndex);
578569
} else {
579570
this._kittyStorage.addImage(image.id, bitmap, true, layer, zIndex);
@@ -615,7 +606,10 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler, IDispos
615606
canvas.getContext('2d')?.drawImage(img, 0, 0);
616607
createImageBitmap(canvas).then(resolve).catch(reject);
617608
});
618-
img.addEventListener('error', reject);
609+
img.addEventListener('error', () => {
610+
URL.revokeObjectURL(url);
611+
reject(new Error('Failed to load image'));
612+
});
619613
img.src = url;
620614
});
621615
}

addons/addon-image/src/kitty/KittyImageStorage.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,16 @@ export class KittyImageStorage implements IDisposable {
2121
private _nextImageId = 1;
2222
private readonly _images: Map<number, IKittyImageData> = new Map();
2323
private readonly _kittyIdToStorageId: Map<number, number> = new Map();
24+
private readonly _storageIdToKittyId: Map<number, number> = new Map();
2425

2526
private readonly _previousOnImageDeleted: ((storageId: number) => void) | undefined;
2627
private readonly _wrappedOnImageDeleted: (storageId: number) => void;
2728
private readonly _handleStorageImageDeleted = (storageId: number): void => {
28-
for (const [kittyId, mappedStorageId] of this._kittyIdToStorageId) {
29-
if (mappedStorageId === storageId) {
30-
this._kittyIdToStorageId.delete(kittyId);
31-
this._images.delete(kittyId);
32-
break;
33-
}
29+
const kittyId = this._storageIdToKittyId.get(storageId);
30+
if (kittyId !== undefined) {
31+
this._kittyIdToStorageId.delete(kittyId);
32+
this._storageIdToKittyId.delete(storageId);
33+
this._images.delete(kittyId);
3434
}
3535
};
3636

@@ -49,6 +49,7 @@ export class KittyImageStorage implements IDisposable {
4949
this._nextImageId = 1;
5050
this._images.clear();
5151
this._kittyIdToStorageId.clear();
52+
this._storageIdToKittyId.clear();
5253
}
5354

5455
public dispose(): void {
@@ -65,6 +66,7 @@ export class KittyImageStorage implements IDisposable {
6566
if (oldStorageId !== undefined) {
6667
this._storage.deleteImage(oldStorageId);
6768
this._kittyIdToStorageId.delete(imageId);
69+
this._storageIdToKittyId.delete(oldStorageId);
6870
}
6971

7072
if (!this._images.has(imageId) && this._images.size >= KittyImageStorage._maxStoredImages) {
@@ -81,6 +83,7 @@ export class KittyImageStorage implements IDisposable {
8183
public addImage(kittyId: number, image: HTMLCanvasElement | ImageBitmap, scrolling: boolean, layer: ImageLayer, zIndex: number): void {
8284
const storageId = this._storage.addImage(image, scrolling, layer, zIndex);
8385
this._kittyIdToStorageId.set(kittyId, storageId);
86+
this._storageIdToKittyId.set(storageId, kittyId);
8487
}
8588

8689
public getImage(kittyId: number): IKittyImageData | undefined {
@@ -93,6 +96,7 @@ export class KittyImageStorage implements IDisposable {
9396
if (storageId !== undefined) {
9497
this._storage.deleteImage(storageId);
9598
this._kittyIdToStorageId.delete(kittyId);
99+
this._storageIdToKittyId.delete(storageId);
96100
}
97101
}
98102

@@ -102,6 +106,7 @@ export class KittyImageStorage implements IDisposable {
102106
this._storage.deleteImage(storageId);
103107
}
104108
this._kittyIdToStorageId.clear();
109+
this._storageIdToKittyId.clear();
105110
}
106111

107112
public get images(): ReadonlyMap<number, IKittyImageData> {

0 commit comments

Comments
 (0)