Skip to content
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

Fix locks in TextureLoader class #1147

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 62 additions & 19 deletions src/w3d/renderer/textureloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,18 @@ w3dtexture_t Load_Compressed_Texture(
void LoaderThreadClass::Thread_Function()
{
while (m_isRunning) {
if (!g_backgroundQueue.Empty()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock for g_backgroundQueue is missing here on read.

bool backgroundQueueEmpty;
{
FastCriticalSectionClass::LockClass background_lock(g_backgroundCritSec);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally takes both locks at the same time. This can be risky and introduce ABA deadlock problem. This can be avoided by limiting scope.

Could perhaps deadlock with TextureLoader::Request_Foreground_Loading.

TextureLoadTaskClass *task = g_backgroundQueue.Pop_Front();
backgroundQueueEmpty = g_backgroundQueue.Empty();
}

if (!backgroundQueueEmpty) {
TextureLoadTaskClass *task;
{
FastCriticalSectionClass::LockClass background_lock(g_backgroundCritSec);
task = g_backgroundQueue.Pop_Front();
}
if (task) {
task->Load();
FastCriticalSectionClass::LockClass foreground_lock(g_foregroundCritSec);
Expand Down Expand Up @@ -282,21 +291,24 @@ w3dsurface_t TextureLoader::Load_Surface_Immediate(const StringClass &texture, W
*/
void TextureLoader::Request_Thumbnail(TextureBaseClass *texture)
{
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock scope can be limited. Optimization.

if (texture->Peek_Platform_Base_Texture() == W3D_TYPE_INVALID_TEXTURE) {
if (TextureLoader::Is_DX8_Thread()) {
Load_Thumbnail(texture);
// This seems at odds with the Renegade code where the load tasks are the other way around.
// Possibly this code isn't ever actually used with Thumbnail code in general being minimally
// called.
if (texture->Get_Thumbnail_Load_Task() != nullptr) {
g_foregroundQueue.Remove(texture->Get_Thumbnail_Load_Task());
{
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
g_foregroundQueue.Remove(texture->Get_Thumbnail_Load_Task());
}
texture->Get_Thumbnail_Load_Task()->Destroy();
}
} else {
if (texture->Get_Thumbnail_Load_Task() == nullptr) {
if (texture->Get_Normal_Load_Task() == nullptr
|| texture->Get_Normal_Load_Task()->Get_Load_State() == TextureLoadTaskClass::STATE_NONE) {
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
g_foregroundQueue.Push_Back(TextureLoadTaskClass::Create(
texture, TextureLoadTaskClass::TASK_THUMBNAIL, TextureLoadTaskClass::PRIORITY_BACKGROUND));
}
Expand All @@ -312,15 +324,14 @@ void TextureLoader::Request_Thumbnail(TextureBaseClass *texture)
*/
void TextureLoader::Request_Background_Loading(TextureBaseClass *texture)
{
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock scope can be limited. Optimization.


if (!texture->Is_Initialized() && texture->Get_Normal_Load_Task() == nullptr) {
TextureLoadTaskClass *task = TextureLoadTaskClass::Create(
texture, TextureLoadTaskClass::TASK_LOAD, TextureLoadTaskClass::PRIORITY_BACKGROUND);

if (TextureLoader::Is_DX8_Thread()) {
TextureLoader::Begin_Load_And_Queue(task);
} else {
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
g_foregroundQueue.Push_Back(task);
}
}
Expand All @@ -334,7 +345,6 @@ void TextureLoader::Request_Background_Loading(TextureBaseClass *texture)
void TextureLoader::Request_Foreground_Loading(TextureBaseClass *texture)
{
captainslog_assert(texture != nullptr);
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);

if (texture->Is_Initialized()) {
return;
Expand All @@ -344,32 +354,47 @@ void TextureLoader::Request_Foreground_Loading(TextureBaseClass *texture)
TextureLoadTaskClass *thumb_task = texture->Get_Thumbnail_Load_Task();

if (!TextureLoader::Is_DX8_Thread()) {
FastCriticalSectionClass::LockClass lock2(g_backgroundCritSec);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Originally takes both locks at the same time. This can be risky and introduce ABA deadlock problem. This can be avoided by limiting scope.

Could perhaps deadlock with LoaderThreadClass::Thread_Function

if (thumb_task != nullptr) {
thumb_task->Set_Load_State(TextureLoadTaskClass::STATE_FOREGROUND_OVERRIDE);
}

if (task != nullptr) {
if (task->Get_Parent() == &g_backgroundQueue) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not need locking. g_backgroundQueue address never changes.

g_backgroundQueue.Remove(task);
g_foregroundQueue.Push_Back(task);
{
FastCriticalSectionClass::LockClass lock(g_backgroundCritSec);
g_backgroundQueue.Remove(task);
}
{
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
g_foregroundQueue.Push_Back(task);
}
}

task->Set_Priority(TextureLoadTaskClass::PRIORITY_FOREGROUND);
} else {
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
g_foregroundQueue.Push_Back(TextureLoadTaskClass::Create(
texture, TextureLoadTaskClass::TASK_LOAD, TextureLoadTaskClass::PRIORITY_FOREGROUND));
}
} else {
if (thumb_task != nullptr) {
g_foregroundQueue.Remove(thumb_task);
{
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
g_foregroundQueue.Remove(thumb_task);
}
thumb_task->Destroy();
}

if (task != nullptr) {
FastCriticalSectionClass::LockClass lock2(g_backgroundCritSec);
g_foregroundQueue.Remove(task);
g_backgroundQueue.Remove(task);
{
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
g_foregroundQueue.Remove(task);
}
{
FastCriticalSectionClass::LockClass lock(g_backgroundCritSec);
g_backgroundQueue.Remove(task);
}
} else {
task = TextureLoadTaskClass::Create(
texture, TextureLoadTaskClass::TASK_LOAD, TextureLoadTaskClass::PRIORITY_FOREGROUND);
Expand All @@ -385,9 +410,17 @@ void TextureLoader::Request_Foreground_Loading(TextureBaseClass *texture)
*/
bool TextureLoader::Queues_Not_Empty()
{
FastCriticalSectionClass::LockClass lock(g_backgroundCritSec);

return !g_backgroundQueue.Empty() && !g_foregroundQueue.Empty();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock for g_foregroundQueue is missing here on read.

bool backgroundQueueEmpty;
bool foregroundQueueEmpty;
{
FastCriticalSectionClass::LockClass lock(g_backgroundCritSec);
backgroundQueueEmpty = g_backgroundQueue.Empty();
}
{
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
foregroundQueueEmpty = g_foregroundQueue.Empty();
}
return !backgroundQueueEmpty && !foregroundQueueEmpty;
}

bool TextureLoader::Is_Format_Compressed(WW3DFormat format, bool allow_compressed)
Expand Down Expand Up @@ -424,11 +457,19 @@ void TextureLoader::Flush_Pending_Load_Tasks()
void TextureLoader::Update(void (*update)(void))
{
if (!s_textureLoadSuspended) {
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);

int time = rts::Get_Time();
TextureLoadTaskClass *task;

while ((task = g_foregroundQueue.Pop_Front()) != nullptr) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock exists before already, but scope can be limited. Optimization.

while (true) {
{
FastCriticalSectionClass::LockClass lock(g_foregroundCritSec);
task = g_foregroundQueue.Pop_Front();
}
if (task == nullptr) {
break;
}

if (update != nullptr) {
int time2 = rts::Get_Time();

Expand Down Expand Up @@ -457,7 +498,8 @@ void TextureLoader::Process_Foreground_Thumbnail(TextureLoadTaskClass *task)
switch (task->Get_Load_State()) {
case TextureLoadTaskClass::STATE_NONE:
Load_Thumbnail(task->Get_Texture());
case TextureLoadTaskClass::STATE_FOREGROUND_OVERRIDE: // Fallthrough
[[fallthrough]];
case TextureLoadTaskClass::STATE_FOREGROUND_OVERRIDE:
task->Destroy();
break;
default:
Expand Down Expand Up @@ -503,6 +545,7 @@ void TextureLoader::Begin_Load_And_Queue(TextureLoadTaskClass *task)

// If we can't start the load of either a dds or tga for the filename in the task set it to missing.
if (task->Begin_Load()) {
FastCriticalSectionClass::LockClass lock(g_backgroundCritSec);
g_backgroundQueue.Push_Front(task);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lock is missing here on write. Could race.

} else {
task->Apply_Missing_Texture();
Expand Down
Loading