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

View File

@@ -166,6 +166,10 @@ IF(LMMS_BUILD_HAIKU)
SET(EXTRA_LIBRARIES "-lnetwork")
ENDIF()
if(LMMS_HAVE_LIBRT)
list(APPEND EXTRA_LIBRARIES "rt")
endif()
SET(LMMS_REQUIRED_LIBS ${LMMS_REQUIRED_LIBS}
${CMAKE_THREAD_LIBS_INIT}
${QT_LIBRARIES}

View File

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

201
src/common/SharedMemory.cpp Normal file
View File

@@ -0,0 +1,201 @@
/*
* SharedMemory.cpp
*
* 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.
*/
#include "SharedMemory.h"
#include "lmmsconfig.h"
#ifdef LMMS_HAVE_UNISTD_H
# include <unistd.h>
#endif
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
# include <system_error>
#
# include <sys/mman.h>
# include <sys/stat.h>
# include <fcntl.h>
#
# include "RaiiHelpers.h"
#else
# include <stdexcept>
#
# include <QtGlobal>
# include <QSharedMemory>
#endif
namespace detail {
#if _POSIX_SHARED_MEMORY_OBJECTS > 0
namespace {
template<typename F>
int retryWhileInterrupted(F&& function) noexcept(std::is_nothrow_invocable_v<F>)
{
int result;
do
{
result = function();
}
while (result == -1 && errno == EINTR);
return result;
}
void deleteFileDescriptor(int fd) noexcept { retryWhileInterrupted([fd]() noexcept { return close(fd); }); }
void deleteShmObject(const char* name) noexcept { shm_unlink(name); }
using FileDescriptor = UniqueNullableResource<int, -1, deleteFileDescriptor>;
using ShmObject = UniqueNullableResource<const char*, nullptr, deleteShmObject>;
} // namespace
class SharedMemoryImpl
{
public:
SharedMemoryImpl(const std::string& key, bool readOnly) :
m_key{"/" + key}
{
const auto openFlags = readOnly ? O_RDONLY : O_RDWR;
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"};
}
auto stat = (struct stat){};
if (fstat(fd.get(), &stat) == -1)
{
throw std::system_error{errno, std::generic_category(), "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"};
}
}
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
m_key{"/" + key},
m_size{size}
{
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"};
}
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"};
}
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"};
}
}
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
~SharedMemoryImpl()
{
munmap(m_mapping, m_size);
}
void* get() { return m_mapping; }
private:
std::string m_key;
std::size_t m_size;
void* m_mapping;
ShmObject m_object;
};
#else
class SharedMemoryImpl
{
public:
SharedMemoryImpl(const std::string& key, bool readOnly) :
m_shm{QString::fromStdString(key)}
{
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
if (!m_shm.attach(mode))
{
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::attach() failed"};
}
}
SharedMemoryImpl(const std::string& key, std::size_t size, bool readOnly) :
m_shm{QString::fromStdString(key)}
{
const auto mode = readOnly ? QSharedMemory::ReadOnly : QSharedMemory::ReadWrite;
if (!m_shm.create(size, mode))
{
throw std::runtime_error{"SharedMemoryImpl: QSharedMemory::create() failed"};
}
}
SharedMemoryImpl(const SharedMemoryImpl&) = delete;
SharedMemoryImpl& operator=(const SharedMemoryImpl&) = delete;
void* get() { return m_shm.data(); }
private:
QSharedMemory m_shm;
};
#endif
SharedMemoryData::SharedMemoryData() noexcept
{ }
SharedMemoryData::SharedMemoryData(std::string&& key, bool readOnly) :
m_key{std::move(key)},
m_impl{std::make_unique<SharedMemoryImpl>(m_key, readOnly)},
m_ptr{m_impl->get()}
{ }
SharedMemoryData::SharedMemoryData(std::string&& key, std::size_t size, bool readOnly) :
m_key{std::move(key)},
m_impl{std::make_unique<SharedMemoryImpl>(m_key, std::max(size, std::size_t{1}), readOnly)},
m_ptr{m_impl->get()}
{ }
SharedMemoryData::~SharedMemoryData() { }
SharedMemoryData::SharedMemoryData(SharedMemoryData&& other) noexcept :
m_key{std::move(other.m_key)},
m_impl{std::move(other.m_impl)},
m_ptr{other.m_ptr}
{ }
} // namespace detail

