Commit 745999a9 by Shahbaz Youssefi Committed by Commit Bot

Add test for multithreaded shared-context resource

The test does this: 1. Context 1: Read Texture 1 and draw into Framebuffer 1 2. Context 2: Read Texture 1 and draw into Framebuffer 2 3. Context 1: Delete Framebuffer 1 4. Context 1: Flush 5. Context 2: Modify Texture 1 Issue is Texture 1's mCurrentReadingNodes contains one node from each context's command graph, one of which is deleted at step 4. At step 5, a dependency is added from both nodes (one already deleted) to a new node, causing use-after-free. Bug: angleproject:4130 Change-Id: I06720aec20d0b49114937f1cd9b193a4f1df9d8d Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1924790Reviewed-by: 's avatarGeoff Lang <geofflang@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
parent 47a1fda2
......@@ -339,7 +339,7 @@ class SharedResourceUse final : angle::NonCopyable
return mUse->serial;
}
// The base counter value for an live resource is "1". Any value greater than one indicates
// The base counter value for a live resource is "1". Any value greater than one indicates
// the resource is in use by a vk::CommandGraph.
ANGLE_INLINE bool isCurrentlyInGraph() const
{
......
......@@ -13,6 +13,10 @@
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include <condition_variable>
#include <mutex>
#include <thread>
using namespace angle;
namespace
......@@ -364,6 +368,220 @@ TEST_P(EGLContextSharingTest, SamplerLifetime)
// Bind the main context to clean up the test.
ASSERT_EGL_TRUE(eglMakeCurrent(display, surface, surface, mainContext));
}
// Test that deleting an object reading from a shared object in one context doesn't cause the other
// context to crash. Mostly focused on the Vulkan back-end where we track resource dependencies in
// a graph.
TEST_P(EGLContextSharingTest, DeleteReaderOfSharedTexture)
{
ANGLE_SKIP_TEST_IF(!platformSupportsMultithreading());
// Bug in the Vulkan backend. http://anglebug.com/4130
ANGLE_SKIP_TEST_IF(IsVulkan());
// Initialize contexts
EGLWindow *window = getEGLWindow();
EGLDisplay dpy = window->getDisplay();
EGLConfig config = window->getConfig();
constexpr size_t kThreadCount = 2;
EGLSurface surface[kThreadCount] = {EGL_NO_SURFACE, EGL_NO_SURFACE};
EGLContext ctx[kThreadCount] = {EGL_NO_CONTEXT, EGL_NO_CONTEXT};
EGLint pbufferAttributes[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE, EGL_NONE};
for (size_t t = 0; t < kThreadCount; ++t)
{
surface[t] = eglCreatePbufferSurface(dpy, config, pbufferAttributes);
EXPECT_EGL_SUCCESS();
ctx[t] = window->createContext(t == 0 ? EGL_NO_CONTEXT : ctx[0]);
EXPECT_NE(EGL_NO_CONTEXT, ctx[t]);
}
// Initialize test resources. They are done outside the threads to reduce the sources of
// errors and thus deadlock-free teardown.
ASSERT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
// Shared texture to read from.
constexpr GLsizei kTexSize = 1;
const GLColor kTexData = GLColor::red;
GLTexture sharedTex;
glBindTexture(GL_TEXTURE_2D, sharedTex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
&kTexData);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// Resources for each context.
GLRenderbuffer renderbuffer[kThreadCount];
GLFramebuffer fbo[kThreadCount];
GLProgram program[kThreadCount];
for (size_t t = 0; t < kThreadCount; ++t)
{
ASSERT_EGL_TRUE(eglMakeCurrent(dpy, surface[t], surface[t], ctx[t]));
glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer[t]);
constexpr int kRenderbufferSize = 4;
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kRenderbufferSize, kRenderbufferSize);
glBindFramebuffer(GL_FRAMEBUFFER, fbo[t]);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,
renderbuffer[t]);
glBindTexture(GL_TEXTURE_2D, sharedTex);
program[t].makeRaster(essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
ASSERT_TRUE(program[t].valid());
}
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
// Synchronization tools to ensure the two threads are interleaved as designed by this test.
std::mutex mutex;
std::condition_variable condVar;
enum class Step
{
Start,
Thread0Draw,
Thread1Draw,
Thread0Delete,
Finish,
Abort,
};
Step currentStep = Step::Start;
// Helper functions to synchronize the threads so that the operations are executed in the
// specific order the test is written for.
auto waitForStep = [&](Step waitStep) -> bool {
std::unique_lock<std::mutex> lock(mutex);
while (currentStep != waitStep)
{
// If necessary, abort execution as the other thread has encountered a GL error.
if (currentStep == Step::Abort)
{
return false;
}
condVar.wait(lock);
}
return true;
};
auto nextStep = [&](Step newStep) {
{
std::unique_lock<std::mutex> lock(mutex);
currentStep = newStep;
}
condVar.notify_one();
};
class AbortOnFailure
{
public:
AbortOnFailure(Step *currentStep, std::mutex *mutex, std::condition_variable *condVar)
: mCurrentStep(currentStep), mMutex(mutex), mCondVar(condVar)
{}
~AbortOnFailure()
{
bool isAborting = false;
{
std::unique_lock<std::mutex> lock(*mMutex);
isAborting = *mCurrentStep != Step::Finish;
if (isAborting)
{
*mCurrentStep = Step::Abort;
}
}
mCondVar->notify_all();
}
private:
Step *mCurrentStep;
std::mutex *mMutex;
std::condition_variable *mCondVar;
};
std::thread deletingThread = std::thread([&]() {
AbortOnFailure abortOnFailure(&currentStep, &mutex, &condVar);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[0], surface[0], ctx[0]));
EXPECT_EGL_SUCCESS();
ASSERT_TRUE(waitForStep(Step::Start));
// Draw using the shared texture.
drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
// Wait for the other thread to also draw using the shared texture.
nextStep(Step::Thread0Draw);
ASSERT_TRUE(waitForStep(Step::Thread1Draw));
// Delete this thread's framebuffer (reader of the shared texture).
fbo[0].reset();
// Flush to make sure the graph nodes associated with this context are deleted.
glFlush();
// Wait for the other thread to use the shared texture again before unbinding the
// context (so no implicit flush happens).
nextStep(Step::Thread0Delete);
ASSERT_TRUE(waitForStep(Step::Finish));
EXPECT_GL_NO_ERROR();
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_SUCCESS();
});
std::thread continuingThread = std::thread([&]() {
AbortOnFailure abortOnFailure(&currentStep, &mutex, &condVar);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface[1], surface[1], ctx[1]));
EXPECT_EGL_SUCCESS();
// Wait for first thread to draw using the shared texture.
ASSERT_TRUE(waitForStep(Step::Thread0Draw));
// Draw using the shared texture.
drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
// Wait for the other thread to delete its framebuffer.
nextStep(Step::Thread1Draw);
ASSERT_TRUE(waitForStep(Step::Thread0Delete));
// Write to the shared texture differently, so a dependency is created from the previous
// readers of the shared texture (the two framebuffers of the two threads) to the new
// commands being recorded for the shared texture.
//
// If the backend attempts to create a dependency from nodes associated with the
// previous readers of the texture to the new node that will contain the following
// commands, there will be a use-after-free error.
const GLColor kTexData2 = GLColor::green;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexSize, kTexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
&kTexData2);
drawQuad(program[0].get(), essl1_shaders::PositionAttrib(), 0.5f);
nextStep(Step::Finish);
EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
EXPECT_EGL_SUCCESS();
});
deletingThread.join();
continuingThread.join();
ASSERT_NE(currentStep, Step::Abort);
// Clean up
for (size_t t = 0; t < kThreadCount; ++t)
{
eglDestroySurface(dpy, surface[t]);
eglDestroyContext(dpy, ctx[t]);
}
}
} // anonymous namespace
ANGLE_INSTANTIATE_TEST(EGLContextSharingTest,
......
......@@ -29,11 +29,6 @@ class MultithreadingTest : public ANGLETest
setConfigAlphaBits(8);
}
bool platformSupportsMultithreading() const
{
return (IsOpenGLES() && IsAndroid()) || IsVulkan();
}
void runMultithreadedGLTest(
std::function<void(EGLSurface surface, size_t threadIndex)> testBody,
size_t threadCount)
......@@ -51,7 +46,7 @@ class MultithreadingTest : public ANGLETest
{
threads[threadIdx] = std::thread([&, threadIdx]() {
EGLSurface surface = EGL_NO_SURFACE;
EGLConfig ctx = EGL_NO_CONTEXT;
EGLContext ctx = EGL_NO_CONTEXT;
{
std::lock_guard<decltype(mutex)> lock(mutex);
......
......@@ -991,6 +991,11 @@ void ANGLETestBase::draw3DTexturedQuad(GLfloat positionAttribZ,
}
}
bool ANGLETestBase::platformSupportsMultithreading() const
{
return (IsOpenGLES() && IsAndroid()) || IsVulkan();
}
void ANGLETestBase::checkD3D11SDKLayersMessages()
{
#if defined(ANGLE_PLATFORM_WINDOWS)
......
......@@ -476,6 +476,8 @@ class ANGLETestBase
mCurrentParams->getDeviceType() == EGL_PLATFORM_ANGLE_DEVICE_TYPE_SWIFTSHADER_ANGLE;
}
bool platformSupportsMultithreading() const;
private:
void checkD3D11SDKLayersMessages();
......
......@@ -439,7 +439,7 @@ EGLContext EGLWindow::createContext(EGLContext share) const
}
contextAttributes.push_back(EGL_NONE);
EGLContext context = eglCreateContext(mDisplay, mConfig, nullptr, &contextAttributes[0]);
EGLContext context = eglCreateContext(mDisplay, mConfig, share, &contextAttributes[0]);
if (eglGetError() != EGL_SUCCESS)
{
std::cerr << "Error on eglCreateContext.\n";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment