Refactor shared memory (#6404)

This commit is contained in:
Dominic Clark
2022-05-28 13:10:45 +01:00
committed by GitHub
parent 3518d307f4
commit 2c8ffd0f16
20 changed files with 529 additions and 397 deletions

60
include/RaiiHelpers.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* RaiiHelpers.h
*
* Copyright (c) 2022 Dominic Clark <mrdomclark/at/gmail.com>
*
* 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 RAII_HELPERS_H
#define RAII_HELPERS_H
#include <cstddef>
#include <memory>
template<typename T, T Null>
class NullableResource
{
public:
NullableResource() = default;
NullableResource(std::nullptr_t) noexcept { }
NullableResource(T value) noexcept : m_value{value} { }
operator T() const noexcept { return m_value; }
explicit operator bool() const noexcept { return m_value != Null; }
friend bool operator==(NullableResource a, NullableResource b) noexcept { return a.m_value == b.m_value; }
friend bool operator==(NullableResource a, T b) noexcept { return a.m_value == b; }
friend bool operator==(T a, NullableResource b) noexcept { return a == b.m_value; }
friend bool operator!=(NullableResource a, NullableResource b) noexcept { return a.m_value != b.m_value; }
friend bool operator!=(NullableResource a, T b) noexcept { return a.m_value != b; }
friend bool operator!=(T a, NullableResource b) noexcept { return a != b.m_value; }
private:
T m_value = Null;
};
template<typename T, T Null, auto Deleter>
struct NullableResourceDeleter
{
using pointer = NullableResource<T, Null>;
void operator()(T value) const noexcept { Deleter(value); }
};
template<typename T, T Null, auto Deleter>
using UniqueNullableResource = std::unique_ptr<T, NullableResourceDeleter<T, Null, Deleter>>;
#endif // RAII_HELPERS_H

View File

@@ -26,6 +26,7 @@
#define REMOTE_PLUGIN_H
#include "RemotePluginBase.h"
#include "SharedMemory.h"
class RemotePlugin;
@@ -155,13 +156,9 @@ private:
QMutex m_commMutex;
bool m_splitChannels;
#ifdef USE_QT_SHMEM
QSharedMemory m_shmObj;
#else
int m_shmID;
#endif
size_t m_shmSize;
float * m_shm;
SharedMemory<float[]> m_audioBuffer;
std::size_t m_audioBufferSize;
int m_inputCount;
int m_outputCount;

View File

@@ -46,26 +46,12 @@
#include <QtGlobal>
#include <QSystemSemaphore>
#endif
#ifdef LMMS_HAVE_SYS_SHM_H
#include <sys/shm.h>
#include <QUuid>
#else
#ifdef LMMS_HAVE_UNISTD_H
#include <unistd.h>
#endif
#else
#define USE_QT_SHMEM
#include <QtGlobal>
#include <QSharedMemory>
#if !defined(LMMS_HAVE_SYS_TYPES_H) || defined(LMMS_BUILD_WIN32)
typedef int32_t key_t;
#endif
#endif
#ifdef LMMS_HAVE_LOCALE_H
#include <clocale>
@@ -100,6 +86,9 @@ typedef int32_t key_t;
#endif
#ifdef SYNC_WITH_SHM_FIFO
#include "SharedMemory.h"
// sometimes we need to exchange bigger messages (e.g. for VST parameter dumps)
// so set a usable value here
const int SHM_FIFO_SIZE = 512*1024;
@@ -120,8 +109,8 @@ class shmFifo
sem32_t dataSem; // semaphore for locking this
// FIFO management data
sem32_t messageSem; // semaphore for incoming messages
volatile int32_t startPtr; // current start of FIFO in memory
volatile int32_t endPtr; // current end of FIFO in memory
int32_t startPtr; // current start of FIFO in memory
int32_t endPtr; // current end of FIFO in memory
char data[SHM_FIFO_SIZE]; // actual data
} ;
@@ -130,33 +119,11 @@ public:
shmFifo() :
m_invalid( false ),
m_master( true ),
m_shmKey( 0 ),
#ifdef USE_QT_SHMEM
m_shmObj(),
#else
m_shmID( -1 ),
#endif
m_data( nullptr ),
m_dataSem( QString() ),
m_messageSem( QString() ),
m_lockDepth( 0 )
{
#ifdef USE_QT_SHMEM
do
{
m_shmObj.setKey( QString( "%1" ).arg( ++m_shmKey ) );
m_shmObj.create( sizeof( shmData ) );
} while( m_shmObj.error() != QSharedMemory::NoError );
m_data = (shmData *) m_shmObj.data();
#else
while( ( m_shmID = shmget( ++m_shmKey, sizeof( shmData ),
IPC_CREAT | IPC_EXCL | 0600 ) ) == -1 )
{
}
m_data = (shmData *) shmat( m_shmID, 0, 0 );
#endif
assert( m_data != nullptr );
m_data.create(QUuid::createUuid().toString().toStdString());
m_data->startPtr = m_data->endPtr = 0;
static int k = 0;
m_data->dataSem.semKey = ( getpid()<<10 ) + ++k;
@@ -170,51 +137,19 @@ public:
// constructor for remote-/client-side - use _shm_key for making up
// the connection to master
shmFifo( key_t _shm_key ) :
shmFifo(const std::string& shmKey) :
m_invalid( false ),
m_master( false ),
m_shmKey( 0 ),
#ifdef USE_QT_SHMEM
m_shmObj( QString::number( _shm_key ) ),
#else
m_shmID( shmget( _shm_key, 0, 0 ) ),
#endif
m_data( nullptr ),
m_dataSem( QString() ),
m_messageSem( QString() ),
m_lockDepth( 0 )
{
#ifdef USE_QT_SHMEM
if( m_shmObj.attach() )
{
m_data = (shmData *) m_shmObj.data();
}
#else
if( m_shmID != -1 )
{
m_data = (shmData *) shmat( m_shmID, 0, 0 );
}
#endif
assert( m_data != nullptr );
m_data.attach(shmKey);
m_dataSem.setKey( QString::number( m_data->dataSem.semKey ) );
m_messageSem.setKey( QString::number(
m_data->messageSem.semKey ) );
}
~shmFifo()
{
// master?
if( m_master )
{
#ifndef USE_QT_SHMEM
shmctl( m_shmID, IPC_RMID, nullptr );
#endif
}
#ifndef USE_QT_SHMEM
shmdt( m_data );
#endif
}
inline bool isInvalid() const
{
return m_invalid;
@@ -314,9 +249,9 @@ public:
}
inline int shmKey() const
const std::string& shmKey() const
{
return m_shmKey;
return m_data.key();
}
@@ -395,13 +330,7 @@ private:
volatile bool m_invalid;
bool m_master;
key_t m_shmKey;
#ifdef USE_QT_SHMEM
QSharedMemory m_shmObj;
#else
int m_shmID;
#endif
shmData * m_data;
SharedMemory<shmData> m_data;
QSystemSemaphore m_dataSem;
QSystemSemaphore m_messageSem;
std::atomic_int m_lockDepth;

View File

@@ -27,6 +27,8 @@
#include "RemotePluginBase.h"
#include <stdexcept>
#ifndef LMMS_BUILD_WIN32
# include <condition_variable>
# include <mutex>
@@ -36,18 +38,20 @@
# include <unistd.h>
#endif
#include "SharedMemory.h"
class RemotePluginClient : public RemotePluginBase
{
public:
#ifdef SYNC_WITH_SHM_FIFO
RemotePluginClient( key_t _shm_in, key_t _shm_out );
RemotePluginClient( const std::string& _shm_in, const std::string& _shm_out );
#else
RemotePluginClient( const char * socketPath );
#endif
virtual ~RemotePluginClient();
#ifdef USE_QT_SHMEM
VstSyncData * getQtVSTshm();
#endif
const VstSyncData* getVstSyncData();
virtual bool processMessage( const message & _m );
virtual void process( const sampleFrame * _in_buf,
@@ -57,11 +61,6 @@ public:
{
}
inline float * sharedMemory()
{
return m_shm;
}
virtual void updateSampleRate()
{
}
@@ -118,15 +117,12 @@ public:
private:
void setShmKey( key_t _key, int _size );
void setShmKey(const std::string& key);
void doProcessing();
#ifdef USE_QT_SHMEM
QSharedMemory m_shmObj;
QSharedMemory m_shmQtID;
#endif
VstSyncData * m_vstSyncData;
float * m_shm;
SharedMemory<float[]> m_audioBuffer;
SharedMemory<const VstSyncData> m_vstSyncShm;
const VstSyncData* m_vstSyncData;
int m_inputCount;
int m_outputCount;
@@ -177,18 +173,13 @@ private:
#endif
#ifdef SYNC_WITH_SHM_FIFO
RemotePluginClient::RemotePluginClient( key_t _shm_in, key_t _shm_out ) :
RemotePluginClient::RemotePluginClient( const std::string& _shm_in, const std::string& _shm_out ) :
RemotePluginBase( new shmFifo( _shm_in ), new shmFifo( _shm_out ) ),
#else
RemotePluginClient::RemotePluginClient( const char * socketPath ) :
RemotePluginBase(),
#endif
#ifdef USE_QT_SHMEM
m_shmObj(),
m_shmQtID( "/usr/bin/lmms" ),
#endif
m_vstSyncData( nullptr ),
m_shm( nullptr ),
m_inputCount( 0 ),
m_outputCount( 0 ),
m_sampleRate( 44100 ),
@@ -218,60 +209,23 @@ RemotePluginClient::RemotePluginClient( const char * socketPath ) :
}
#endif
#ifdef USE_QT_SHMEM
if( m_shmQtID.attach( QSharedMemory::ReadOnly ) )
try
{
m_vstSyncData = (VstSyncData *) m_shmQtID.data();
m_vstSyncShm.attach("usr_bin_lmms");
m_vstSyncData = m_vstSyncShm.get();
m_bufferSize = m_vstSyncData->m_bufferSize;
m_sampleRate = m_vstSyncData->m_sampleRate;
sendMessage( IdHostInfoGotten );
return;
}
#else
key_t key;
int m_shmID;
if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 )
catch (const std::runtime_error&)
{
perror( "RemotePluginClient::ftok" );
}
else
{ // connect to shared memory segment
if( ( m_shmID = shmget( key, 0, 0 ) ) == -1 )
// if attaching shared memory fails
sendMessage( IdSampleRateInformation );
sendMessage( IdBufferSizeInformation );
if( waitForMessage( IdBufferSizeInformation ).id
!= IdBufferSizeInformation )
{
perror( "RemotePluginClient::shmget" );
fprintf( stderr, "Could not get buffer size information\n" );
}
else
{ // attach segment
m_vstSyncData = (VstSyncData *)shmat(m_shmID, 0, 0);
if( m_vstSyncData == (VstSyncData *)( -1 ) )
{
perror( "RemotePluginClient::shmat" );
}
else
{
m_bufferSize = m_vstSyncData->m_bufferSize;
m_sampleRate = m_vstSyncData->m_sampleRate;
sendMessage( IdHostInfoGotten );
// detach segment
if( shmdt(m_vstSyncData) == -1 )
{
perror("RemotePluginClient::shmdt");
}
return;
}
}
}
#endif
// if attaching shared memory fails
sendMessage( IdSampleRateInformation );
sendMessage( IdBufferSizeInformation );
if( waitForMessage( IdBufferSizeInformation ).id
!= IdBufferSizeInformation )
{
fprintf( stderr, "Could not get buffer size information\n" );
}
sendMessage( IdHostInfoGotten );
}
@@ -281,15 +235,8 @@ RemotePluginClient::RemotePluginClient( const char * socketPath ) :
RemotePluginClient::~RemotePluginClient()
{
#ifdef USE_QT_SHMEM
m_shmQtID.detach();
#endif
sendMessage( IdQuit );
#ifndef USE_QT_SHMEM
shmdt( m_shm );
#endif
#ifndef SYNC_WITH_SHM_FIFO
if ( close( m_socket ) == -1)
{
@@ -300,12 +247,12 @@ RemotePluginClient::~RemotePluginClient()
#ifdef USE_QT_SHMEM
VstSyncData * RemotePluginClient::getQtVSTshm()
const VstSyncData* RemotePluginClient::getVstSyncData()
{
return m_vstSyncData;
}
#endif
@@ -353,7 +300,7 @@ bool RemotePluginClient::processMessage( const message & _m )
break;
case IdChangeSharedMemoryKey:
setShmKey( _m.getInt( 0 ), _m.getInt( 1 ) );
setShmKey(_m.getString(0));
break;
case IdInitDone:
@@ -378,43 +325,16 @@ bool RemotePluginClient::processMessage( const message & _m )
void RemotePluginClient::setShmKey( key_t _key, int _size )
void RemotePluginClient::setShmKey(const std::string& key)
{
#ifdef USE_QT_SHMEM
m_shmObj.setKey( QString::number( _key ) );
if( m_shmObj.attach() || m_shmObj.error() == QSharedMemory::NoError )
try
{
m_shm = (float *) m_shmObj.data();
m_audioBuffer.attach(key);
}
else
catch (const std::runtime_error& error)
{
char buf[64];
sprintf( buf, "failed getting shared memory: %d\n", m_shmObj.error() );
debugMessage( buf );
debugMessage(std::string{"failed getting shared memory: "} + error.what() + '\n');
}
#else
if( m_shm != nullptr )
{
shmdt( m_shm );
m_shm = nullptr;
}
// only called for detaching SHM?
if( _key == 0 )
{
return;
}
int shm_id = shmget( _key, _size, 0 );
if( shm_id == -1 )
{
debugMessage( "failed getting shared memory\n" );
}
else
{
m_shm = (float *) shmat( shm_id, 0, 0 );
}
#endif
}
@@ -422,10 +342,10 @@ void RemotePluginClient::setShmKey( key_t _key, int _size )
void RemotePluginClient::doProcessing()
{
if( m_shm != nullptr )
if (m_audioBuffer)
{
process( (sampleFrame *)( m_inputCount > 0 ? m_shm : nullptr ),
(sampleFrame *)( m_shm +
process( (sampleFrame *)( m_inputCount > 0 ? m_audioBuffer.get() : nullptr ),
(sampleFrame *)( m_audioBuffer.get() +
( m_inputCount*m_bufferSize ) ) );
}
else

143
include/SharedMemory.h Normal file
View File

@@ -0,0 +1,143 @@
/*
* SharedMemory.h
*
* Copyright (c) 2022 Dominic Clark <mrdomclark/at/gmail.com>
*
* 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 SHARED_MEMORY_H
#define SHARED_MEMORY_H
#include <memory>
#include <string>
#include <type_traits>
namespace detail {
class SharedMemoryImpl;
class SharedMemoryData
{
public:
SharedMemoryData() noexcept;
SharedMemoryData(std::string&& key, bool readOnly);
SharedMemoryData(std::string&& key, std::size_t size, bool readOnly);
~SharedMemoryData();
SharedMemoryData(SharedMemoryData&& other) noexcept;
SharedMemoryData& operator=(SharedMemoryData&& other) noexcept
{
auto temp = std::move(other);
swap(*this, temp);
return *this;
}
friend void swap(SharedMemoryData& a, SharedMemoryData& b) noexcept
{
using std::swap;
swap(a.m_key, b.m_key);
swap(a.m_impl, b.m_impl);
swap(a.m_ptr, b.m_ptr);
}
const std::string& key() const noexcept { return m_key; }
void* get() const noexcept { return m_ptr; }
private:
std::string m_key;
std::unique_ptr<SharedMemoryImpl> m_impl;
void* m_ptr = nullptr;
};
} // namespace detail
template<typename T>
class SharedMemory
{
// This is stricter than necessary, but keeps things easy for now
static_assert(std::is_trivial_v<T>, "objects held in shared memory must be trivial");
public:
SharedMemory() = default;
SharedMemory(SharedMemory&&) = default;
SharedMemory& operator=(SharedMemory&&) = default;
void attach(std::string key)
{
m_data = detail::SharedMemoryData{std::move(key), std::is_const_v<T>};
}
void create(std::string key)
{
m_data = detail::SharedMemoryData{std::move(key), sizeof(T), std::is_const_v<T>};
}
void detach() noexcept
{
m_data = detail::SharedMemoryData{};
}
const std::string& key() const noexcept { return m_data.key(); }
T* get() const noexcept { return static_cast<T*>(m_data.get()); }
T* operator->() const noexcept { return get(); }
T& operator*() const noexcept { return *get(); }
explicit operator bool() const noexcept { return get() != nullptr; }
private:
detail::SharedMemoryData m_data;
};
template<typename T>
class SharedMemory<T[]>
{
// This is stricter than necessary, but keeps things easy for now
static_assert(std::is_trivial_v<T>, "objects held in shared memory must be trivial");
public:
SharedMemory() = default;
SharedMemory(SharedMemory&&) = default;
SharedMemory& operator=(SharedMemory&&) = default;
void attach(std::string key)
{
m_data = detail::SharedMemoryData{std::move(key), std::is_const_v<T>};
}
void create(std::string key, std::size_t size)
{
m_data = detail::SharedMemoryData{std::move(key), size * sizeof(T), std::is_const_v<T>};
}
void detach() noexcept
{
m_data = detail::SharedMemoryData{};
}
const std::string& key() const noexcept { return m_data.key(); }
T* get() const noexcept { return static_cast<T*>(m_data.get()); }
T& operator[](std::size_t index) const noexcept { return get()[index]; }
explicit operator bool() const noexcept { return get() != nullptr; }
private:
detail::SharedMemoryData m_data;
};
#endif // SHARED_MEMORY_H

View File

@@ -27,8 +27,8 @@
#define VST_SYNC_CONTROLLER_H
#include <QObject>
#include <QSharedMemory>
#include "SharedMemory.h"
#include "VstSyncData.h"
@@ -75,11 +75,7 @@ private slots:
private:
VstSyncData* m_syncData;
int m_shmID;
QSharedMemory m_shm;
SharedMemory<VstSyncData> m_shm;
};
#endif

View File

@@ -33,10 +33,6 @@
// When defined, latency should be subtracted from song PPQ position
//#define VST_SNC_LATENCY
// define file for ftok as shared memory shmget key
constexpr const char* VST_SNC_SHM_KEY_FILE = "/dev/null";
//constexpr int64_t VST_SNC_SHM_RND_KEY = 3561653564469;
struct VstSyncData