View File

@@ -40,9 +40,9 @@
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
#include <QUuid>
#ifndef SYNC_WITH_SHM_FIFO
#include <QUuid>
#include <sys/socket.h>
#include <sys/un.h>
#endif
@@ -137,13 +137,7 @@ RemotePlugin::RemotePlugin() :
m_watcher( this ),
m_commMutex( QMutex::Recursive ),
m_splitChannels( false ),
#ifdef USE_QT_SHMEM
m_shmObj(),
#else
m_shmID( 0 ),
#endif
m_shmSize( 0 ),
m_shm( nullptr ),
m_audioBufferSize( 0 ),
m_inputCount( DEFAULT_CHANNELS ),
m_outputCount( DEFAULT_CHANNELS )
{
@@ -209,11 +203,6 @@ RemotePlugin::~RemotePlugin()
}
unlock();
}
#ifndef USE_QT_SHMEM
shmdt( m_shm );
shmctl( m_shmID, IPC_RMID, nullptr );
#endif
}
#ifndef SYNC_WITH_SHM_FIFO
@@ -273,8 +262,8 @@ bool RemotePlugin::init(const QString &pluginExecutable,
QStringList args;
#ifdef SYNC_WITH_SHM_FIFO
// swap in and out for bidirectional communication
args << QString::number( out()->shmKey() );
args << QString::number( in()->shmKey() );
args << QString::fromStdString(out()->shmKey());
args << QString::fromStdString(in()->shmKey());
#else
args << m_socketFile;
#endif
@@ -342,13 +331,13 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf
return false;
}
if( m_shm == nullptr )
if (!m_audioBuffer)
{
// m_shm being zero means we didn't initialize everything so
// m_audioBuffer being zero means we didn't initialize everything so
// far so process one message each time (and hope we get
// information like SHM-key etc.) until we process messages
// in a later stage of this procedure
if( m_shmSize == 0 )
if( m_audioBufferSize == 0 )
{
lock();
fetchAndProcessAllMessages();
@@ -361,7 +350,7 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf
return false;
}
memset( m_shm, 0, m_shmSize );
memset( m_audioBuffer.get(), 0, m_audioBufferSize );
ch_cnt_t inputs = qMin<ch_cnt_t>( m_inputCount, DEFAULT_CHANNELS );
@@ -373,18 +362,18 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf
{
for( fpp_t frame = 0; frame < frames; ++frame )
{
m_shm[ch * frames + frame] =
m_audioBuffer[ch * frames + frame] =
_in_buf[frame][ch];
}
}
}
else if( inputs == DEFAULT_CHANNELS )
{
memcpy( m_shm, _in_buf, frames * BYTES_PER_FRAME );
memcpy( m_audioBuffer.get(), _in_buf, frames * BYTES_PER_FRAME );
}
else
{
sampleFrame * o = (sampleFrame *) m_shm;
sampleFrame * o = (sampleFrame *) m_audioBuffer.get();
for( ch_cnt_t ch = 0; ch < inputs; ++ch )
{
for( fpp_t frame = 0; frame < frames; ++frame )
@@ -415,19 +404,19 @@ bool RemotePlugin::process( const sampleFrame * _in_buf, sampleFrame * _out_buf
{
for( fpp_t frame = 0; frame < frames; ++frame )
{
_out_buf[frame][ch] = m_shm[( m_inputCount+ch )*
_out_buf[frame][ch] = m_audioBuffer[( m_inputCount+ch )*
frames + frame];
}
}
}
else if( outputs == DEFAULT_CHANNELS )
{
memcpy( _out_buf, m_shm + m_inputCount * frames,
memcpy( _out_buf, m_audioBuffer.get() + m_inputCount * frames,
frames * BYTES_PER_FRAME );
}
else
{
sampleFrame * o = (sampleFrame *) ( m_shm +
sampleFrame * o = (sampleFrame *) ( m_audioBuffer.get() +
m_inputCount*frames );
// clear buffer, if plugin didn't fill up both channels
BufferManager::clear( _out_buf, frames );
@@ -481,37 +470,19 @@ void RemotePlugin::hideUI()
void RemotePlugin::resizeSharedProcessingMemory()
{
const size_t s = ( m_inputCount+m_outputCount ) * Engine::audioEngine()->framesPerPeriod() * sizeof( float );
if( m_shm != nullptr )
const size_t s = (m_inputCount + m_outputCount) * Engine::audioEngine()->framesPerPeriod();
try
{
#ifdef USE_QT_SHMEM
m_shmObj.detach();
#else
shmdt( m_shm );
shmctl( m_shmID, IPC_RMID, nullptr );
#endif
m_audioBuffer.create(QUuid::createUuid().toString().toStdString(), s);
}
static int shm_key = 0;
#ifdef USE_QT_SHMEM
do
{
m_shmObj.setKey( QString( "%1" ).arg( ++shm_key ) );
m_shmObj.create( s );
} while( m_shmObj.error() != QSharedMemory::NoError );
m_shm = (float *) m_shmObj.data();
#else
while( ( m_shmID = shmget( ++shm_key, s, IPC_CREAT | IPC_EXCL |
0600 ) ) == -1 )
catch (const std::runtime_error& error)
{
qCritical() << "Failed to allocate shared audio buffer:" << error.what();
m_audioBuffer.detach();
return;
}
m_shm = (float *) shmat( m_shmID, 0, 0 );
#endif
m_shmSize = s;
sendMessage( message( IdChangeSharedMemoryKey ).
addInt( shm_key ).addInt( m_shmSize ) );
m_audioBufferSize = s * sizeof(float);
sendMessage(message(IdChangeSharedMemoryKey).addString(m_audioBuffer.key()));
}

View File

@@ -25,6 +25,8 @@
#include "VstSyncController.h"
#include <stdexcept>
#include <QDebug>
#include "AudioEngine.h"
@@ -32,53 +34,23 @@
#include "Engine.h"
#include "RemotePlugin.h"
#ifndef USE_QT_SHMEM
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#endif
VstSyncController::VstSyncController() :
m_syncData( nullptr ),
m_shmID( -1 ),
m_shm( "/usr/bin/lmms" )
m_syncData( nullptr )
{
if( ConfigManager::inst()->value( "ui", "syncvstplugins" ).toInt() )
{
connect( Engine::audioEngine(), SIGNAL( sampleRateChanged() ), this, SLOT( updateSampleRate() ) );
#ifdef USE_QT_SHMEM
if ( m_shm.create( sizeof( VstSyncData ) ) )
try
{
m_syncData = (VstSyncData*) m_shm.data();
m_shm.create("usr_bin_lmms");
m_syncData = m_shm.get();
}
else
catch (const std::runtime_error& error)
{
qWarning() << QString( "Failed to allocate shared memory for VST sync: %1" ).arg( m_shm.errorString() );
qWarning() << "Failed to allocate shared memory for VST sync:" << error.what();
}
#else
key_t key; // make the key:
if( ( key = ftok( VST_SNC_SHM_KEY_FILE, 'R' ) ) == -1 )
{
qWarning( "VstSyncController: ftok() failed" );
}
else
{ // connect to shared memory segment
if( ( m_shmID = shmget( key, sizeof( VstSyncData ), 0644 | IPC_CREAT ) ) == -1 )
{
qWarning( "VstSyncController: shmget() failed" );
}
else
{ // attach segment
m_syncData = (VstSyncData *)shmat( m_shmID, 0, 0 );
if( m_syncData == (VstSyncData *)( -1 ) )
{
qWarning( "VstSyncController: shmat() failed" );
}
}
}
#endif
}
else
{
@@ -111,25 +83,6 @@ VstSyncController::~VstSyncController()
{
delete m_syncData;
}
else
{
#ifdef USE_QT_SHMEM
if( m_shm.data() )
{
// detach shared memory, delete it:
m_shm.detach();
}
#else
if( shmdt( m_syncData ) != -1 )
{
shmctl( m_shmID, IPC_RMID, nullptr );
}
else
{
qWarning( "VstSyncController: shmdt() failed" );
}
#endif
}
}

View File

@@ -40,7 +40,6 @@
#cmakedefine LMMS_HAVE_SYS_TYPES_H
#cmakedefine LMMS_HAVE_SYS_IPC_H
#cmakedefine LMMS_HAVE_SEMAPHORE_H
#cmakedefine LMMS_HAVE_SYS_SHM_H
#cmakedefine LMMS_HAVE_SYS_TIME_H
#cmakedefine LMMS_HAVE_SYS_TIMES_H
#cmakedefine LMMS_HAVE_SCHED_H