Add native system semaphore and Windows shared memory (#7212)
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
set(COMMON_SRCS
|
||||
RemotePluginBase.cpp
|
||||
SharedMemory.cpp
|
||||
SystemSemaphore.cpp
|
||||
)
|
||||
|
||||
foreach(SRC ${COMMON_SRCS})
|
||||
|
||||
@@ -23,45 +23,44 @@
|
||||
|
||||
#include "SharedMemory.h"
|
||||
|
||||
#include <system_error>
|
||||
#include <utility>
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
#include "RaiiHelpers.h"
|
||||
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
|
||||
# include <system_error>
|
||||
#
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE)
|
||||
# include <sys/mman.h>
|
||||
# include <sys/stat.h>
|
||||
# include <fcntl.h>
|
||||
#
|
||||
# include "RaiiHelpers.h"
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <stdexcept>
|
||||
#
|
||||
# include <QtGlobal>
|
||||
# include <QSharedMemory>
|
||||
# error "No shared memory implementation available"
|
||||
#endif
|
||||
|
||||
|
||||
namespace lmms::detail {
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
|
||||
|
||||
#if _POSIX_SHARED_MEMORY_OBJECTS > 0 || defined(LMMS_BUILD_APPLE)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), message};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v<F>)
|
||||
{
|
||||
int result;
|
||||
do
|
||||
{
|
||||
result = function();
|
||||
while (true) {
|
||||
const auto result = function();
|
||||
if (result != -1 || errno != EINTR) { return result; }
|
||||
}
|
||||
while (result == -1 && errno == EINTR);
|
||||
return result;
|
||||
}
|
||||
|
||||
void deleteFileDescriptor(int fd) noexcept { retryWhileInterrupted([fd]() noexcept { return close(fd); }); }
|
||||
@@ -82,22 +81,15 @@ public:
|
||||
const auto fd = FileDescriptor{
|
||||
retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), openFlags, 0); })
|
||||
};
|
||||
if (!fd)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"};
|
||||
}
|
||||
if (!fd) { throwSystemError("SharedMemoryImpl: shm_open() failed"); }
|
||||
|
||||
auto stat = (struct stat){};
|
||||
if (fstat(fd.get(), &stat) == -1)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: fstat() failed"};
|
||||
}
|
||||
if (fstat(fd.get(), &stat) == -1) { throwSystemError("SharedMemoryImpl: fstat() failed"); }
|
||||
m_size = stat.st_size;
|
||||
|
||||
const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE;
|
||||
m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0);
|
||||
if (m_mapping == MAP_FAILED)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"};
|
||||
}
|
||||
if (m_mapping == MAP_FAILED) { throwSystemError("SharedMemoryImpl: mmap() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
|
||||
@@ -107,21 +99,16 @@ public:
|
||||
const auto fd = FileDescriptor{
|
||||
retryWhileInterrupted([&]() noexcept { return shm_open(m_key.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600); })
|
||||
};
|
||||
if (fd.get() == -1)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: shm_open() failed"};
|
||||
}
|
||||
if (fd.get() == -1) { throwSystemError("SharedMemoryImpl: shm_open() failed"); }
|
||||
m_object.reset(m_key.c_str());
|
||||
if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: ftruncate() failed"};
|
||||
|
||||
if (retryWhileInterrupted([&]() noexcept { return ftruncate(fd.get(), m_size); }) == -1) {
|
||||
throwSystemError("SharedMemoryImpl: ftruncate() failed");
|
||||
}
|
||||
|
||||
const auto mappingProtection = readOnly ? PROT_READ : PROT_READ | PROT_WRITE;
|
||||
m_mapping = mmap(nullptr, m_size, mappingProtection, MAP_SHARED, fd.get(), 0);
|
||||
if (m_mapping == MAP_FAILED)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), "SharedMemoryImpl: mmap() failed"};
|
||||
}
|
||||
if (m_mapping == MAP_FAILED) { throwSystemError("SharedMemoryImpl: mmap() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
|
||||
@@ -132,7 +119,7 @@ public:
|
||||
munmap(m_mapping, m_size);
|
||||
}
|
||||
|
||||
void* get() { return m_mapping; }
|
||||
auto get() const noexcept -> void* { return m_mapping; }
|
||||
|
||||
private:
|
||||
std::string m_key;
|
||||
@@ -141,38 +128,67 @@ private:
|
||||
ShmObject m_object;
|
||||
};
|
||||
|
||||
#else
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
|
||||
namespace {
|
||||
|
||||
auto sizeToHighAndLow(std::size_t size) -> std::pair<DWORD, DWORD>
|
||||
{
|
||||
if constexpr(sizeof(std::size_t) <= sizeof(DWORD)) {
|
||||
return {0, size};
|
||||
} else {
|
||||
return {static_cast<DWORD>(size >> 32), static_cast<DWORD>(size)};
|
||||
}
|
||||
}
|
||||
|
||||
[[noreturn]] void throwLastError(const char* message)
|
||||
{
|
||||
throw std::system_error{static_cast<int>(GetLastError()), std::system_category(), message};
|
||||
}
|
||||
|
||||
using UniqueHandle = UniqueNullableResource<HANDLE, nullptr, CloseHandle>;
|
||||
using FileView = UniqueNullableResource<void*, nullptr, UnmapViewOfFile>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SharedMemoryImpl
|
||||
{
|
||||
public:
|
||||
SharedMemoryImpl(const std::string& key, bool readOnly) :
|
||||
m_shm{QString::fromStdString(key)}
|
||||
SharedMemoryImpl(const std::string& key, bool readOnly)
|
||||
{
|
||||
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
|
||||
if (!m_shm.attach(mode))
|
||||
{
|
||||
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::attach() failed"};
|
||||
}
|
||||
const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE;
|
||||
m_mapping.reset(OpenFileMappingA(access, false, key.c_str()));
|
||||
if (!m_mapping) { throwLastError("SharedMemoryImpl: OpenFileMappingA() failed"); }
|
||||
|
||||
m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0));
|
||||
if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
|
||||
m_shm{QString::fromStdString(key)}
|
||||
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly)
|
||||
{
|
||||
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
|
||||
if (!m_shm.create(size, mode))
|
||||
{
|
||||
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::create() failed"};
|
||||
const auto [high, low] = sizeToHighAndLow(size);
|
||||
m_mapping.reset(CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, high, low, key.c_str()));
|
||||
// This constructor is supposed to create a new shared memory object,
|
||||
// but passing the name of an existing object causes CreateFileMappingA
|
||||
// to succeed and return a handle to that object. Thus we have to check
|
||||
// GetLastError() too.
|
||||
if (!m_mapping || GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
throwLastError("SharedMemoryImpl: CreateFileMappingA() failed");
|
||||
}
|
||||
|
||||
const auto access = readOnly ? FILE_MAP_READ : FILE_MAP_WRITE;
|
||||
m_view.reset(MapViewOfFile(m_mapping.get(), access, 0, 0, 0));
|
||||
if (!m_view) { throwLastError("SharedMemoryImpl: MapViewOfFile() failed"); }
|
||||
}
|
||||
|
||||
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
|
||||
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
|
||||
auto operator=(const SharedMemoryImpl&) -> SharedMemoryImpl& = delete;
|
||||
|
||||
void* get() { return m_shm.data(); }
|
||||
auto get() const noexcept -> void* { return m_view.get(); }
|
||||
|
||||
private:
|
||||
QSharedMemory m_shm;
|
||||
UniqueHandle m_mapping;
|
||||
FileView m_view;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -196,7 +212,7 @@ SharedMemoryData::~SharedMemoryData() = default;
|
||||
SharedMemoryData::SharedMemoryData(SharedMemoryData&& other) noexcept :
|
||||
m_key{std::move(other.m_key)},
|
||||
m_impl{std::move(other.m_impl)},
|
||||
m_ptr{other.m_ptr}
|
||||
m_ptr{std::exchange(other.m_ptr, nullptr)}
|
||||
{ }
|
||||
|
||||
} // namespace lmms::detail
|
||||
|
||||
181
src/common/SystemSemaphore.cpp
Normal file
181
src/common/SystemSemaphore.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* SystemSemaphore.cpp
|
||||
*
|
||||
* Copyright (c) 2024 Dominic Clark
|
||||
*
|
||||
* This file is part of LMMS - https://lmms.io
|
||||
*
|
||||
* 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 2 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 (see COPYING); if not, write to the
|
||||
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "SystemSemaphore.h"
|
||||
|
||||
#include <limits>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "lmmsconfig.h"
|
||||
#include "RaiiHelpers.h"
|
||||
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE)
|
||||
# include <fcntl.h>
|
||||
# include <semaphore.h>
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
# include <windows.h>
|
||||
#else
|
||||
# error "No system semaphore implementation available"
|
||||
#endif
|
||||
|
||||
namespace lmms {
|
||||
|
||||
namespace detail {
|
||||
|
||||
#if (_POSIX_SEMAPHORES > 0 && !defined(__MINGW32__)) || defined(LMMS_BUILD_APPLE)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{errno, std::generic_category(), message};
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
auto retryWhileInterrupted(F&& function, std::invoke_result_t<F> error = -1)
|
||||
noexcept(std::is_nothrow_invocable_v<F>) -> auto
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
const auto result = function();
|
||||
if (result != error || errno != EINTR) { return result; }
|
||||
}
|
||||
}
|
||||
|
||||
using UniqueSemaphore = UniqueNullableResource<const char*, nullptr, sem_unlink>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SystemSemaphoreImpl
|
||||
{
|
||||
public:
|
||||
SystemSemaphoreImpl(const std::string& key, unsigned int value) :
|
||||
m_key{"/" + key},
|
||||
m_sem{retryWhileInterrupted([&]() noexcept {
|
||||
return sem_open(m_key.c_str(), O_CREAT | O_EXCL, 0600, value);
|
||||
}, SEM_FAILED)}
|
||||
{
|
||||
if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); }
|
||||
m_ownedSemaphore.reset(m_key.c_str());
|
||||
}
|
||||
|
||||
explicit SystemSemaphoreImpl(const std::string& key) :
|
||||
m_key{"/" + key},
|
||||
m_sem{retryWhileInterrupted([&]() noexcept {
|
||||
return sem_open(m_key.c_str(), 0);
|
||||
}, SEM_FAILED)}
|
||||
{
|
||||
if (m_sem == SEM_FAILED) { throwSystemError("SystemSemaphoreImpl: sem_open() failed"); }
|
||||
}
|
||||
|
||||
~SystemSemaphoreImpl()
|
||||
{
|
||||
// We can't use `UniqueNullableResource` for `m_sem`, as the null value
|
||||
// (`SEM_FAILED`) is not a constant expression on macOS (it's defined as
|
||||
// `(sem_t*) -1`), so can't be used as a template parameter.
|
||||
sem_close(m_sem);
|
||||
}
|
||||
|
||||
auto acquire() noexcept -> bool
|
||||
{
|
||||
return retryWhileInterrupted([&]() noexcept {
|
||||
return sem_wait(m_sem);
|
||||
}) == 0;
|
||||
}
|
||||
|
||||
auto release() noexcept -> bool { return sem_post(m_sem) == 0; }
|
||||
|
||||
private:
|
||||
std::string m_key;
|
||||
sem_t* m_sem;
|
||||
UniqueSemaphore m_ownedSemaphore;
|
||||
};
|
||||
|
||||
#elif defined(LMMS_BUILD_WIN32)
|
||||
|
||||
namespace {
|
||||
|
||||
[[noreturn]] void throwSystemError(const char* message)
|
||||
{
|
||||
throw std::system_error{static_cast<int>(GetLastError()), std::system_category(), message};
|
||||
}
|
||||
|
||||
using UniqueHandle = UniqueNullableResource<HANDLE, nullptr, CloseHandle>;
|
||||
|
||||
} // namespace
|
||||
|
||||
class SystemSemaphoreImpl
|
||||
{
|
||||
public:
|
||||
SystemSemaphoreImpl(const std::string& key, unsigned int value) :
|
||||
m_sem{CreateSemaphoreA(nullptr, value, std::numeric_limits<LONG>::max(), key.c_str())}
|
||||
{
|
||||
if (!m_sem || GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
throwSystemError("SystemSemaphoreImpl: CreateSemaphoreA failed");
|
||||
}
|
||||
}
|
||||
|
||||
explicit SystemSemaphoreImpl(const std::string& key) :
|
||||
m_sem{OpenSemaphoreA(SEMAPHORE_MODIFY_STATE | SYNCHRONIZE, false, key.c_str())}
|
||||
{
|
||||
if (!m_sem) { throwSystemError("SystemSemaphoreImpl: OpenSemaphoreA failed"); }
|
||||
}
|
||||
|
||||
auto acquire() noexcept -> bool { return WaitForSingleObject(m_sem.get(), INFINITE) == WAIT_OBJECT_0; }
|
||||
auto release() noexcept -> bool { return ReleaseSemaphore(m_sem.get(), 1, nullptr); }
|
||||
|
||||
private:
|
||||
UniqueHandle m_sem;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace detail
|
||||
|
||||
SystemSemaphore::SystemSemaphore() noexcept = default;
|
||||
|
||||
SystemSemaphore::SystemSemaphore(std::string key, unsigned int value) :
|
||||
m_key{std::move(key)},
|
||||
m_impl{std::make_unique<detail::SystemSemaphoreImpl>(m_key, value)}
|
||||
{}
|
||||
|
||||
SystemSemaphore::SystemSemaphore(std::string key) :
|
||||
m_key{std::move(key)},
|
||||
m_impl{std::make_unique<detail::SystemSemaphoreImpl>(m_key)}
|
||||
{}
|
||||
|
||||
SystemSemaphore::~SystemSemaphore() = default;
|
||||
|
||||
SystemSemaphore::SystemSemaphore(SystemSemaphore&& other) noexcept = default;
|
||||
auto SystemSemaphore::operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore& = default;
|
||||
|
||||
auto SystemSemaphore::acquire() noexcept -> bool { return m_impl->acquire(); }
|
||||
auto SystemSemaphore::release() noexcept -> bool { return m_impl->release(); }
|
||||
|
||||
} // namespace lmms
|
||||
Reference in New Issue
Block a user