-
-
Notifications
You must be signed in to change notification settings - Fork 22.2k
Renderer: Reduce scope of mutex locks to prevent common deadlocks #105138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few notes
@@ -182,6 +182,7 @@ void WorkerThreadPool::_process_task(Task *p_task) { | |||
|
|||
void WorkerThreadPool::_thread_function(void *p_user) { | |||
ThreadData *thread_data = (ThreadData *)p_user; | |||
Thread::set_name(vformat("WorkerThread %d", thread_data->index)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make it easy to find the worker threads
scene/resources/material.cpp
Outdated
if (shader_map.has(current_key)) { | ||
shader_map[current_key].users--; | ||
if (shader_map[current_key].users == 0) { | ||
// Deallocate shader which is no longer in use. | ||
RS::get_singleton()->free(shader_map[current_key].shader); | ||
shader_map.erase(current_key); | ||
{ | ||
MutexLock lock(shader_map_mutex); | ||
if (ShaderData *v = shader_map.getptr(current_key); v) { | ||
v->users--; | ||
if (v->users == 0) { | ||
// Deallocate shader which is no longer in use. | ||
RS::get_singleton()->free(v->shader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An improvement, as we no longer perform multiple lookups into shader_map
too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great pattern that is worth using more widely. Calling has()
is almost always a waste of cycles
scene/resources/material.cpp
Outdated
if (shader_map.has(mk)) { | ||
shader_rid = shader_map[mk].shader; | ||
shader_map[mk].users++; | ||
if (ShaderData *v = shader_map.getptr(mk); v) { | ||
shader_rid = v->shader; | ||
v->users++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here – let's not perform multiple lookups into shader_map.
scene/resources/material.cpp
Outdated
if (ShaderData *v = shader_map.getptr(mk); v) { | ||
// We raced and managed to sneak the same key in concurrently, so we'll destroy the one we just created, | ||
// given we know it isn't used, and use the winner. | ||
RS::get_singleton()->free(shader_data.shader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@clayjohn I had to remove this, as it caused a crash in the List
destructor of this list, as it wasn't empty:
godot/servers/rendering/renderer_rd/forward_clustered/scene_shader_forward_clustered.h
Line 291 in 324512e
SelfList<ShaderData>::List shader_list; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense
SelfList<BaseMaterial3D>::List copy; | ||
{ | ||
MutexLock lock(material_mutex); | ||
while (SelfList<BaseMaterial3D> *E = dirty_materials.first()) { | ||
dirty_materials.remove(E); | ||
copy.add(E); | ||
} | ||
} | ||
|
||
while (dirty_materials.first()) { | ||
dirty_materials.first()->self()->_update_shader(); | ||
dirty_materials.first()->remove_from_list(); | ||
while (SelfList<BaseMaterial3D> *E = copy.first()) { | ||
E->self()->_update_shader(); | ||
copy.remove(E); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't allocate anything, which is convenient! We just remove from the dirty_materials
list into the local copy
.
Mutex variant_set_mutex; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer needed, as we have a mutex per version.
@@ -95,7 +94,9 @@ class ShaderRD { | |||
void _compile_ensure_finished(Version *p_version); | |||
void _allocate_placeholders(Version *p_version, int p_group); | |||
|
|||
RID_Owner<Version> version_owner; | |||
RID_Owner<Version, true> version_owner; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this thread-safe now
SelfList<Material>::List copy; | ||
{ | ||
MutexLock lock(material_update_list_mutex); | ||
while (SelfList<Material> *E = material_update_list.first()) { | ||
DEV_ASSERT(E == &E->self()->update_element); | ||
material_update_list.remove(E); | ||
copy.add(E); | ||
} | ||
} | ||
|
||
while (SelfList<Material> *E = copy.first()) { | ||
Material *material = E->self(); | ||
copy.remove(E); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change was necessary to limit the scope of the lock, as update_parameters
could cause shader compilation.
@clayjohn I've switched to the preferred style |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
Thanks! |
Fixes #102877
Introduces fine-grained locking to
ShaderRD
, so that each version has its own lock, makingShaderRD
version APIs thread safe.Note
It is expected that one of the
initialize
APIs are called prior to querying version, which is a requirement. Theinitialize
APIs mutate some of the state, after which point it is read-only.Additional changes to
BaseMaterial3D
andMaterialStorage
to extract the queued updates into a separate list (non-allocating) and then releases the lock. This is to ensure that any shader updates are not performed under the shared locks ofBaseMaterial3D
andMaterialStorage
.Now that accessing shader versions of
ShaderRD
is thread-safe, we remove a number of calls to the shared mutex inSceneShaderForwardClustered
when querying shader versions, which was a source of many deadlocks.ShaderRD
no longer uses a separate namedWorkerThreadPool
.Note
We may want to introduce a platform-specific
Thread
implementation for Apple, so that we can increase the size of the stack for worker threads.