From e5752a5c6d4b746afe947e512e9861dc734ea990 Mon Sep 17 00:00:00 2001 From: Aleksandar Fabijanic Date: Wed, 16 Oct 2024 16:58:00 -0500 Subject: [PATCH] File lock (#4734) * add RWLock implementation for file [posix] * add implementation FileStreamRWLock for windows replace FileStreamRWLock to the Process package * add files FileStreamRWLock* into makefile and vcproj * remove unnecessary file from makefile * use absolute path to the TesApp with ProcessRunner * fix vc*.proj * add new test files into vc.proj.filters * fix comments * fix spelling fo PR #4723 add atomic_bool _locked and check if FileStreamRWLock is locked on destruction for force unlock * add atomic header * File lock (#4740) * throw error on any errno not only on EDEADLK * fix function naming * fix windows build * fix windows build --------- Co-authored-by: Alexander B Co-authored-by: bas524 --- Foundation/Foundation_vs160.vcxproj | 34 ++- Foundation/Foundation_vs170.vcxproj | 46 ++- Foundation/Makefile | 2 +- Foundation/include/Poco/FileStreamRWLock.h | 204 ++++++++++++++ .../include/Poco/FileStreamRWLock_POSIX.h | 115 ++++++++ .../include/Poco/FileStreamRWLock_WIN32.h | 134 +++++++++ Foundation/src/FileStreamRWLock.cpp | 44 +++ Foundation/src/FileStreamRWLock_POSIX.cpp | 36 +++ Foundation/src/FileStreamRWLock_WIN32.cpp | 39 +++ Foundation/testsuite/Makefile-Driver | 2 +- Foundation/testsuite/TestSuite_vs160.vcxproj | 5 +- .../testsuite/TestSuite_vs160.vcxproj.filters | 6 +- Foundation/testsuite/TestSuite_vs170.vcxproj | 4 +- .../testsuite/TestSuite_vs170.vcxproj.filters | 6 +- .../testsuite/src/FileStreamRWLockTest.cpp | 264 ++++++++++++++++++ .../testsuite/src/FileStreamRWLockTest.h | 39 +++ .../testsuite/src/ProcessesTestSuite.cpp | 2 + Foundation/testsuite/src/TestApp.cpp | 167 +++++++++++ 18 files changed, 1141 insertions(+), 8 deletions(-) create mode 100644 Foundation/include/Poco/FileStreamRWLock.h create mode 100644 Foundation/include/Poco/FileStreamRWLock_POSIX.h create mode 100644 Foundation/include/Poco/FileStreamRWLock_WIN32.h create mode 100644 Foundation/src/FileStreamRWLock.cpp create mode 100644 Foundation/src/FileStreamRWLock_POSIX.cpp create mode 100644 Foundation/src/FileStreamRWLock_WIN32.cpp create mode 100644 Foundation/testsuite/src/FileStreamRWLockTest.cpp create mode 100644 Foundation/testsuite/src/FileStreamRWLockTest.h diff --git a/Foundation/Foundation_vs160.vcxproj b/Foundation/Foundation_vs160.vcxproj index f0d0379f2b..3781a66778 100644 --- a/Foundation/Foundation_vs160.vcxproj +++ b/Foundation/Foundation_vs160.vcxproj @@ -977,6 +977,35 @@ true true + + + true + true + true + true + true + true + true + true + true + true + true + true + + + true + true + true + true + true + true + true + true + true + true + true + true + true @@ -1613,6 +1642,9 @@ + + + @@ -1891,4 +1923,4 @@ - \ No newline at end of file + diff --git a/Foundation/Foundation_vs170.vcxproj b/Foundation/Foundation_vs170.vcxproj index 4491d060f7..041e45a1e9 100644 --- a/Foundation/Foundation_vs170.vcxproj +++ b/Foundation/Foundation_vs170.vcxproj @@ -1415,6 +1415,47 @@ true true + + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + + + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true + true @@ -2231,6 +2272,9 @@ + + + @@ -2533,4 +2577,4 @@ - \ No newline at end of file + diff --git a/Foundation/Makefile b/Foundation/Makefile index 67b68bb48d..3e07a06d36 100644 --- a/Foundation/Makefile +++ b/Foundation/Makefile @@ -34,7 +34,7 @@ objects = ArchiveStrategy Ascii ASCIIEncoding AsyncChannel AsyncNotificationCent FileStreamFactory URIStreamFactory URIStreamOpener UTF32Encoding UTF16Encoding UTF8Encoding UTF8String \ Unicode UnicodeConverter Windows1250Encoding Windows1251Encoding Windows1252Encoding \ UUID UUIDGenerator Void Var VarHolder VarIterator VarVisitor Format Pipe PipeImpl PipeStream SharedMemory \ - MemoryStream FileStream AtomicCounter DataURIStream DataURIStreamFactory + MemoryStream FileStream AtomicCounter DataURIStream DataURIStreamFactory FileStreamRWLock zlib_objects = adler32 compress crc32 deflate \ infback inffast inflate inftrees trees zutil diff --git a/Foundation/include/Poco/FileStreamRWLock.h b/Foundation/include/Poco/FileStreamRWLock.h new file mode 100644 index 0000000000..7227ed03d9 --- /dev/null +++ b/Foundation/include/Poco/FileStreamRWLock.h @@ -0,0 +1,204 @@ +// +// FileStreamRWLock.h +// +// Library: Foundation +// Package: Processes +// Module: FileStreamRWLock +// +// Definition of the FileStreamRWLock class. +// +// Copyright (c) 2004-2024, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef Foundation_FileStreamRWLock_INCLUDED +#define Foundation_FileStreamRWLock_INCLUDED + + +#include "Poco/Foundation.h" +#include "Poco/Exception.h" +#include "Poco/FileStream.h" +#include + +#if defined(POCO_OS_FAMILY_WINDOWS) +#include "Poco/FileStreamRWLock_WIN32.h" +#else +#include "Poco/FileStreamRWLock_POSIX.h" +#endif + + +namespace Poco { + + +class ScopedFStreamRWLock; +class ScopedFStreamReadRWLock; +class ScopedFStreamWriteRWLock; + + +class Foundation_API FileStreamRWLock: private FileStreamRWLockImpl + /// A reader writer lock on the file region allows multiple concurrent + /// process-readers or one exclusive process-writer. +{ +public: + using ScopedLock = ScopedFStreamRWLock; + using ScopedReadLock = ScopedFStreamReadRWLock; + using ScopedWriteLock = ScopedFStreamWriteRWLock; + + FileStreamRWLock(const FileStream &fs, Poco::UInt64 offset, Poco::UInt64 size); + /// Creates the Reader/Writer lock on the file region. + /// offset - from start of the file + /// size - size of the locker region + + ~FileStreamRWLock(); + /// Destroys the Reader/Writer lock on the file region. + + void readLock(); + /// Acquires a read lock. If another process currently holds a write lock, + /// waits until the write lock is released. + + bool tryReadLock(); + /// Tries to acquire a read lock. Immediately returns true if successful, or + /// false if another process currently holds a write lock. + + void writeLock(); + /// Acquires a write lock on the file region. If one or more other processes currently hold + /// locks, waits until all locks are released. + + bool tryWriteLock(); + /// Tries to acquire a write lock on the file region. Immediately returns true if successful, + /// or false if one or more other processes currently hold + /// locks. + + void unlock(); + /// Releases the read or write lock. + +private: + std::atomic_bool _locked = false; + FileStreamRWLock(const FileStreamRWLock&); + FileStreamRWLock& operator = (const FileStreamRWLock&); +}; + + +class Foundation_API ScopedFStreamRWLock + /// A variant of ScopedLock for reader/writer locks. +{ +public: + ScopedFStreamRWLock(FileStreamRWLock& rwl, bool write = false); + ~ScopedFStreamRWLock(); + +private: + FileStreamRWLock& _rwl; + + ScopedFStreamRWLock(); + ScopedFStreamRWLock(const ScopedFStreamRWLock&); + ScopedFStreamRWLock& operator = (const ScopedFStreamRWLock&); +}; + + +class Foundation_API ScopedFStreamReadRWLock : public ScopedFStreamRWLock + /// A variant of ScopedLock for reader locks. +{ +public: + ScopedFStreamReadRWLock(FileStreamRWLock& rwl); + ~ScopedFStreamReadRWLock(); +}; + + +class Foundation_API ScopedFStreamWriteRWLock : public ScopedFStreamRWLock + /// A variant of ScopedLock for writer locks. +{ +public: + ScopedFStreamWriteRWLock(FileStreamRWLock& rwl); + ~ScopedFStreamWriteRWLock(); +}; + + +// +// inlines +// +inline void FileStreamRWLock::readLock() +{ + readLockImpl(); + _locked = true; +} + + +inline bool FileStreamRWLock::tryReadLock() +{ + bool locked = tryReadLockImpl(); + if (locked) _locked = true; // assign only if success lock + return locked; +} + + +inline void FileStreamRWLock::writeLock() +{ + writeLockImpl(); + _locked = true; +} + + +inline bool FileStreamRWLock::tryWriteLock() +{ + bool locked = tryWriteLockImpl(); + if (locked) _locked = true; // assign only if success lock + return locked; +} + + +inline void FileStreamRWLock::unlock() +{ + unlockImpl(); + _locked = false; +} + + +inline ScopedFStreamRWLock::ScopedFStreamRWLock(FileStreamRWLock& rwl, bool write): _rwl(rwl) +{ + if (write) + _rwl.writeLock(); + else + _rwl.readLock(); +} + + +inline ScopedFStreamRWLock::~ScopedFStreamRWLock() +{ + try + { + _rwl.unlock(); + } + catch (...) + { + poco_unexpected(); + } +} + + +inline ScopedFStreamReadRWLock::ScopedFStreamReadRWLock(FileStreamRWLock& rwl): ScopedFStreamRWLock(rwl, false) +{ +} + + +inline ScopedFStreamReadRWLock::~ScopedFStreamReadRWLock() +{ +} + + +inline ScopedFStreamWriteRWLock::ScopedFStreamWriteRWLock(FileStreamRWLock& rwl): ScopedFStreamRWLock(rwl, true) +{ +} + + +inline ScopedFStreamWriteRWLock::~ScopedFStreamWriteRWLock() +{ +} + + +} // namespace Poco + + +#endif // Foundation_FileStreamRWLock_INCLUDED diff --git a/Foundation/include/Poco/FileStreamRWLock_POSIX.h b/Foundation/include/Poco/FileStreamRWLock_POSIX.h new file mode 100644 index 0000000000..9bcd38624a --- /dev/null +++ b/Foundation/include/Poco/FileStreamRWLock_POSIX.h @@ -0,0 +1,115 @@ +// +// FileStreamRWLock_POSIX.h +// +// Library: Foundation +// Package: Processes +// Module: FileStreamRWLock +// +// Definition of the FileStreamRWLockImpl class for POSIX FileStream. +// +// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef Foundation_FileStreamRWLock_POSIX_INCLUDED +#define Foundation_FileStreamRWLock_POSIX_INCLUDED + + +#include "Poco/Foundation.h" +#include "Poco/Exception.h" +#include "Poco/FileStream.h" +#include +#include + + +namespace Poco { + + +class Foundation_API FileStreamRWLockImpl +{ +protected: + FileStreamRWLockImpl(const FileStream::NativeHandle &fd, Poco::UInt64 offset, Poco::UInt64 size); + ~FileStreamRWLockImpl(); + void readLockImpl(); + bool tryReadLockImpl(); + void writeLockImpl(); + bool tryWriteLockImpl(); + void unlockImpl(); + +private: + FileStream::NativeHandle _fd; + struct flock _flock; + int _lockMode; +}; + + +// +// inlines +// +inline void FileStreamRWLockImpl::readLockImpl() +{ + _flock.l_type = F_RDLCK; + _lockMode = F_SETLKW; + int rc = fcntl(_fd, _lockMode, &_flock); + if (rc == -1) + throw SystemException("cannot lock reader lock", errno); +} + + +inline bool FileStreamRWLockImpl::tryReadLockImpl() +{ + _flock.l_type = F_RDLCK; + _lockMode = F_SETLK; + int rc = fcntl(_fd, _lockMode, &_flock); + if (rc == 0) + return true; + else if (errno == EAGAIN || errno == EACCES) + return false; + else + throw SystemException("cannot lock try-reader lock", errno); + +} + + +inline void FileStreamRWLockImpl::writeLockImpl() +{ + _flock.l_type = F_WRLCK; + _lockMode = F_SETLKW; + int rc = fcntl(_fd, _lockMode, &_flock); + if (rc == -1) + throw SystemException("cannot lock writer lock", errno); +} + + +inline bool FileStreamRWLockImpl::tryWriteLockImpl() +{ + _flock.l_type = F_WRLCK; + _lockMode = F_SETLK; + int rc = fcntl(_fd, _lockMode, &_flock); + if (rc != -1) + return true; + else if (errno == EAGAIN || errno == EACCES) + return false; + else + throw SystemException("cannot lock writer lock", errno); + +} + + +inline void FileStreamRWLockImpl::unlockImpl() +{ + _flock.l_type = F_UNLCK; + _lockMode = F_SETLKW; + int rc = fcntl(_fd, _lockMode, &_flock); + if (rc == -1) + throw SystemException("cannot unlock", errno); +} + + +} // namespace Poco + + +#endif // Foundation_FileStreamRWLock_POSIX_INCLUDED diff --git a/Foundation/include/Poco/FileStreamRWLock_WIN32.h b/Foundation/include/Poco/FileStreamRWLock_WIN32.h new file mode 100644 index 0000000000..537b19ae2b --- /dev/null +++ b/Foundation/include/Poco/FileStreamRWLock_WIN32.h @@ -0,0 +1,134 @@ +// +// FileStreamRWLock_WIN32.h +// +// Library: Foundation +// Package: Processes +// Module: FileStreamRWLock +// +// Definition of the FileStreamRWLockImpl class for WIN32 FileStream. +// +// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef Foundation_FileStreamRWLock_WIN32_INCLUDED +#define Foundation_FileStreamRWLock_WIN32_INCLUDED + + +#include "Poco/Foundation.h" +#include "Poco/Exception.h" +#include "Poco/FileStream.h" +#include + +namespace Poco { + +struct LockMode +{ + static constexpr DWORD READ = 0; + static constexpr DWORD WRITE = LOCKFILE_EXCLUSIVE_LOCK; + static constexpr DWORD TRY_READ = LOCKFILE_FAIL_IMMEDIATELY; + static constexpr DWORD TRY_WRITE = (LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY); +}; + +class Foundation_API FileStreamRWLockImpl +{ +protected: + FileStreamRWLockImpl(const FileStream::NativeHandle &fd, Poco::UInt64 offset, Poco::UInt64 size); + ~FileStreamRWLockImpl(); + void readLockImpl(); + bool tryReadLockImpl(); + void writeLockImpl(); + bool tryWriteLockImpl(); + void unlockImpl(); + +private: + FileStream::NativeHandle _fd; + OVERLAPPED _overlapped; + LARGE_INTEGER _size; +}; + + +// +// inlines +// +inline void FileStreamRWLockImpl::readLockImpl() +{ + BOOL fSuccess = LockFileEx(_fd, LockMode::READ, 0, _size.LowPart, _size.HighPart, &_overlapped); + if (!fSuccess) + { + throw SystemException("cannot lock reader lock", GetLastError()); + } +} + + +inline bool FileStreamRWLockImpl::tryReadLockImpl() +{ + BOOL fSuccess = LockFileEx(_fd, LockMode::TRY_READ, 0, _size.LowPart, _size.HighPart, &_overlapped); + if (fSuccess) + { + return true; + } + else + { + auto lastError = GetLastError(); + if (lastError == ERROR_IO_PENDING || lastError == ERROR_LOCK_VIOLATION) + { + return false; + } + else + { + throw SystemException("cannot lock try-reader lock", lastError); + } + } +} + + +inline void FileStreamRWLockImpl::writeLockImpl() +{ + BOOL fSuccess = LockFileEx(_fd, LockMode::WRITE, 0, _size.LowPart, _size.HighPart, &_overlapped); + if (!fSuccess) + { + throw SystemException("cannot lock writer lock", GetLastError()); + } +} + + +inline bool FileStreamRWLockImpl::tryWriteLockImpl() +{ + BOOL fSuccess = LockFileEx(_fd, LockMode::TRY_WRITE, 0, _size.LowPart, _size.HighPart, &_overlapped); + if (fSuccess) + { + return true; + } + else + { + auto lastError = GetLastError(); + if (lastError == ERROR_IO_PENDING || lastError == ERROR_LOCK_VIOLATION) + { + return false; + } + else + { + throw SystemException("cannot lock try-writer lock", lastError); + } + } +} + + +inline void FileStreamRWLockImpl::unlockImpl() +{ + BOOL fSuccess = UnlockFileEx(_fd, 0, _size.LowPart, _size.HighPart, &_overlapped); + if (!fSuccess) + { + throw SystemException("cannot unlock ", GetLastError()); + } +} + + +} // namespace Poco + + +#endif // Foundation_FileStreamRWLock_WIN32_INCLUDED diff --git a/Foundation/src/FileStreamRWLock.cpp b/Foundation/src/FileStreamRWLock.cpp new file mode 100644 index 0000000000..482b4933eb --- /dev/null +++ b/Foundation/src/FileStreamRWLock.cpp @@ -0,0 +1,44 @@ +// +// FileStreamRWLock.cpp +// +// Library: Foundation +// Package: Processes +// Module: FileStreamRWLock +// +// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/FileStreamRWLock.h" + + +#if defined(POCO_OS_FAMILY_WINDOWS) +#include "FileStreamRWLock_WIN32.cpp" +#else +#include "FileStreamRWLock_POSIX.cpp" +#endif + + +namespace Poco { + + +FileStreamRWLock::FileStreamRWLock(const FileStream &fs, Poco::UInt64 offset, Poco::UInt64 size) : + FileStreamRWLockImpl(fs.nativeHandle(), offset, size) +{ +} + + +FileStreamRWLock::~FileStreamRWLock() +{ + if (_locked) + { + unlockImpl(); + _locked = false; + } +} + + +} // namespace Poco diff --git a/Foundation/src/FileStreamRWLock_POSIX.cpp b/Foundation/src/FileStreamRWLock_POSIX.cpp new file mode 100644 index 0000000000..94c7c8a0be --- /dev/null +++ b/Foundation/src/FileStreamRWLock_POSIX.cpp @@ -0,0 +1,36 @@ +// +// FileStreamRWLock_POSIX.cpp +// +// Library: Foundation +// Package: Processes +// Module: FileStreamRWLock +// +// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/FileStreamRWLock_POSIX.h" + + +namespace Poco { + + +FileStreamRWLockImpl::FileStreamRWLockImpl(const FileStream::NativeHandle &fd, Poco::UInt64 offset, Poco::UInt64 size): + _fd(fd), _lockMode(0) +{ + _flock.l_whence = SEEK_SET; + _flock.l_start = offset; + _flock.l_len = size; + _flock.l_pid = 0; +} + + +FileStreamRWLockImpl::~FileStreamRWLockImpl() +{ +} + + +} // namespace Poco diff --git a/Foundation/src/FileStreamRWLock_WIN32.cpp b/Foundation/src/FileStreamRWLock_WIN32.cpp new file mode 100644 index 0000000000..12d4935baa --- /dev/null +++ b/Foundation/src/FileStreamRWLock_WIN32.cpp @@ -0,0 +1,39 @@ +// +// FileStreamRWLock_WIN32.cpp +// +// Library: Foundation +// Package: Processes +// Module: FileStreamRWLock +// +// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "Poco/FileStreamRWLock_WIN32.h" + + +namespace Poco { + + +FileStreamRWLockImpl::FileStreamRWLockImpl(const FileStream::NativeHandle &fd, Poco::UInt64 offset, Poco::UInt64 size): + _fd(fd) +{ + LARGE_INTEGER offt; + offt.QuadPart = offset; + memset(&_overlapped, 0, sizeof(OVERLAPPED)); + + _overlapped.Offset = offt.LowPart; + _overlapped.OffsetHigh = offt.HighPart; + _size.QuadPart = size; +} + + +FileStreamRWLockImpl::~FileStreamRWLockImpl() +{ +} + + +} // namespace Poco diff --git a/Foundation/testsuite/Makefile-Driver b/Foundation/testsuite/Makefile-Driver index 2f4396a1f9..846c62f84a 100644 --- a/Foundation/testsuite/Makefile-Driver +++ b/Foundation/testsuite/Makefile-Driver @@ -38,7 +38,7 @@ objects = ActiveMethodTest ActivityTest ActiveDispatcherTest \ UniqueExpireCacheTest UniqueExpireLRUCacheTest UnicodeConverterTest \ TuplesTest NamedTuplesTest TypeListTest VarTest DynamicTestSuite FileStreamTest \ MemoryStreamTest ObjectPoolTest DirectoryWatcherTest DirectoryIteratorsTest \ - DataURIStreamTest + DataURIStreamTest FileStreamRWLockTest target = testrunner target_version = 1 diff --git a/Foundation/testsuite/TestSuite_vs160.vcxproj b/Foundation/testsuite/TestSuite_vs160.vcxproj index 41a1fa60af..1153387e51 100644 --- a/Foundation/testsuite/TestSuite_vs160.vcxproj +++ b/Foundation/testsuite/TestSuite_vs160.vcxproj @@ -663,6 +663,7 @@ + @@ -806,7 +807,9 @@ + + @@ -912,4 +915,4 @@ - \ No newline at end of file + diff --git a/Foundation/testsuite/TestSuite_vs160.vcxproj.filters b/Foundation/testsuite/TestSuite_vs160.vcxproj.filters index e26995529d..e20e0fbfc9 100644 --- a/Foundation/testsuite/TestSuite_vs160.vcxproj.filters +++ b/Foundation/testsuite/TestSuite_vs160.vcxproj.filters @@ -603,6 +603,8 @@ Threading\Source Files + + Processes\Source Files Logging\Source Files @@ -1031,8 +1033,10 @@ Threading\Header Files + + Processes\Header Files Logging\Header Files - \ No newline at end of file + diff --git a/Foundation/testsuite/TestSuite_vs170.vcxproj b/Foundation/testsuite/TestSuite_vs170.vcxproj index 91e3d7910c..d3a2a1cd41 100644 --- a/Foundation/testsuite/TestSuite_vs170.vcxproj +++ b/Foundation/testsuite/TestSuite_vs170.vcxproj @@ -996,6 +996,7 @@ + @@ -1140,6 +1141,7 @@ + @@ -1245,4 +1247,4 @@ - \ No newline at end of file + diff --git a/Foundation/testsuite/TestSuite_vs170.vcxproj.filters b/Foundation/testsuite/TestSuite_vs170.vcxproj.filters index e26995529d..e20e0fbfc9 100644 --- a/Foundation/testsuite/TestSuite_vs170.vcxproj.filters +++ b/Foundation/testsuite/TestSuite_vs170.vcxproj.filters @@ -603,6 +603,8 @@ Threading\Source Files + + Processes\Source Files Logging\Source Files @@ -1031,8 +1033,10 @@ Threading\Header Files + + Processes\Header Files Logging\Header Files - \ No newline at end of file + diff --git a/Foundation/testsuite/src/FileStreamRWLockTest.cpp b/Foundation/testsuite/src/FileStreamRWLockTest.cpp new file mode 100644 index 0000000000..2752709ee8 --- /dev/null +++ b/Foundation/testsuite/src/FileStreamRWLockTest.cpp @@ -0,0 +1,264 @@ +// +// FileStreamRWLockTest.cpp +// +// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#include "FileStreamRWLockTest.h" +#include "CppUnit/TestCaller.h" +#include "CppUnit/TestSuite.h" +#include "Poco/FileStreamRWLock.h" +#include "Poco/FileStream.h" +#include "Poco/TemporaryFile.h" +#include "Poco/Thread.h" +#include "Poco/Runnable.h" +#include "Poco/ProcessRunner.h" +#include "Poco/Path.h" +#include "Poco/Stopwatch.h" + + +using Poco::FileStreamRWLock; +using Poco::Thread; +using Poco::Runnable; +using Poco::FileStream; +using Poco::TemporaryFile; +using Poco::ProcessRunner; +using Poco::Path; +using Poco::Stopwatch; + +static std::string getTestAppName() +{ + std::string name("TestApp"); + std::string ext; +#if POCO_OS == POCO_OS_WINDOWS_NT + ext = ".exe"; +#endif // POCO_OS == POCO_OS_WINDOWS_NT + +#if defined(_DEBUG) + Poco::File testapp(name + ext); + if (!testapp.exists()) + { + name += "d"; + } +#endif + Poco::Path testAppPath = Poco::Path::current(); + testAppPath.append(name + ext).makeFile(); + return testAppPath.toString(); +} + +#if POCO_OS == POCO_OS_WINDOWS_NT + +#include "Poco/File_WIN32U.h" + +HANDLE createFileWithRWAccess(const std::string &path) +{ + DWORD access = GENERIC_READ | GENERIC_WRITE; + DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + HANDLE handle = CreateFileA(path.c_str(), access, shareMode, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) + Poco::File::handleLastError(path); + + return handle; +} +#endif + +class FileStreamRWLockRunnable: public Runnable +{ +public: + FileStreamRWLockRunnable(std::string fileLock): _fileLock(std::move(fileLock)), _cmd(getTestAppName()) + { + } + + void run() override + { + std::string pid = Poco::format("%spid%ld.file", Path::tempHome(), Thread::currentOsTid()); + std::vector args; + args.push_back(std::string("--lock-file=").append(_fileLock)); + args.push_back(std::string("--pidfile=").append(pid)); + ProcessRunner pr(_cmd, args, pid, 0, 10, true); + while (pr.running()) + { + Poco::Thread::sleep(100); + } + int result = pr.result(); + _ok = (result >= 0); + } + + bool ok() const + { + return _ok; + } + +private: + std::string _fileLock; + bool _ok{false}; + std::string _cmd; +}; + + +class FileStreamRWTryLockRunnable: public Runnable +{ +public: + + FileStreamRWTryLockRunnable(std::string fileLock): _fileLock(std::move(fileLock)), _cmd(getTestAppName()) + { + } + + void run() override + { + std::string pid = Poco::format("%spid%ld.file", Path::tempHome(), Thread::currentOsTid()); + std::vector args; + args.push_back(std::string("--trylock-file=").append(_fileLock)); + args.push_back(std::string("--pidfile=").append(pid)); + ProcessRunner pr(_cmd, args, pid, 0, 10, true); + while (pr.running()) + { + Poco::Thread::sleep(100); + } + int result = pr.result(); + _ok = (result >= 0); + } + + bool ok() const + { + return _ok; + } + +private: + std::string _fileLock; + bool _ok{false}; + std::string _cmd; +}; + + +FileStreamRWLockTest::FileStreamRWLockTest(const std::string& name): CppUnit::TestCase(name) +{ +} + + +FileStreamRWLockTest::~FileStreamRWLockTest() +{ +} + + +void FileStreamRWLockTest::testFSLock() +{ + TemporaryFile fl; +#if POCO_OS != POCO_OS_WINDOWS_NT + FileStream fs(fl.path(), std::ios::in | std::ios::out | std::ios::binary); +#else + FileStream fs; + fs.openHandle(createFileWithRWAccess(fl.path()), std::ios::in | std::ios::out | std::ios::binary); +#endif // POCO_OS != POCO_OS_WINDOWS_NT + + Poco::Int32 i32 = 0; + fs.seekp(0, std::ios::beg); + fs.write((const char *)&i32, sizeof(i32)); + fs.flushToDisk(); + + const auto &path = fl.path(); + FileStreamRWLockRunnable r1(path); + FileStreamRWLockRunnable r2(path); + FileStreamRWLockRunnable r3(path); + FileStreamRWLockRunnable r4(path); + FileStreamRWLockRunnable r5(path); + Thread t1; + Thread t2; + Thread t3; + Thread t4; + Thread t5; + t1.start(r1); + t2.start(r2); + t3.start(r3); + t4.start(r4); + t5.start(r5); + t1.join(); + t2.join(); + t3.join(); + t4.join(); + t5.join(); + + fs.seekp(0, std::ios::beg); + fs.read((char *)&i32, sizeof(i32)); + assertEqual(500, i32); + assertTrue (r1.ok()); + assertTrue (r2.ok()); + assertTrue (r3.ok()); + assertTrue (r4.ok()); + assertTrue (r5.ok()); +} + + +void FileStreamRWLockTest::testFSTryLock() +{ + TemporaryFile fl; +#if POCO_OS != POCO_OS_WINDOWS_NT + FileStream fs(fl.path(), std::ios::in | std::ios::out | std::ios::binary); +#else + FileStream fs; + fs.openHandle(createFileWithRWAccess(fl.path()), std::ios::in | std::ios::out | std::ios::binary); +#endif // POCO_OS != POCO_OS_WINDOWS_NT + + Poco::Int32 i32 = 0; + fs.seekp(0, std::ios::beg); + fs.write((const char *)&i32, sizeof(i32)); + fs.flushToDisk(); + + const auto &path = fl.path(); + FileStreamRWTryLockRunnable r1(path); + FileStreamRWTryLockRunnable r2(path); + FileStreamRWTryLockRunnable r3(path); + FileStreamRWTryLockRunnable r4(path); + FileStreamRWTryLockRunnable r5(path); + Thread t1; + Thread t2; + Thread t3; + Thread t4; + Thread t5; + t1.start(r1); + t2.start(r2); + t3.start(r3); + t4.start(r4); + t5.start(r5); + t1.join(); + t2.join(); + t3.join(); + t4.join(); + t5.join(); + + fs.seekp(0, std::ios::beg); + fs.read((char *)&i32, sizeof(i32)); + assertEqual(500, i32); + assertTrue (r1.ok()); + assertTrue (r2.ok()); + assertTrue (r3.ok()); + assertTrue (r4.ok()); + assertTrue (r5.ok()); +} + + +void FileStreamRWLockTest::setUp() +{ +} + + +void FileStreamRWLockTest::tearDown() +{ +} + + +CppUnit::Test* FileStreamRWLockTest::suite() +{ + CppUnit::TestSuite* pSuite = new CppUnit::TestSuite("FileStreamRWLockTest"); + + CppUnit_addLongTest(pSuite, FileStreamRWLockTest, testFSLock); + CppUnit_addLongTest(pSuite, FileStreamRWLockTest, testFSTryLock); + + return pSuite; +} diff --git a/Foundation/testsuite/src/FileStreamRWLockTest.h b/Foundation/testsuite/src/FileStreamRWLockTest.h new file mode 100644 index 0000000000..86d8421ffc --- /dev/null +++ b/Foundation/testsuite/src/FileStreamRWLockTest.h @@ -0,0 +1,39 @@ +// +// FileStreamRWLockTest.h +// +// Definition of the FileStreamRWLockTest class. +// +// Copyright (c) 2004-2006, Applied Informatics Software Engineering GmbH. +// and Contributors. +// +// SPDX-License-Identifier: BSL-1.0 +// + + +#ifndef FileStreamRWLockTest_INCLUDED +#define FileStreamRWLockTest_INCLUDED + + +#include "Poco/Foundation.h" +#include "CppUnit/TestCase.h" + + +class FileStreamRWLockTest: public CppUnit::TestCase +{ +public: + FileStreamRWLockTest(const std::string& name); + ~FileStreamRWLockTest(); + + void testFSLock(); + void testFSTryLock(); + + void setUp(); + void tearDown(); + + static CppUnit::Test* suite(); + +private: +}; + + +#endif // FileStreamRWLockTest_INCLUDED diff --git a/Foundation/testsuite/src/ProcessesTestSuite.cpp b/Foundation/testsuite/src/ProcessesTestSuite.cpp index ab0f12b594..dd9671aaab 100644 --- a/Foundation/testsuite/src/ProcessesTestSuite.cpp +++ b/Foundation/testsuite/src/ProcessesTestSuite.cpp @@ -14,6 +14,7 @@ #include "NamedEventTest.h" #include "SharedMemoryTest.h" #include "ProcessRunnerTest.h" +#include "FileStreamRWLockTest.h" CppUnit::Test* ProcessesTestSuite::suite() @@ -25,6 +26,7 @@ CppUnit::Test* ProcessesTestSuite::suite() pSuite->addTest(NamedEventTest::suite()); pSuite->addTest(SharedMemoryTest::suite()); pSuite->addTest(ProcessRunnerTest::suite()); + pSuite->addTest(FileStreamRWLockTest::suite()); return pSuite; } diff --git a/Foundation/testsuite/src/TestApp.cpp b/Foundation/testsuite/src/TestApp.cpp index ff62cd6f21..8c0d0ac8ce 100644 --- a/Foundation/testsuite/src/TestApp.cpp +++ b/Foundation/testsuite/src/TestApp.cpp @@ -19,10 +19,13 @@ #include #include +#include "Poco/FileStreamRWLock.h" + #if defined(POCO_OS_FAMILY_UNIX) #include "Poco/Thread.h" #include "Poco/Runnable.h" #elif defined(POCO_OS_FAMILY_WINDOWS) +#include "Poco/Thread.h" #include "Poco/Process.h" #include "Poco/Event.h" #include "Poco/NamedEvent.h" @@ -98,6 +101,7 @@ class MyApp _terminate.wait(); _terminated.set(); } + #elif defined(POCO_OS_FAMILY_UNIX) void waitForTerminationRequest() { @@ -156,6 +160,22 @@ class MyApp #if defined(POCO_OS_FAMILY_WINDOWS) Poco::Event MyApp::_terminated; Poco::NamedEvent MyApp::_terminate(Poco::ProcessImpl::terminationEventName(Poco::Process::id())); + +#include "Poco/File.h" +#include "Poco/File_WIN32U.h" + +HANDLE openFileWithRWAccess(const std::string& path) +{ + DWORD access = GENERIC_READ | GENERIC_WRITE; + DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + HANDLE handle = CreateFileA(path.c_str(), access, shareMode, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (handle == INVALID_HANDLE_VALUE) + Poco::File::handleLastError(path); + + return handle; +} #endif int main(int argc, char** argv) @@ -222,6 +242,153 @@ int main(int argc, char** argv) } } #endif + else if (argc > 2 && arg.find("--lock-file") != std::string::npos && std::string(argv[2]).find("--pidfile") != std::string::npos) + { + std::string pidfArg = std::string(argv[2]); + std::unique_ptr pidF; + size_t equals_pos = pidfArg.find('='); + if (equals_pos != std::string::npos) + { + std::string pidPath = pidfArg.substr(equals_pos + 1); + pidF = std::make_unique(pidPath, true); + } + if (pidF == nullptr) + { + return -1; + } + equals_pos = arg.find('='); + if (equals_pos != std::string::npos) + { + std::string fl = arg.substr(equals_pos + 1); +#if POCO_OS != POCO_OS_WINDOWS_NT + FileStream fs(fl, std::ios::in | std::ios::out | std::ios::binary); +#else + FileStream fs; + fs.openHandle(openFileWithRWAccess(fl), std::ios::in | std::ios::out | std::ios::binary); +#endif // POCO_OS != POCO_OS_WINDOWS_NT + Poco::Int32 ok = 1; + Poco::Int32 lastCount = 0; + Poco::Int32 counter = 0; + FileStreamRWLock lock(fs, 0, sizeof(counter)); + for (int i = 0; i < 100; ++i) + { + lock.readLock(); + fs.seekg(0, std::ios::beg); + fs.read((char *)&counter, sizeof(counter)); + lastCount = counter; + for (int k = 0; k < 100; ++k) + { + if (counter != lastCount) ok = -1; + } + lock.unlock(); + lock.writeLock(); + for (int k = 0; k < 100; ++k) + { + counter = 0; + fs.seekg(0, std::ios::beg); + fs.read((char *)&counter, sizeof(counter)); + --counter; + fs.seekp(0, std::ios::beg); + fs.write((char *)&counter, sizeof(counter)); + fs.flushToDisk(); + } + for (int k = 0; k < 100; ++k) + { + fs.seekg(0, std::ios::beg); + fs.read((char *)&counter, sizeof(counter)); + ++counter; + fs.seekp(0, std::ios::beg); + fs.write((char *)&counter, sizeof(counter)); + fs.flushToDisk(); + } + fs.seekg(0, std::ios::beg); + fs.read((char *)&counter, sizeof(counter)); + ++counter; + fs.seekp(0, std::ios::beg); + fs.write((char *)&counter, sizeof(counter)); + fs.flushToDisk(); + if (counter <= lastCount) ok = -1; + lock.unlock(); + } + return ok * counter; + } + return -1; + } + else if (argc > 2 && arg.find("--trylock-file") != std::string::npos && std::string(argv[2]).find("--pidfile") != std::string::npos) + { + std::string pidfArg = std::string(argv[2]); + std::unique_ptr pidF; + size_t equals_pos = pidfArg.find('='); + if (equals_pos != std::string::npos) + { + std::string pidPath = pidfArg.substr(equals_pos + 1); + pidF = std::make_unique(pidPath, true); + } + if (pidF == nullptr) + { + return -1; + } + equals_pos = arg.find('='); + if (equals_pos != std::string::npos) + { + std::string fl = arg.substr(equals_pos + 1); +#if POCO_OS != POCO_OS_WINDOWS_NT + FileStream fs(fl, std::ios::in | std::ios::out | std::ios::binary); +#else + FileStream fs; + fs.openHandle(openFileWithRWAccess(fl), std::ios::in | std::ios::out | std::ios::binary); +#endif // POCO_OS != POCO_OS_WINDOWS_NT + Poco::Int32 ok = 1; + Poco::Int32 lastCount = 0; + Poco::Int32 counter = 0; + FileStreamRWLock lock(fs, 0, sizeof(counter)); + for (int i = 0; i < 100; ++i) + { + while (!lock.tryReadLock()) Thread::yield(); + fs.seekg(0, std::ios::beg); + fs.read((char *)&counter, sizeof(counter)); + lastCount = counter; + for (int k = 0; k < 100; ++k) + { + if (counter != lastCount) ok = -1; + Thread::yield(); + } + lock.unlock(); + while (!lock.tryWriteLock()) Thread::yield(); + for (int k = 0; k < 100; ++k) + { + counter = 0; + fs.seekg(0, std::ios::beg); + fs.read((char *)&counter, sizeof(counter)); + --counter; + fs.seekp(0, std::ios::beg); + fs.write((char *)&counter, sizeof(counter)); + fs.flushToDisk(); + Thread::yield(); + } + for (int k = 0; k < 100; ++k) + { + fs.seekg(0, std::ios::beg); + fs.read((char *)&counter, sizeof(counter)); + ++counter; + fs.seekp(0, std::ios::beg); + fs.write((char *)&counter, sizeof(counter)); + fs.flushToDisk(); + Thread::yield(); + } + fs.seekg(0, std::ios::beg); + fs.read((char *)&counter, sizeof(counter)); + ++counter; + fs.seekp(0, std::ios::beg); + fs.write((char *)&counter, sizeof(counter)); + fs.flushToDisk(); + if (counter <= lastCount) ok = -1; + lock.unlock(); + } + return ok * counter; + } + return -1; + } } return argc - 1; }