Skip to content

Commit

Permalink
Use CLock instead of std::mutex, add thread-safety annotations
Browse files Browse the repository at this point in the history
Use `CLock` and `CLockScope` instead of `std::mutex` and add clang thread-safety analysis annotations everywhere except for usages in engine graphics and video, as those usages also involve `std::condition_variable`.

Fix lock not being unlocked on all code paths in `CFutureLogger::Log`, which is caught by the static analysis.
  • Loading branch information
Robyt3 committed Nov 10, 2023
1 parent 3d858c2 commit 1047004
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 87 deletions.
29 changes: 12 additions & 17 deletions src/base/log.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ class CWindowsConsoleLogger : public ILogger
HANDLE m_pConsole;
int m_BackgroundColor;
int m_ForegroundColor;
std::mutex m_OutputLock;
CLock m_OutputLock;
bool m_Finished = false;

public:
Expand All @@ -336,7 +336,7 @@ class CWindowsConsoleLogger : public ILogger
m_ForegroundColor = FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;
}
}
void Log(const CLogMessage *pMessage) override
void Log(const CLogMessage *pMessage) override REQUIRES(!m_OutputLock)
{
if(m_Filter.Filters(pMessage))
{
Expand All @@ -356,44 +356,41 @@ class CWindowsConsoleLogger : public ILogger
else
Color |= FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY;

m_OutputLock.lock();
const CLockScope LockScope(m_OutputLock);
if(!m_Finished)
{
SetConsoleTextAttribute(m_pConsole, Color);
WriteConsoleW(m_pConsole, WideMessage.c_str(), WideMessage.length(), NULL, NULL);
}
m_OutputLock.unlock();
}
void GlobalFinish() override
void GlobalFinish() override REQUIRES(!m_OutputLock)
{
// Restore original color
m_OutputLock.lock();
const CLockScope LockScope(m_OutputLock);
SetConsoleTextAttribute(m_pConsole, m_BackgroundColor | m_ForegroundColor);
m_Finished = true;
m_OutputLock.unlock();
}
};
class CWindowsFileLogger : public ILogger
{
HANDLE m_pFile;
std::mutex m_OutputLock;
CLock m_OutputLock;

public:
CWindowsFileLogger(HANDLE pFile) :
m_pFile(pFile)
{
}
void Log(const CLogMessage *pMessage) override
void Log(const CLogMessage *pMessage) override REQUIRES(!m_OutputLock)
{
if(m_Filter.Filters(pMessage))
{
return;
}
m_OutputLock.lock();
const CLockScope LockScope(m_OutputLock);
DWORD Written; // we don't care about the value, but Windows 7 crashes if we pass NULL
WriteFile(m_pFile, pMessage->m_aLine, pMessage->m_LineLength, &Written, NULL);
WriteFile(m_pFile, "\r\n", 2, &Written, NULL);
m_OutputLock.unlock();
}
};
#endif
Expand Down Expand Up @@ -447,9 +444,9 @@ std::unique_ptr<ILogger> log_logger_windows_debugger()

void CFutureLogger::Set(std::shared_ptr<ILogger> pLogger)
{
std::shared_ptr<ILogger> null;
m_PendingLock.lock();
if(!std::atomic_compare_exchange_strong_explicit(&m_pLogger, &null, pLogger, std::memory_order_acq_rel, std::memory_order_acq_rel))
const CLockScope LockScope(m_PendingLock);
std::shared_ptr<ILogger> pNullLogger;
if(!std::atomic_compare_exchange_strong_explicit(&m_pLogger, &pNullLogger, pLogger, std::memory_order_acq_rel, std::memory_order_acq_rel))
{
dbg_assert(false, "future logger has already been set and can only be set once");
}
Expand All @@ -461,7 +458,6 @@ void CFutureLogger::Set(std::shared_ptr<ILogger> pLogger)
}
m_vPending.clear();
m_vPending.shrink_to_fit();
m_PendingLock.unlock();
}

void CFutureLogger::Log(const CLogMessage *pMessage)
Expand All @@ -472,15 +468,14 @@ void CFutureLogger::Log(const CLogMessage *pMessage)
pLogger->Log(pMessage);
return;
}
m_PendingLock.lock();
const CLockScope LockScope(m_PendingLock);
pLogger = std::atomic_load_explicit(&m_pLogger, std::memory_order_relaxed);
if(pLogger)
{
pLogger->Log(pMessage);
return;
}
m_vPending.push_back(*pMessage);
m_PendingLock.unlock();
}

void CFutureLogger::GlobalFinish()
Expand Down
9 changes: 5 additions & 4 deletions src/base/logger.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#ifndef BASE_LOGGER_H
#define BASE_LOGGER_H

#include "lock.h"
#include "log.h"

#include <atomic>
#include <memory>
#include <mutex>
#include <vector>

typedef void *IOHANDLE;
Expand Down Expand Up @@ -226,15 +227,15 @@ class CFutureLogger : public ILogger
private:
std::shared_ptr<ILogger> m_pLogger;
std::vector<CLogMessage> m_vPending;
std::mutex m_PendingLock;
CLock m_PendingLock;

public:
/**
* Replace the `CFutureLogger` instance with the given logger. It'll
* receive all log messages sent to the `CFutureLogger` so far.
*/
void Set(std::shared_ptr<ILogger> pLogger);
void Log(const CLogMessage *pMessage) override;
void Set(std::shared_ptr<ILogger> pLogger) REQUIRES(!m_PendingLock);
void Log(const CLogMessage *pMessage) override REQUIRES(!m_PendingLock);
void GlobalFinish() override;
void OnFilterChange() override;
};
Expand Down
25 changes: 12 additions & 13 deletions src/engine/client/sound.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ void CSound::SetVoiceVolume(CVoiceHandle Voice, float Volume)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -702,7 +702,7 @@ void CSound::SetVoiceFalloff(CVoiceHandle Voice, float Falloff)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -717,7 +717,7 @@ void CSound::SetVoiceLocation(CVoiceHandle Voice, float x, float y)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -732,7 +732,7 @@ void CSound::SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand Down Expand Up @@ -766,7 +766,7 @@ void CSound::SetVoiceCircle(CVoiceHandle Voice, float Radius)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -781,7 +781,7 @@ void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -792,7 +792,7 @@ void CSound::SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height)

ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float x, float y)
{
m_SoundLock.lock();
const CLockScope LockScope(m_SoundLock);

// search for voice
int VoiceID = -1;
Expand Down Expand Up @@ -836,7 +836,6 @@ ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags, float
Age = m_aVoices[VoiceID].m_Age;
}

m_SoundLock.unlock();
return CreateVoiceHandle(VoiceID, Age);
}

Expand All @@ -853,7 +852,7 @@ ISound::CVoiceHandle CSound::Play(int ChannelID, int SampleID, int Flags)
void CSound::Pause(int SampleID)
{
// TODO: a nice fade out
std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
CSample *pSample = &m_aSamples[SampleID];
for(auto &Voice : m_aVoices)
{
Expand All @@ -868,7 +867,7 @@ void CSound::Pause(int SampleID)
void CSound::Stop(int SampleID)
{
// TODO: a nice fade out
std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
CSample *pSample = &m_aSamples[SampleID];
for(auto &Voice : m_aVoices)
{
Expand All @@ -886,7 +885,7 @@ void CSound::Stop(int SampleID)
void CSound::StopAll()
{
// TODO: a nice fade out
std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
for(auto &Voice : m_aVoices)
{
if(Voice.m_pSample)
Expand All @@ -907,7 +906,7 @@ void CSound::StopVoice(CVoiceHandle Voice)

int VoiceID = Voice.Id();

std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
if(m_aVoices[VoiceID].m_Age != Voice.Age())
return;

Expand All @@ -917,7 +916,7 @@ void CSound::StopVoice(CVoiceHandle Voice)

bool CSound::IsPlaying(int SampleID)
{
std::unique_lock<std::mutex> Lock(m_SoundLock);
const CLockScope LockScope(m_SoundLock);
const CSample *pSample = &m_aSamples[SampleID];
return std::any_of(std::begin(m_aVoices), std::end(m_aVoices), [pSample](const auto &Voice) { return Voice.m_pSample == pSample; });
}
Expand Down
43 changes: 22 additions & 21 deletions src/engine/client/sound.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
#ifndef ENGINE_CLIENT_SOUND_H
#define ENGINE_CLIENT_SOUND_H

#include <base/lock.h>

#include <engine/sound.h>

#include <SDL_audio.h>

#include <atomic>
#include <mutex>

struct CSample
{
Expand Down Expand Up @@ -57,7 +58,7 @@ class CSound : public IEngineSound

bool m_SoundEnabled = false;
SDL_AudioDeviceID m_Device = 0;
std::mutex m_SoundLock;
CLock m_SoundLock;

CSample m_aSamples[NUM_SAMPLES] = {{0}};
CVoice m_aVoices[NUM_VOICES] = {{0}};
Expand Down Expand Up @@ -86,41 +87,41 @@ class CSound : public IEngineSound
public:
int Init() override;
int Update() override;
void Shutdown() override;
void Shutdown() override REQUIRES(!m_SoundLock);

bool IsSoundEnabled() override { return m_SoundEnabled; }

int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override;
int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override;
int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor) override;
int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override;
void UnloadSample(int SampleID) override;
void UnloadSample(int SampleID) override REQUIRES(!m_SoundLock);

float GetSampleTotalTime(int SampleID) override; // in s
float GetSampleCurrentTime(int SampleID) override; // in s
void SetSampleCurrentTime(int SampleID, float Time) override;
float GetSampleCurrentTime(int SampleID) override REQUIRES(!m_SoundLock); // in s
void SetSampleCurrentTime(int SampleID, float Time) override REQUIRES(!m_SoundLock);

void SetChannel(int ChannelID, float Vol, float Pan) override;
void SetListenerPos(float x, float y) override;

void SetVoiceVolume(CVoiceHandle Voice, float Volume) override;
void SetVoiceFalloff(CVoiceHandle Voice, float Falloff) override;
void SetVoiceLocation(CVoiceHandle Voice, float x, float y) override;
void SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset) override; // in s
void SetVoiceVolume(CVoiceHandle Voice, float Volume) override REQUIRES(!m_SoundLock);
void SetVoiceFalloff(CVoiceHandle Voice, float Falloff) override REQUIRES(!m_SoundLock);
void SetVoiceLocation(CVoiceHandle Voice, float x, float y) override REQUIRES(!m_SoundLock);
void SetVoiceTimeOffset(CVoiceHandle Voice, float TimeOffset) override REQUIRES(!m_SoundLock); // in s

void SetVoiceCircle(CVoiceHandle Voice, float Radius) override;
void SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height) override;
void SetVoiceCircle(CVoiceHandle Voice, float Radius) override REQUIRES(!m_SoundLock);
void SetVoiceRectangle(CVoiceHandle Voice, float Width, float Height) override REQUIRES(!m_SoundLock);

CVoiceHandle Play(int ChannelID, int SampleID, int Flags, float x, float y);
CVoiceHandle PlayAt(int ChannelID, int SampleID, int Flags, float x, float y) override;
CVoiceHandle Play(int ChannelID, int SampleID, int Flags) override;
void Pause(int SampleID) override;
void Stop(int SampleID) override;
void StopAll() override;
void StopVoice(CVoiceHandle Voice) override;
bool IsPlaying(int SampleID) override;
CVoiceHandle Play(int ChannelID, int SampleID, int Flags, float x, float y) REQUIRES(!m_SoundLock);
CVoiceHandle PlayAt(int ChannelID, int SampleID, int Flags, float x, float y) override REQUIRES(!m_SoundLock);
CVoiceHandle Play(int ChannelID, int SampleID, int Flags) override REQUIRES(!m_SoundLock);
void Pause(int SampleID) override REQUIRES(!m_SoundLock);
void Stop(int SampleID) override REQUIRES(!m_SoundLock);
void StopAll() override REQUIRES(!m_SoundLock);
void StopVoice(CVoiceHandle Voice) override REQUIRES(!m_SoundLock);
bool IsPlaying(int SampleID) override REQUIRES(!m_SoundLock);

void Mix(short *pFinalOut, unsigned Frames) override;
void Mix(short *pFinalOut, unsigned Frames) override REQUIRES(!m_SoundLock);
void PauseAudioDevice() override;
void UnpauseAudioDevice() override;
};
Expand Down
4 changes: 2 additions & 2 deletions src/engine/server/server_logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ class CServer;
class CServerLogger : public ILogger
{
CServer *m_pServer = nullptr;
std::mutex m_PendingLock;
CLock m_PendingLock;
std::vector<CLogMessage> m_vPending;
std::thread::id m_MainThread;

public:
CServerLogger(CServer *pServer);
void Log(const CLogMessage *pMessage) override;
void Log(const CLogMessage *pMessage) override REQUIRES(!m_PendingLock);
// Must be called from the main thread!
void OnServerDeletion();
};
Expand Down
Loading

0 comments on commit 1047004

Please sign in to comment.