Fix an issue that clearing the image cache may cause resource leaks (#104527)
This commit is contained in:
parent
a56c5e51ae
commit
b5adbee145
@ -397,16 +397,16 @@ class ImageCache {
|
|||||||
if (!kReleaseMode) {
|
if (!kReleaseMode) {
|
||||||
listenerTask = TimelineTask(parent: timelineTask)..start('listener');
|
listenerTask = TimelineTask(parent: timelineTask)..start('listener');
|
||||||
}
|
}
|
||||||
// If we're doing tracing, we need to make sure that we don't try to finish
|
// A multi-frame provider may call the listener more than once. We need do make
|
||||||
// the trace entry multiple times if we get re-entrant calls from a multi-
|
// sure that some cleanup works won't run multiple times, such as finishing the
|
||||||
// frame provider here.
|
// tracing task or removing the listeners
|
||||||
bool listenedOnce = false;
|
bool listenedOnce = false;
|
||||||
|
|
||||||
// We shouldn't use the _pendingImages map if the cache is disabled, but we
|
// We shouldn't use the _pendingImages map if the cache is disabled, but we
|
||||||
// will have to listen to the image at least once so we don't leak it in
|
// will have to listen to the image at least once so we don't leak it in
|
||||||
// the live image tracking.
|
// the live image tracking.
|
||||||
// If the cache is disabled, this variable will be set.
|
final bool trackPendingImage = maximumSize > 0 && maximumSizeBytes > 0;
|
||||||
_PendingImage? untrackedPendingImage;
|
late _PendingImage pendingImage;
|
||||||
void listener(ImageInfo? info, bool syncCall) {
|
void listener(ImageInfo? info, bool syncCall) {
|
||||||
int? sizeBytes;
|
int? sizeBytes;
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
@ -421,14 +421,14 @@ class ImageCache {
|
|||||||
_trackLiveImage(key, result, sizeBytes);
|
_trackLiveImage(key, result, sizeBytes);
|
||||||
|
|
||||||
// Only touch if the cache was enabled when resolve was initially called.
|
// Only touch if the cache was enabled when resolve was initially called.
|
||||||
if (untrackedPendingImage == null) {
|
if (trackPendingImage) {
|
||||||
_touch(key, image, listenerTask);
|
_touch(key, image, listenerTask);
|
||||||
} else {
|
} else {
|
||||||
image.dispose();
|
image.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _PendingImage? pendingImage = untrackedPendingImage ?? _pendingImages.remove(key);
|
_pendingImages.remove(key);
|
||||||
if (pendingImage != null) {
|
if (!listenedOnce) {
|
||||||
pendingImage.removeListener();
|
pendingImage.removeListener();
|
||||||
}
|
}
|
||||||
if (!kReleaseMode && !listenedOnce) {
|
if (!kReleaseMode && !listenedOnce) {
|
||||||
@ -445,10 +445,9 @@ class ImageCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ImageStreamListener streamListener = ImageStreamListener(listener);
|
final ImageStreamListener streamListener = ImageStreamListener(listener);
|
||||||
if (maximumSize > 0 && maximumSizeBytes > 0) {
|
pendingImage = _PendingImage(result, streamListener);
|
||||||
_pendingImages[key] = _PendingImage(result, streamListener);
|
if (trackPendingImage) {
|
||||||
} else {
|
_pendingImages[key] = pendingImage;
|
||||||
untrackedPendingImage = _PendingImage(result, streamListener);
|
|
||||||
}
|
}
|
||||||
// Listener is removed in [_PendingImage.removeListener].
|
// Listener is removed in [_PendingImage.removeListener].
|
||||||
result.addListener(streamListener);
|
result.addListener(streamListener);
|
||||||
|
@ -332,6 +332,33 @@ void main() {
|
|||||||
expect(imageCache.liveImageCount, 0);
|
expect(imageCache.liveImageCount, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Clearing image cache does not leak live images', () async {
|
||||||
|
imageCache.maximumSize = 1;
|
||||||
|
|
||||||
|
final ui.Image testImage1 = await createTestImage(width: 8, height: 8);
|
||||||
|
final ui.Image testImage2 = await createTestImage(width: 10, height: 10);
|
||||||
|
|
||||||
|
final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
|
||||||
|
final TestImageStreamCompleter completer2 = TestImageStreamCompleter()..testSetImage(testImage2);
|
||||||
|
|
||||||
|
imageCache.putIfAbsent(testImage1, () => completer1);
|
||||||
|
expect(imageCache.statusForKey(testImage1).pending, true);
|
||||||
|
expect(imageCache.statusForKey(testImage1).live, true);
|
||||||
|
|
||||||
|
imageCache.clear();
|
||||||
|
expect(imageCache.statusForKey(testImage1).pending, false);
|
||||||
|
expect(imageCache.statusForKey(testImage1).live, true);
|
||||||
|
|
||||||
|
completer1.testSetImage(testImage1);
|
||||||
|
expect(imageCache.statusForKey(testImage1).keepAlive, true);
|
||||||
|
expect(imageCache.statusForKey(testImage1).live, false);
|
||||||
|
|
||||||
|
imageCache.putIfAbsent(testImage2, () => completer2);
|
||||||
|
expect(imageCache.statusForKey(testImage1).tracked, false); // evicted
|
||||||
|
expect(imageCache.statusForKey(testImage2).tracked, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
test('Evicting a pending image clears the live image by default', () async {
|
test('Evicting a pending image clears the live image by default', () async {
|
||||||
final ui.Image testImage = await createTestImage(width: 8, height: 8);
|
final ui.Image testImage = await createTestImage(width: 8, height: 8);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user