Skip to content

Commit

Permalink
Leaked 2D contexts might consume all OS global GPU resources
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=273326
rdar://95930955

Reviewed by Matt Woodrow.

Limit all the GPUP accelerated image buffers to certain numbers:
 - 30000 total limit
 - 3000 layer tile, 2D Context backing stores limit per WP
 - 1000 2D Context backing stores limit per WP

Store the per-WP limit to the RemoteSharedResourceCache. This is shared
with all the RemoteRenderingBackend instances of a particular WP:
main thread rendering and Web Worker rendering.

If GPUP allocates unlimited amount of accelerated image buffers, the
allocations cause all of system IOSurface handles. The failures are
handled ok in GPUP code, but might cause problems in other parts of the
WebKit as well as in other processes. The underlying CGContexts will
also run into per-process Metal object count limits.

* LayoutTests/fast/canvas/image-buffer-backend-count-limit-expected.txt: Added.
* LayoutTests/fast/canvas/image-buffer-backend-count-limit.html: Added.
* Source/WebKit/GPUProcess/RemoteSharedResourceCache.cpp:
(WebKit::RemoteSharedResourceCache::didAddAcceleratedImageBuffer):
(WebKit::RemoteSharedResourceCache::didTakeAcceleratedImageBuffer):
(WebKit::RemoteSharedResourceCache::adjustAcceleratedImageBufferRenderingMode const):
* Source/WebKit/GPUProcess/RemoteSharedResourceCache.h:
* Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.cpp:
(WebKit::RemoteRenderingBackend::didCreateImageBuffer):
(WebKit::RemoteRenderingBackend::allocateImageBuffer):
(WebKit::RemoteRenderingBackend::releaseImageBuffer):
(WebKit::RemoteRenderingBackend::takeImageBuffer):

Canonical link: https://commits.webkit.org/278304@main
  • Loading branch information
kkinnunen-apple committed May 3, 2024
1 parent 33411be commit 78038e7
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Test creates many 2D contexts and checks when they are not created accelerated anymore

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS Effective rendering mode switches to Unaccelerated at instance: 1000
PASS successfullyParsed is true

TEST COMPLETE

64 changes: 64 additions & 0 deletions LayoutTests/fast/canvas/image-buffer-backend-count-limit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE HTML><!-- webkit-test-runner [ runSingly=true ] -->
<html>
<body>
<script src="../../resources/js-test.js"></script>
<script>
description("Test creates many 2D contexts and checks when they are not created accelerated anymore")

function pixelsEqual(a, b) {
for (let i = 0; i < 4; ++i) {
if (a[i] != b[i])
return false;
}
return true;
}

var transparentBlack = [0, 0, 0, 0];
var lime = [0, 255, 0, 255];
var imageData;
var context;
function runTest() {
if (typeof CanvasRenderingContext2D.prototype.getEffectiveRenderingModeForTesting === "undefined") {
testFailed("Need effective rendering mode API");
return;
}
let canvases = [];
for (let i = 0; i < 15000; ++i) {
let canvas = document.createElement('canvas');
canvases.push(canvas)
canvas.height = 100;
canvas.width = 100;
context = canvas.getContext("2d");
if (!context) {
testFailed(`Context ${i} not created`);
return;
}
context.fillStyle = "lime";
context.fillRect(0, 0, canvas.width, canvas.height);
imageData = context.getImageData(0, 0, 1, 1);
if (!imageData) {
testFailed("Context failed to allocate imageData");
return;
} else if (pixelsEqual(imageData.data, transparentBlack)) {
testFailed("Context was lost");
return;
} else if (!pixelsEqual(imageData.data, lime)) {
shouldBe("imageData.data", "lime");
return;
}
let renderingMode = context.getEffectiveRenderingModeForTesting();
if (renderingMode != "Accelerated") {
testPassed(`Effective rendering mode switches to ${renderingMode} at instance: ${i}`);
canvases.length = 0;
delete context;
delete canvas;
gc();
return;
}
}
testFailed(`All ${i} contexts accelerated. Currently unexpected`);
}
runTest();
</script>
</body>
</html>
2 changes: 2 additions & 0 deletions LayoutTests/platform/glib/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -3915,6 +3915,8 @@ imported/w3c/web-platform-tests/svg/text/reftests/transform-dynamic-change.html

