Add native system semaphore and Windows shared memory (#7212)
This commit is contained in:
24
.github/workflows/build.yml
vendored
24
.github/workflows/build.yml
vendored
@@ -218,7 +218,6 @@ jobs:
|
||||
name: msvc-${{ matrix.arch }}
|
||||
runs-on: windows-2019
|
||||
env:
|
||||
qt-version: '5.15.2'
|
||||
CCACHE_MAXSIZE: 0
|
||||
CCACHE_NOCOMPRESS: 1
|
||||
steps:
|
||||
@@ -246,22 +245,21 @@ jobs:
|
||||
path: ~\AppData\Local\ccache
|
||||
- name: Install tools
|
||||
run: choco install ccache
|
||||
- name: Install 64-bit Qt
|
||||
if: matrix.arch == 'x64'
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946
|
||||
with:
|
||||
version: ${{ env.qt-version }}
|
||||
arch: win64_msvc2019_64
|
||||
version: '5.15.2'
|
||||
arch: |-
|
||||
${{
|
||||
fromJSON('
|
||||
{
|
||||
"x86": "win32_msvc2019",
|
||||
"x64": "win64_msvc2019_64"
|
||||
}
|
||||
')[matrix.arch]
|
||||
}}
|
||||
archives: qtbase qtsvg qttools
|
||||
cache: true
|
||||
- name: Install 32-bit Qt
|
||||
uses: jurplel/install-qt-action@b3ea5275e37b734d027040e2c7fe7a10ea2ef946
|
||||
with:
|
||||
version: ${{ env.qt-version }}
|
||||
arch: win32_msvc2019
|
||||
archives: qtbase qtsvg qttools
|
||||
cache: true
|
||||
set-env: ${{ matrix.arch == 'x86' }}
|
||||
- name: Set up build environment
|
||||
uses: ilammy/msvc-dev-cmd@cec98b9d092141f74527d0afa6feb2af698cfe89
|
||||
with:
|
||||
|
||||
@@ -41,10 +41,6 @@
|
||||
#ifdef LMMS_HAVE_PROCESS_H
|
||||
#include <process.h>
|
||||
#endif
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QSystemSemaphore>
|
||||
#include <QUuid>
|
||||
#else // !(LMMS_HAVE_SYS_IPC_H && LMMS_HAVE_SEMAPHORE_H)
|
||||
#ifdef LMMS_HAVE_UNISTD_H
|
||||
#include <unistd.h>
|
||||
@@ -75,6 +71,7 @@
|
||||
#include <QProcess>
|
||||
#include <QThread>
|
||||
#include <QString>
|
||||
#include <QUuid>
|
||||
|
||||
#ifndef SYNC_WITH_SHM_FIFO
|
||||
#include <poll.h>
|
||||
@@ -85,6 +82,7 @@
|
||||
|
||||
#ifdef SYNC_WITH_SHM_FIFO
|
||||
#include "SharedMemory.h"
|
||||
#include "SystemSemaphore.h"
|
||||
#endif
|
||||
|
||||
namespace lmms
|
||||
@@ -120,12 +118,11 @@ class shmFifo
|
||||
} ;
|
||||
|
||||
public:
|
||||
#ifndef BUILD_REMOTE_PLUGIN_CLIENT
|
||||
// constructor for master-side
|
||||
shmFifo() :
|
||||
m_invalid( false ),
|
||||
m_master( true ),
|
||||
m_dataSem( QString() ),
|
||||
m_messageSem( QString() ),
|
||||
m_lockDepth( 0 )
|
||||
{
|
||||
m_data.create(QUuid::createUuid().toString().toStdString());
|
||||
@@ -133,26 +130,21 @@ public:
|
||||
static int k = 0;
|
||||
m_data->dataSem.semKey = ( getpid()<<10 ) + ++k;
|
||||
m_data->messageSem.semKey = ( getpid()<<10 ) + ++k;
|
||||
m_dataSem.setKey( QString::number( m_data->dataSem.semKey ),
|
||||
1, QSystemSemaphore::Create );
|
||||
m_messageSem.setKey( QString::number(
|
||||
m_data->messageSem.semKey ),
|
||||
0, QSystemSemaphore::Create );
|
||||
m_dataSem = SystemSemaphore{std::to_string(m_data->dataSem.semKey), 1u};
|
||||
m_messageSem = SystemSemaphore{std::to_string(m_data->messageSem.semKey), 0u};
|
||||
}
|
||||
#endif
|
||||
|
||||
// constructor for remote-/client-side - use _shm_key for making up
|
||||
// the connection to master
|
||||
shmFifo(const std::string& shmKey) :
|
||||
m_invalid( false ),
|
||||
m_master( false ),
|
||||
m_dataSem( QString() ),
|
||||
m_messageSem( QString() ),
|
||||
m_lockDepth( 0 )
|
||||
{
|
||||
m_data.attach(shmKey);
|
||||
m_dataSem.setKey( QString::number( m_data->dataSem.semKey ) );
|
||||
m_messageSem.setKey( QString::number(
|
||||
m_data->messageSem.semKey ) );
|
||||
m_dataSem = SystemSemaphore{std::to_string(m_data->dataSem.semKey)};
|
||||
m_messageSem = SystemSemaphore{std::to_string(m_data->messageSem.semKey)};
|
||||
}
|
||||
|
||||
inline bool isInvalid() const
|
||||
@@ -336,11 +328,10 @@ private:
|
||||
volatile bool m_invalid;
|
||||
bool m_master;
|
||||
SharedMemory<shmData> m_data;
|
||||
QSystemSemaphore m_dataSem;
|
||||
QSystemSemaphore m_messageSem;
|
||||
SystemSemaphore m_dataSem;
|
||||
SystemSemaphore m_messageSem;
|
||||
std::atomic_int m_lockDepth;
|
||||
|
||||
} ;
|
||||
};
|
||||
#endif // SYNC_WITH_SHM_FIFO
|
||||
|
||||
|
||||
|
||||
61
include/SystemSemaphore.h
Normal file
61
include/SystemSemaphore.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* SystemSemaphore.h
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef LMMS_SYSTEM_SEMAPHORE_H
|
||||
#define LMMS_SYSTEM_SEMAPHORE_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace lmms {
|
||||
|
||||
namespace detail {
|
||||
|
||||
class SystemSemaphoreImpl;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
class SystemSemaphore
|
||||
{
|
||||
public:
|
||||
SystemSemaphore() noexcept;
|
||||
SystemSemaphore(std::string key, unsigned int value);
|
||||
explicit SystemSemaphore(std::string key);
|
||||
~SystemSemaphore();
|
||||
|
||||
SystemSemaphore(SystemSemaphore&& other) noexcept;
|
||||
auto operator=(SystemSemaphore&& other) noexcept -> SystemSemaphore&;
|
||||
|
||||
auto acquire() noexcept -> bool;
|
||||
auto release() noexcept -> bool;
|
||||
|
||||
auto key() const noexcept -> const std::string& { return m_key; }
|
||||
|
||||
private:
|
||||
std::string m_key;
|
||||
std::unique_ptr<detail::SystemSemaphoreImpl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace lmms
|
||||
|
||||
#endif // LMMS_SYSTEM_SEMAPHORE_H
|
||||
@@ -22,6 +22,9 @@ FOREACH( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} )
|
||||
SET("CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
|
||||
ENDFOREACH()
|
||||
|
||||
# Import of windows.h breaks min()/max()
|
||||
add_definitions(-DNOMINMAX)
|
||||
|
||||
ADD_SUBDIRECTORY("${LMMS_SOURCE_DIR}/src/common" common)
|
||||
|
||||
if(NOT IS_WIN)
|
||||
@@ -64,12 +67,6 @@ if(MSVC)
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
if(WIN32)
|
||||
find_package(Qt5Core REQUIRED)
|
||||
target_link_libraries(${EXE_NAME} Qt5::Core)
|
||||
endif()
|
||||
|
||||
if(IS_MINGW)
|
||||
SET(CMAKE_REQUIRED_FLAGS "-std=c++17")
|
||||
|
||||
@@ -96,7 +93,9 @@ if(LMMS_BUILD_WIN32)
|
||||
set(NOOP_COMMAND "${CMAKE_COMMAND}" "-E" "echo")
|
||||
endif()
|
||||
if(STRIP)
|
||||
set(STRIP_COMMAND "$<IF:$<TARGET:Debug,RelWithDebInfo>,${NOOP_COMMAND},${STRIP}>")
|
||||
# TODO CMake 3.19: Now that CONFIG generator expressions support testing for
|
||||
# multiple configurations, combine the OR into a single CONFIG expression.
|
||||
set(STRIP_COMMAND "$<IF:$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>,${NOOP_COMMAND},${STRIP}>")
|
||||
else()
|
||||
set(STRIP_COMMAND "${NOOP_COMMAND}")
|
||||
endif()
|
||||
|
||||
@@ -17,23 +17,6 @@ ENDMACRO()
|
||||
IF(LMMS_BUILD_WIN32 AND NOT LMMS_BUILD_WIN64)
|
||||
ADD_SUBDIRECTORY(RemoteVstPlugin)
|
||||
ELSEIF(LMMS_BUILD_WIN64 AND MSVC)
|
||||
IF(NOT QT_32_PREFIX)
|
||||
SET(LMMS_MSVC_YEAR_FOR_QT ${LMMS_MSVC_YEAR})
|
||||
|
||||
if(LMMS_MSVC_YEAR_FOR_QT EQUAL 2019 AND Qt5_VERSION VERSION_LESS "5.15")
|
||||
SET(LMMS_MSVC_YEAR_FOR_QT 2017) # Qt only provides binaries for MSVC 2017, but 2019 is binary compatible
|
||||
endif()
|
||||
|
||||
GET_FILENAME_COMPONENT(QT_BIN_DIR ${QT_QMAKE_EXECUTABLE} DIRECTORY)
|
||||
SET(QT_32_PREFIX "${QT_BIN_DIR}/../../msvc${LMMS_MSVC_YEAR_FOR_QT}")
|
||||
ENDIF()
|
||||
|
||||
#TODO: qt5 installed using vcpkg: I don't know how to detect if the user built the x86 version of qt5 from here. At least not cleanly.
|
||||
#So for the moment, we'll allow the built.
|
||||
IF(NOT (IS_DIRECTORY ${QT_32_PREFIX} AND EXISTS ${QT_32_PREFIX}/bin/qmake.exe))
|
||||
MESSAGE(WARNING "No Qt 32 bit installation found at ${QT_32_PREFIX}. If you're using VCPKG you can ignore this message if you've built x86-windows version of qt5")
|
||||
ENDIF()
|
||||
|
||||
ExternalProject_Add(RemoteVstPlugin32
|
||||
"${EXTERNALPROJECT_ARGS}"
|
||||
CMAKE_GENERATOR "${LMMS_MSVC_GENERATOR}"
|
||||
@@ -42,7 +25,6 @@ ELSEIF(LMMS_BUILD_WIN64 AND MSVC)
|
||||
CMAKE_ARGS
|
||||
"${EXTERNALPROJECT_CMAKE_ARGS}"
|
||||
"-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}"
|
||||
"-DCMAKE_PREFIX_PATH=${QT_32_PREFIX}"
|
||||
)
|
||||
|
||||
INSTALL_EXTERNAL_PROJECT(RemoteVstPlugin32)
|
||||
|
||||
@@ -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