Branch
Hash :
99c47333
Author :
Date :
2025-10-15T13:21:19
[metal] Fix data race in commands scheduled sync The recently added commands scheduled sync object adds a callback to the command queue to be invoked when the scheduled handler for the committed command buffer runs. This callback could be called after the sync object is destoyed, so make sure it doesn't reference the original sync object, but only a thread-safe ref-counted state object. Bug: chromium:444702048 Change-Id: Ifaef54eae5dfdb0b6eb6b767120947e66a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/7046662 Reviewed-by: Geoff Lang <geofflang@chromium.org> Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org> Commit-Queue: Geoff Lang <geofflang@chromium.org>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
// Copyright 2025 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// EGLSyncTestCommandsScheduled:
// Tests pertaining to EGL_ANGLE_metal_commands_scheduled_sync extension.
//
#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#include <gtest/gtest.h>
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include <Metal/Metal.h>
#include <thread>
using namespace angle;
class EGLSyncTestMetalCommandsScheduled : public ANGLETest<>
{
protected:
bool hasCommandsScheduledSyncExtension() const
{
return IsEGLDisplayExtensionEnabled(getEGLWindow()->getDisplay(),
"EGL_ANGLE_metal_commands_scheduled_sync");
}
void waitForCommandsScheduled(EGLDisplay display, EGLSync sync)
{
// Don't wait forever to make sure the test terminates
constexpr GLuint64 kTimeout = 1000'000'000ul; // 1 second
EXPECT_EQ(EGL_CONDITION_SATISFIED,
eglClientWaitSync(display, sync, EGL_SYNC_FLUSH_COMMANDS_BIT, kTimeout));
EGLAttrib value = 0;
EXPECT_EGL_TRUE(eglGetSyncAttrib(display, sync, EGL_SYNC_STATUS, &value));
EXPECT_EQ(value, EGL_SIGNALED);
}
void recordCommands()
{
// Create work to do
ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
}
};
// Test that a sync object is signaled after commands are scheduled.
TEST_P(EGLSyncTestMetalCommandsScheduled, SingleThread)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
recordCommands();
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC);
waitForCommandsScheduled(display, sync);
EXPECT_EGL_TRUE(eglDestroySync(display, sync));
}
// Test that a sync object can be waited on from another thread.
TEST_P(EGLSyncTestMetalCommandsScheduled, MultiThread)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
recordCommands();
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC);
std::thread thread([&]() { waitForCommandsScheduled(display, sync); });
thread.join();
EXPECT_EGL_TRUE(eglDestroySync(display, sync));
}
// Test that a sync object can be waited on after the work is done.
TEST_P(EGLSyncTestMetalCommandsScheduled, AfterFinish)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
recordCommands();
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC);
glFinish();
waitForCommandsScheduled(display, sync);
EXPECT_EGL_TRUE(eglDestroySync(display, sync));
}
// Test that a sync object is signaled even if no work is done.
TEST_P(EGLSyncTestMetalCommandsScheduled, NoWork)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync, EGL_NO_SYNC);
waitForCommandsScheduled(display, sync);
EXPECT_EGL_TRUE(eglDestroySync(display, sync));
}
// Test that there's no crash if a sync object is destroyed before its command buffer is scheduled.
TEST_P(EGLSyncTestMetalCommandsScheduled, DestroyBeforeScheduled)
{
ANGLE_SKIP_TEST_IF(!hasCommandsScheduledSyncExtension());
recordCommands();
EGLDisplay display = getEGLWindow()->getDisplay();
EGLSync sync1 = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync1, EGL_NO_SYNC);
// Create and destroy another sync object immediately - it will add a callback to the commands
// scheduled handler, but the callback shouldn't crash even after the sync object is destroyed.
EGLSync sync2 = eglCreateSync(display, EGL_SYNC_METAL_COMMANDS_SCHEDULED_ANGLE, nullptr);
EXPECT_NE(sync2, EGL_NO_SYNC);
EXPECT_EGL_TRUE(eglDestroySync(display, sync2));
waitForCommandsScheduled(display, sync1);
EXPECT_EGL_TRUE(eglDestroySync(display, sync1));
}
ANGLE_INSTANTIATE_TEST(EGLSyncTestMetalCommandsScheduled, ES2_METAL(), ES3_METAL());
// This test suite is not instantiated on non-Metal backends and OSes.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(EGLSyncTestMetalCommandsScheduled);