# Cairo doesn't support hardware acceleration, this test will pass once we switch to Skia.
fast/canvas/canvas-will-read-frequently.html [ Failure ]
# Accelerated canvases not implemented for Cairo.
fast/canvas/image-buffer-backend-count-limit.html [ Skip ]

# End: Common failures between GTK and WPE.

Expand Down
3 changes: 3 additions & 0 deletions LayoutTests/platform/mac-wk1/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -2852,3 +2852,6 @@ http/tests/media/hls/hls-webvtt-style.html [ Pass Timeout ]
[ Ventura Debug ] imported/w3c/web-platform-tests/html/semantics/embedded-content/media-elements/track/track-element/track-mode.html [ Skip ]

webkit.org/b/273577 [ Ventura ] http/tests/media/hls/track-webvtt-multitracks.html [ Skip ]

# Limiting accelerated canvases not implemented for WK1
fast/canvas/image-buffer-backend-count-limit.html [ Skip ]
3 changes: 3 additions & 0 deletions LayoutTests/platform/wincairo/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,9 @@ webgl/webgl-worker.html [ Skip ]
# Accelerated canvas isn't supported yet
fast/canvas/canvas-will-read-frequently.html [ Skip ]

# Limiting accelerated canvases not implemented for WK1
fast/canvas/image-buffer-backend-count-limit.html [ Skip ]

#//////////////////////////////////////////////////////////////////////////////////////////
# These areas are not implemented well on WinCairo
#//////////////////////////////////////////////////////////////////////////////////////////
Expand Down
38 changes: 38 additions & 0 deletions Source/WebKit/GPUProcess/RemoteSharedResourceCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,20 @@
namespace WebKit {
using namespace WebCore;

// Per GPU process limit of accelerated image buffers.
// These consume limited global OS resources.
constexpr size_t acceleratedImageBufferGlobalLimit = 30000;

// Per GPU process count of current accelerated image buffers.
static std::atomic<size_t> acceleratedImageBufferGlobalCount;

// Per WebContent process limit of accelerated image buffers.
constexpr size_t acceleratedImageBufferLimit = 5000;

// Per WebContent process limit of accelerated image buffers for 2d context.
// It is most common to leak 2d contexts.
constexpr size_t acceleratedImageBuffer2DContextLimit = 1000;

constexpr Seconds defaultRemoteSharedResourceCacheTimeout = 15_s;

Ref<RemoteSharedResourceCache> RemoteSharedResourceCache::create()
Expand All @@ -57,6 +71,30 @@ void RemoteSharedResourceCache::releaseSerializedImageBuffer(WebCore::RenderingR
m_serializedImageBuffers.remove({ { identifier, 0 }, 0 });
}

void RemoteSharedResourceCache::didAddAcceleratedImageBuffer()
{
++acceleratedImageBufferGlobalCount;
++m_acceleratedImageBufferCount;
}

void RemoteSharedResourceCache::didTakeAcceleratedImageBuffer()
{
--acceleratedImageBufferGlobalCount;
--m_acceleratedImageBufferCount;
}

WebCore::RenderingMode RemoteSharedResourceCache::adjustAcceleratedImageBufferRenderingMode(RenderingPurpose renderingPurpose) const
{
// These are naturally racy, but the limits are heuristic in nature.
if (acceleratedImageBufferGlobalCount >= acceleratedImageBufferGlobalLimit)
return RenderingMode::Unaccelerated;
if (m_acceleratedImageBufferCount >= acceleratedImageBufferLimit)
return RenderingMode::Unaccelerated;
if (renderingPurpose == RenderingPurpose::Canvas && m_acceleratedImageBufferCount >= acceleratedImageBuffer2DContextLimit)
return RenderingMode::Unaccelerated;
return RenderingMode::Accelerated;
}

} // namespace WebKit

#endif // ENABLE(GPU_PROCESS)
9 changes: 7 additions & 2 deletions Source/WebKit/GPUProcess/RemoteSharedResourceCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@
#include "ThreadSafeObjectHeap.h"
#include <WebCore/ImageBuffer.h>
#include <WebCore/RenderingResourceIdentifier.h>
#include <atomic>
#include <wtf/FastMalloc.h>
#include <wtf/Ref.h>
#include <wtf/ThreadSafeRefCounted.h>

