Make Log() instances only lock in ~Log(), and make the mutex non-recursive.

This commit is contained in:
Joseph Geumlek 2022-06-23 14:20:48 -07:00
parent fefd70b943
commit 517186bbe0
2 changed files with 37 additions and 36 deletions

View file

@ -12,14 +12,14 @@
LogLevel Log::getReportingLevel() LogLevel Log::getReportingLevel()
{ {
// Static Log functions need to grab the lock. // Static Log functions need to grab the lock.
std::unique_lock<std::recursive_mutex> lock {sLogMutex}; std::unique_lock<std::mutex> lock {sLogMutex};
return sReportingLevel; return sReportingLevel;
} }
void Log::setReportingLevel(LogLevel level) void Log::setReportingLevel(LogLevel level)
{ {
// Static Log functions need to grab the lock. // Static Log functions need to grab the lock.
std::unique_lock<std::recursive_mutex> lock {sLogMutex}; std::unique_lock<std::mutex> lock {sLogMutex};
sReportingLevel = level; sReportingLevel = level;
} }
@ -49,7 +49,7 @@ void Log::init()
void Log::open() void Log::open()
{ {
// Static Log functions need to grab the lock. // Static Log functions need to grab the lock.
std::unique_lock<std::recursive_mutex> lock {sLogMutex}; std::unique_lock<std::mutex> lock {sLogMutex};
#if defined(_WIN64) #if defined(_WIN64)
sFile.open(Utils::String::stringToWideString(getLogPath()).c_str()); sFile.open(Utils::String::stringToWideString(getLogPath()).c_str());
#else #else
@ -57,9 +57,25 @@ void Log::open()
#endif #endif
} }
void Log::flush()
{
// Flush file. Static Log functions need to grab the lock.
std::unique_lock<std::mutex> lock {sLogMutex};
sFile.flush();
}
void Log::close()
{
// Static Log functions need to grab the lock.
std::unique_lock<std::mutex> lock {sLogMutex};
if (sFile.is_open())
sFile.close();
}
std::ostringstream& Log::get(LogLevel level) std::ostringstream& Log::get(LogLevel level)
{ {
// This function is not-static, lock is guarded by the Log() instance. // This function is not-static, but only touches instance member variables.
// The lock is not needed.
time_t t {time(nullptr)}; time_t t {time(nullptr)};
struct tm tm; struct tm tm;
#if defined(_WIN64) #if defined(_WIN64)
@ -75,34 +91,19 @@ std::ostringstream& Log::get(LogLevel level)
return mOutStringStream; return mOutStringStream;
} }
void Log::flush()
{
// Flush file. Static Log functions need to grab the lock.
std::unique_lock<std::recursive_mutex> lock {sLogMutex};
sFile.flush();
}
void Log::close()
{
// Static Log functions need to grab the lock.
std::unique_lock<std::recursive_mutex> lock {sLogMutex};
if (sFile.is_open())
sFile.close();
}
Log::Log()
{
// Log instance created. We grab the lock until destruction.
// This permits `Log().get(...) << msg << msg << msg;` to
// function as expected.
sLogMutex.lock();
}
Log::~Log() Log::~Log()
{ {
// sLogMutex was (and still is) locked from the constructor Log().
mOutStringStream << std::endl; mOutStringStream << std::endl;
// Log() constructor does not need the lock.
// Log::get() does not need the lock.
// get(..) << msg << msg also does not need the lock,
// since get() returns the instance member mOutStringStream.
// It is only now that we need the lock, to guard access to
// both std::cerr and sFile.
std::unique_lock<std::mutex> lock {sLogMutex};
if (!sFile.is_open()) { if (!sFile.is_open()) {
// Not open yet, print to stdout. // Not open yet, print to stdout.
std::cerr << "Error: Tried to write to log file before it was open, " std::cerr << "Error: Tried to write to log file before it was open, "
@ -116,8 +117,4 @@ Log::~Log()
// If it's an error or the --debug flag has been set, then print to the console as well. // If it's an error or the --debug flag has been set, then print to the console as well.
if (mMessageLevel == LogError || sReportingLevel >= LogDebug) if (mMessageLevel == LogError || sReportingLevel >= LogDebug)
std::cerr << mOutStringStream.str(); std::cerr << mOutStringStream.str();
// Release the lock, after any and all operations have been performed
// on mOutStringStream or sFile.
sLogMutex.unlock();
} }

View file

@ -34,11 +34,14 @@ enum LogLevel {
class Log class Log
{ {
public: public:
// Constructor/deconstructor handle a lock, making get() thread-safe. // Deconstructor grabs the lock.
Log();
~Log(); ~Log();
// No lock needed for get() as it operates on and returns non-static members.
std::ostringstream& get(LogLevel level = LogInfo); std::ostringstream& get(LogLevel level = LogInfo);
// Lock is grabbed for sReportingLevel.
// This means level > Log::getReportingLevel() still requires the lock.
static LogLevel getReportingLevel(); static LogLevel getReportingLevel();
static void setReportingLevel(LogLevel level); static void setReportingLevel(LogLevel level);
@ -46,10 +49,11 @@ public:
static std::string getLogPath(); static std::string getLogPath();
// init() is not thread-safe. // init() is not thread-safe.
static void init(); static void init();
// open() is not fully thread-safe, as it uses getLogPath().
static void open();
// The following static functions are thread-safe. // The following static functions are thread-safe.
static void flush(); static void flush();
static void open();
static void close(); static void close();
protected: protected:
@ -63,7 +67,7 @@ private:
{LogDebug, "Debug"}}; {LogDebug, "Debug"}};
static inline std::ofstream sFile; static inline std::ofstream sFile;
static inline LogLevel sReportingLevel = LogInfo; static inline LogLevel sReportingLevel = LogInfo;
static inline std::recursive_mutex sLogMutex; static inline std::mutex sLogMutex;
LogLevel mMessageLevel; LogLevel mMessageLevel;
}; };