From a45bd6ffad36609231d6aa00b1cc37cf6396f6c4 Mon Sep 17 00:00:00 2001 From: Tyagiquamar Date: Thu, 1 Oct 2026 00:39:00 +0530 Subject: [PATCH] Fix descriptor pool recycling race --- CMakeLists.txt | 6 ++++ src/vsg/vk/DescriptorPool.cpp | 13 +++---- tests/CMakeLists.txt | 4 +++ tests/DescriptorPoolRecycle.cpp | 63 +++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 8 deletions(-) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/DescriptorPoolRecycle.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 78dd44c85..72d505f6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -242,4 +242,10 @@ vsg_add_option_maintainer( # add_subdirectory(src/vsg) +option(VSG_BUILD_TESTS "Build VulkanSceneGraph tests" OFF) +if (VSG_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +endif() + vsg_add_feature_summary() diff --git a/src/vsg/vk/DescriptorPool.cpp b/src/vsg/vk/DescriptorPool.cpp index f3062dc8a..622b6cab3 100644 --- a/src/vsg/vk/DescriptorPool.cpp +++ b/src/vsg/vk/DescriptorPool.cpp @@ -138,14 +138,11 @@ ref_ptr DescriptorPool::allocateDescriptorSet(Des void DescriptorPool::freeDescriptorSet(ref_ptr dsi) { - { - // swap ownership so that DescriptorSet::Implementation' reference is reset to null and while this DescriptorPool takes a reference to it. - // acquire lock within local scope so that subsequent dsi->_descriptorPool = {} call doesn't unref and (possibly) delete this DescriptorPool while lock still held. - std::scoped_lock lock(mutex); - _recyclingList.push_back(dsi); - ++_availableDescriptorSet; - accumulateBindings(_recycledDescriptorPoolSizes, dsi->_descriptorSetLayout, +1); - } + ref_ptr keepAlive(this); + std::scoped_lock lock(mutex); + _recyclingList.push_back(dsi); + ++_availableDescriptorSet; + accumulateBindings(_recycledDescriptorPoolSizes, dsi->_descriptorSetLayout, +1); dsi->_descriptorPool = {}; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 000000000..4c57eef42 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(vsg_descriptor_pool_recycle DescriptorPoolRecycle.cpp) +target_link_libraries(vsg_descriptor_pool_recycle PRIVATE vsg::vsg) +add_test(NAME vsg_descriptor_pool_recycle COMMAND vsg_descriptor_pool_recycle) +set_tests_properties(vsg_descriptor_pool_recycle PROPERTIES SKIP_RETURN_CODE 77) diff --git a/tests/DescriptorPoolRecycle.cpp b/tests/DescriptorPoolRecycle.cpp new file mode 100644 index 000000000..853e9be68 --- /dev/null +++ b/tests/DescriptorPoolRecycle.cpp @@ -0,0 +1,63 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +int main() +{ + auto instance = vsg::Instance::create(vsg::Names{}, vsg::Names{}); + auto physicalDevice = instance->getPhysicalDevice(VK_QUEUE_GRAPHICS_BIT); + if (!physicalDevice) + { + std::cerr << "No Vulkan graphics device is available\n"; + return 77; + } + + int queueFamily = physicalDevice->getQueueFamily(VK_QUEUE_GRAPHICS_BIT); + auto device = vsg::Device::create(physicalDevice, vsg::QueueSettings{{queueFamily, {1.0f}}}, vsg::Names{}, vsg::Names{}); + auto context = vsg::Context::create(device); + auto layout = vsg::DescriptorSetLayout::create(); + layout->addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); + layout->compile(*context); + + auto pool = vsg::DescriptorPool::create(device, 1, vsg::DescriptorPoolSizes{{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1}}); + auto current = pool->allocateDescriptorSet(layout); + if (!current) + { + std::cerr << "Initial descriptor set allocation failed\n"; + return 1; + } + + for (int iteration = 0; iteration < 5000; ++iteration) + { + auto previous = std::move(current); + std::atomic recyclingStarted{false}; + + std::thread allocationThread([&] { + while (!recyclingStarted.load(std::memory_order_acquire)) std::this_thread::yield(); + auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2); + while (!(current = pool->allocateDescriptorSet(layout)) && std::chrono::steady_clock::now() < deadline) + std::this_thread::yield(); + }); + + std::thread recyclingThread([&] { + recyclingStarted.store(true, std::memory_order_release); + vsg::DescriptorSet::Implementation::recycle(previous); + }); + + recyclingThread.join(); + allocationThread.join(); + if (!current) + { + std::cerr << "Descriptor set reuse failed at iteration " << iteration << '\n'; + return 1; + } + } + + return 0; +}