namespace WebKit {

// Class holding GPU process resources per Web Process.
// Class holding GPU process resources per WebContent process.
// Thread-safe.
class RemoteSharedResourceCache final : public ThreadSafeRefCounted<RemoteSharedResourceCache>, IPC::MessageReceiver {
WTF_MAKE_FAST_ALLOCATED;
Expand All @@ -49,6 +50,10 @@ class RemoteSharedResourceCache final : public ThreadSafeRefCounted<RemoteShared
void addSerializedImageBuffer(WebCore::RenderingResourceIdentifier, Ref<WebCore::ImageBuffer>);
RefPtr<WebCore::ImageBuffer> takeSerializedImageBuffer(WebCore::RenderingResourceIdentifier);

void didAddAcceleratedImageBuffer();
void didTakeAcceleratedImageBuffer();
WebCore::RenderingMode adjustAcceleratedImageBufferRenderingMode(WebCore::RenderingPurpose) const;

// IPC::MessageReceiver
void didReceiveMessage(IPC::Connection&, IPC::Decoder&) final;

Expand All @@ -59,9 +64,9 @@ class RemoteSharedResourceCache final : public ThreadSafeRefCounted<RemoteShared
void releaseSerializedImageBuffer(WebCore::RenderingResourceIdentifier);

IPC::ThreadSafeObjectHeap<RemoteSerializedImageBufferIdentifier, RefPtr<WebCore::ImageBuffer>> m_serializedImageBuffers;
std::atomic<size_t> m_acceleratedImageBufferCount { 0 };
};


} // namespace WebKit

#endif // ENABLE(GPU_PROCESS)
14 changes: 11 additions & 3 deletions Source/WebKit/GPUProcess/graphics/RemoteRenderingBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ void RemoteRenderingBackend::didFailCreateImageBuffer(RenderingResourceIdentifie

void RemoteRenderingBackend::didCreateImageBuffer(Ref<ImageBuffer> imageBuffer)
{
if (imageBuffer->renderingMode() == RenderingMode::Accelerated)
m_sharedResourceCache->didAddAcceleratedImageBuffer();

auto imageBufferIdentifier = imageBuffer->renderingResourceIdentifier();
auto* sharing = imageBuffer->toBackendSharing();
auto handle = downcast<ImageBufferBackendHandleSharing>(*sharing).createBackendHandle();
Expand Down Expand Up @@ -284,6 +287,8 @@ RefPtr<ImageBuffer> RemoteRenderingBackend::allocateImageBuffer(const FloatSize&
#endif

RefPtr<ImageBuffer> imageBuffer;
if (renderingMode == RenderingMode::Accelerated)
renderingMode = m_sharedResourceCache->adjustAcceleratedImageBufferRenderingMode(purpose);

#if ENABLE(RE_DYNAMIC_CONTENT_SCALING)
if (m_gpuConnectionToWebProcess->isDynamicContentScalingEnabled() && (purpose == RenderingPurpose::LayerBacking || purpose == RenderingPurpose::DOM))
Expand Down Expand Up @@ -314,8 +319,8 @@ void RemoteRenderingBackend::releaseImageBuffer(RenderingResourceIdentifier rend
{
assertIsCurrent(workQueue());
m_remoteDisplayLists.take(renderingResourceIdentifier);
bool success = m_remoteImageBuffers.take(renderingResourceIdentifier).get();
MESSAGE_CHECK(success, "Resource is being released before being cached."_s);
auto imageBuffer = takeImageBuffer(renderingResourceIdentifier);
MESSAGE_CHECK(imageBuffer, "Resource is being released before being cached."_s);
}

void RemoteRenderingBackend::createRemoteImageBufferSet(RemoteImageBufferSetIdentifier bufferSetIdentifier, WebCore::RenderingResourceIdentifier displayListIdentifier)
Expand Down Expand Up @@ -599,7 +604,10 @@ RefPtr<ImageBuffer> RemoteRenderingBackend::takeImageBuffer(RenderingResourceIde
RefPtr remoteImageBuffer = remoteImageBufferReceiveQueue.get();
remoteImageBufferReceiveQueue.reset();
ASSERT(remoteImageBuffer->hasOneRef());
return remoteImageBuffer->imageBuffer();
Ref imageBuffer = remoteImageBuffer->imageBuffer();
if (imageBuffer->renderingMode() == RenderingMode::Accelerated)
m_sharedResourceCache->didTakeAcceleratedImageBuffer();
return imageBuffer;
}

void RemoteRenderingBackend::terminateWebProcess(ASCIILiteral message)
Expand Down

0 comments on commit 78038e7

Please sign in to comment.