Add native system semaphore and Windows shared memory (#7212)

This commit is contained in:
Dominic Clark
2024-04-20 23:21:29 +01:00
committed by GitHub
parent df11a98902
commit bda042e1eb
8 changed files with 347 additions and 118 deletions

View File

@@ -1,6 +1,7 @@
set(COMMON_SRCS
RemotePluginBase.cpp
SharedMemory.cpp
SystemSemaphore.cpp
)
foreach(SRC ${COMMON_SRCS})

View File

@@ -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

View 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