Skip to content

Commit

Permalink
Use system wide file locks to synchronize log files.
Browse files Browse the repository at this point in the history
The global semaphore has been observed to not be released on unix like
platforms when the process exits. So, we convert to file locks, which
will be released on process exit on unix like platforms. Unfortunately,
Win32 file locks and POSIX file locks have slightly different semantics
in that Win32 locks are mandatory and POSIX locks are advisory, which
can result in slightly different behavior. OTOH, this should fix the
client deadlocking until the system is restarted, so this should be an
overall win.
  • Loading branch information
Hoikas committed Jul 20, 2024
1 parent 44063ab commit c9220a8
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 12 deletions.
2 changes: 2 additions & 0 deletions Sources/Plasma/CoreLib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ set(CoreLib_SOURCES
hsExceptions.cpp
hsExceptionStack.cpp
hsFastMath.cpp
hsFILELock.cpp
hsGeometry3.cpp
hsMatrix33.cpp
hsMatrix44.cpp
Expand Down Expand Up @@ -43,6 +44,7 @@ set(CoreLib_HEADERS
hsExceptions.h
hsExceptionStack.h
hsFastMath.h
hsFILELock.h
hsGeometry3.h
hsLockGuard.h
hsMatrix44.h
Expand Down
162 changes: 162 additions & 0 deletions Sources/Plasma/CoreLib/hsFILELock.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/

#include "hsFILELock.h"

#include <string_theory/format>
#include <system_error>

#include "hsWindows.h"
#ifdef _MSC_VER
# include <io.h>
#endif
#ifndef HS_BUILD_FOR_WIN32
# include <sys/file.h>
#endif

bool hsFILELock::ILock(bool block) const
{
if (fRef == nullptr)
return true;

#ifdef HS_BUILD_FOR_WIN32
OVERLAPPED o{};
DWORD flags = LOCKFILE_EXCLUSIVE_LOCK;
if (block)
flags |= LOCKFILE_FAIL_IMMEDIATELY;

// Lock only the first byte of the file - we have
// to provide the exact same region to UnlockFileEx(),
// and this is a mandatory lock. Totally different than
// POSIX file locks, which are advisory and operate on
// the whole file.
BOOL result = LockFileEx(
#ifdef _MSC_VER
(HANDLE)_get_osfhandle(_fileno(fRef)),
#else
(HANDLE)fileno(f),
#endif // _MSC_VER
flags,
0,
1,
0,
&o
);
if (result == 0) {
DWORD error = GetLastError();
if (!block && error == ERROR_IO_PENDING)
return false;
// BasicLockable requires an exception to indicate that the
// lock was not acquired.
throw std::system_error(
std::error_code(error, std::system_category()),
ST::format(
"LockFileEx() failed: {}",
hsCOMError(hsLastWin32Error, error)
).to_std_string()
);
}

#else
int op = LOCK_EX;
if (!block)
op |= LOCK_NB;
int result = flock(fileno(f), op);
if (result == -1) {
if (!block && errno == EWOULDBLOCK)
return false;
// BasicLockable requires an exception to indicate that the
// lock was not acquired. Even though EINTR is probably not
// an error condition, we should still throw to indicate no
// lock was acquired.
throw std::system_error(
std::error_code(errno, std::system_category()),
ST::format(
"flock() LOCK_EX failed: {}",
strerror(errno)
).to_std_string()
);
}
#endif // HS_BUILD_FOR_WIN32

return true;
}

void hsFILELock::unlock() const
{
if (fRef == nullptr)
return;

#ifdef HS_BUILD_FOR_WIN32
OVERLAPPED o{};
BOOL result = UnlockFileEx(
#ifdef _MSC_VER
(HANDLE)_get_osfhandle(_fileno(fRef)),
#else
(HANDLE)fileno(f),
#endif // _MSC_VER
0,
1,
0,
&o
);
// BasicLockable doesn't allow exceptions to be
// thrown in the unlock method.
hsAssert(
result,
ST::format(
"UnlockFileEx() failed: {}",
hsCOMError(hsLastWin32Error, GetLastError())
).c_str()
);

#else
int result = flock(fileno(f), LOCK_UN);
hsAssert(
result == 0,
ST::format(
"flock() LOCK_UN failed: {}",
strerror(errno)
).c_str()
);
#endif // HS_BUILD_FOR_WIN32
}
83 changes: 83 additions & 0 deletions Sources/Plasma/CoreLib/hsFILELock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/

#ifndef hsFILELock_inc
#define hsFILELock_inc

#include "HeadSpin.h"

/**
* Acquire a platform dependent lock on the underlying file handle.
* \remarks On Windows, file locks are obligatory. Any attempts to
* read/write from/to the locked region will be denied. The region
* that this method locks is an implementation detail. On unix-like
* platforms, locks are advisory, and reads/writes from/to the locked
* region will succeed. Therefore, file locks should be treated as
* advisory at best (they won't stop someone from making a mess), and
* mandatory at worst (they can cause reads/writes to fail).
* \note This class meets the requirements of `Lockable`.
*/
class hsFILELock
{
FILE* fRef;

bool ILock(bool block) const;

public:
hsFILELock() = delete;
hsFILELock(const hsFILELock&) = delete;
hsFILELock(hsFILELock&& mv)
: fRef(mv.fRef)
{
mv.fRef = nullptr;
}
hsFILELock(FILE* f) : fRef(f) { }

void lock() const { ILock(true); }

[[nodiscard]]
bool try_lock() const { return ILock(false); }

void unlock() const;
};

#endif
14 changes: 4 additions & 10 deletions Sources/Plasma/PubUtilLib/plStatusLog/plStatusLog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "plStatusLog.h"
#include "plEncryptLogLine.h"

#include "hsFILELock.h"
#include "plProduct.h"
#include "hsThread.h"
#include "hsTimer.h"
Expand Down Expand Up @@ -259,20 +260,17 @@ bool plStatusLogMgr::DumpLogs( const plFileName &newFolderName )
uint32_t plStatusLog::fLoggingOff = false;

plStatusLog::plStatusLog( uint8_t numDisplayLines, const plFileName &filename, uint32_t flags )
: fFileHandle(), fSema(), fSize(), fForceLog(), fMaxNumLines(numDisplayLines),
: fFileHandle(), fSize(), fForceLog(), fMaxNumLines(numDisplayLines),
fDisplayPointer()
{
if (filename.IsValid())
{
fFilename = filename;
fSema = new hsGlobalSemaphore(1, fFilename.AsString().c_str());
}
else
{
fFilename = "";
flags |= kDontWriteFile;

fSema = new hsGlobalSemaphore(1);
}

fOrigFlags = fFlags = flags;
Expand Down Expand Up @@ -366,9 +364,6 @@ void plStatusLog::IFini()
if (fBack != nullptr || fNext != nullptr)
IUnlink();

if (fSema)
delete fSema;

delete [] fLines;
delete [] fColors;
}
Expand Down Expand Up @@ -431,7 +426,8 @@ bool plStatusLog::IAddLine(const ST::string& line, uint32_t color)
return true;

/// Scroll pointers up
fSema->Wait();
hsFILELock fileLock(fFileHandle);
hsLockGuard(fileLock);

if (fMaxNumLines > 0)
{
Expand All @@ -448,8 +444,6 @@ bool plStatusLog::IAddLine(const ST::string& line, uint32_t color)

bool ret = IPrintLineToFile(line);

fSema->Signal();

return ret;
}

Expand Down
2 changes: 0 additions & 2 deletions Sources/Plasma/PubUtilLib/plStatusLog/plStatusLog.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#define _plStatusLog_h

#include "HeadSpin.h"
#include "hsThread.h"
#include "plFileSystem.h"
#include "plLoggable.h"

Expand Down Expand Up @@ -88,7 +87,6 @@ class plStatusLog : public plLog
plFileName fFilename;
ST::string* fLines;
uint32_t* fColors;
hsGlobalSemaphore* fSema;
FILE* fFileHandle;
uint32_t fSize;
bool fForceLog;
Expand Down

0 comments on commit c9220a8

Please sign in to comment.