Clean shutdown, stability

This commit is contained in:
Robin Davies
2024-09-21 13:18:00 -04:00
parent 951a7a73de
commit 2b13dc1c96
30 changed files with 820 additions and 358 deletions
+11 -13
View File
@@ -256,17 +256,15 @@ namespace pipedal
std::mutex terminateSync;
bool terminateAudio_ = false;
std::atomic<bool> terminateAudio_ = false;
void terminateAudio(bool terminate)
{
std::lock_guard lock{terminateSync};
this->terminateAudio_ = terminate;
}
bool terminateAudio()
{
std::lock_guard lock{terminateSync};
return this->terminateAudio_;
}
@@ -1297,10 +1295,10 @@ namespace pipedal
FillOutputBuffer();
err = snd_pcm_start(handle);
// if (err < 0)
// {
// throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
// }
if (err < 0)
{
throw PiPedalStateException(SS("Can't recover from ALSA underrun. (" << snd_strerror(err) << ")"));
}
return;
}
else if (err == -ESTRPIPE)
@@ -1320,7 +1318,7 @@ namespace pipedal
}
return;
}
throw PiPedalStateException(SS("ALSA error:" << snd_strerror(err)));
throw PiPedalStateException(SS("ALSA error: " << snd_strerror(err)));
}
std::jthread *audioThread = nullptr;
@@ -1663,16 +1661,16 @@ namespace pipedal
}
void Close()
{
if (hInParams)
{
snd_rawmidi_params_free(hInParams);
hInParams = 0;
}
if (hIn)
{
snd_rawmidi_close(hIn);
hIn = nullptr;
}
if (hInParams)
{
snd_rawmidi_params_free(hInParams);
hInParams = 0;
}
}
int GetDataLength(uint8_t cc)
+8 -10
View File
@@ -349,17 +349,14 @@ private:
virtual void Close()
{
std::lock_guard guard{mutex};
if (!isOpen)
return;
isOpen = false;
if (realtimeMonitorPortSubscriptions != nullptr)
{
delete realtimeMonitorPortSubscriptions;
realtimeMonitorPortSubscriptions = nullptr;
std::lock_guard guard{mutex};
if (!isOpen)
return;
isOpen = false;
}
if (active)
{
audioDriver->Deactivate();
@@ -371,6 +368,7 @@ private:
StopReaderThread();
// release any pdealboards owned by the process thread.
this->activePedalboards.resize(0);
this->realtimeActivePedalboard = nullptr;
@@ -815,7 +813,7 @@ private:
virtual void OnAudioTerminated() override
{
this->active = false;
Lv2Log::info("Audio stopped.");
Lv2Log::info("Audio thread terminated.");
}
+277 -94
View File
@@ -1,18 +1,18 @@
/*
* MIT License
*
*
* Copyright (c) 2022 Robin E. R. Davies
*
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -24,6 +24,7 @@
#include "AvahiService.hpp"
#include <string.h>
#include <thread>
#include <avahi-client/client.h>
#include <avahi-client/publish.h>
#include <avahi-common/alternative.h>
@@ -35,6 +36,7 @@
#include <unistd.h>
#include "Lv2Log.hpp"
#include "ss.hpp"
#include <chrono>
using namespace pipedal;
@@ -43,43 +45,122 @@ void AvahiService::Announce(
const std::string &name,
const std::string &instanceId,
const std::string &mdnsName,
bool addTestGroup)
bool wait)
{
if (terminated)
return;
// We should update an existing group if the name doesn't change.
// but in practice, the name is the only thing that's going to change without restarting the server.
if (this->name && strcmp(name.c_str(),this->name) == 0)
// but in practice, the name is the only thing that's going to change without restarting the pipedal server.
if (this->serviceName == name)
{
return;
}
Unannounce();
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->addTestGroup = addTestGroup;
this->name = avahi_strdup(name.c_str());
Start();
}
void AvahiService::Unannounce()
{
Stop();
if (name)
// this->name = avahi_strdup(name.c_str());
if (!started)
{
avahi_free(name);
this->name = nullptr;
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->serviceName = name;
started = true;
makeAnnouncement = true;
createPending = true;
Start();
if (wait) {
Wait();
}
}
else
{
// already started, so we have to use poll_lock instead.
avahi_threaded_poll_lock(threadedPoll);
// all state that we have to protect with the lock now that the avahi serv3ce is running.
this->portNumber = portNumber;
this->instanceId = instanceId;
this->mdnsName = mdnsName;
this->serviceName = name;
this->makeAnnouncement = true;
// we've requested a start but have not created the group.
if (this->createPending)
{
// the first create will use our freshly updated parameters.
// no action required.
}
else
{
// otherwise we have to use a lock to effect the update.
if (group)
{
avahi_entry_group_reset(group); // unannounce the previous
SetState(ServiceState::Reset);
create_group(client);
}
else
{
Lv2Log::error("Failed to update mDNS service because of a synch problem.");
}
}
avahi_threaded_poll_unlock(threadedPoll);
if (wait)
{
Wait();
}
}
this->group = nullptr;
this->client = nullptr;
this->threadedPoll = nullptr;
}
void AvahiService::Unannounce(bool wait)
{
if (terminated)
return;
if (this->threadedPoll)
{
avahi_threaded_poll_lock(threadedPoll);
if (started)
{
if (this->createPending)
{
// if service is starting up, and we haven't yet made our first announcement,
// just signal that we don't want to announc.
this->makeAnnouncement = false;
}
else
{
this->makeAnnouncement = false;
// we have previously made a successful announcement. Retract it.
if (group)
{
int rc = avahi_entry_group_reset(group);
if (rc < 0)
{
Lv2Log::error("Avahi: failed to un-announce.");
}
}
}
}
avahi_threaded_poll_unlock(threadedPoll);
if (wait)
{
WaitForUnannounce();
}
}
}
void AvahiService::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userData)
{
((AvahiService *)userData)->EntryGroupCallback(g, state);
}
void AvahiService::SetState(ServiceState serviceState)
{
this->serviceState = serviceState;
}
void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState state)
{
group = g;
@@ -87,29 +168,39 @@ void AvahiService::EntryGroupCallback(AvahiEntryGroup *g, AvahiEntryGroupState s
switch (state)
{
case AVAHI_ENTRY_GROUP_ESTABLISHED:
SetState(ServiceState::Established);
Lv2Log::info(SS("DNS/SD group established."));
/* The entry group has been established successfully */
Lv2Log::debug(SS("Service " << name << " successfully established."));
break;
case AVAHI_ENTRY_GROUP_COLLISION:
{
char *n;
/* A service name collision with a remote service
* happened. Let's pick a new name */
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
Lv2Log::error(SS("Service name collision, renaming service to '" << name << "'\n"));
* happened. Let's pick a new name */
SetState(ServiceState::Collision);
char *n = avahi_alternative_service_name(avahiNameString);
avahi_free(avahiNameString);
avahiNameString = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'\n"));
/* And recreate the services */
create_group(avahi_entry_group_get_client(g));
break;
}
case AVAHI_ENTRY_GROUP_FAILURE:
Lv2Log::error(SS("Entry group failure: " << avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) << "\n"));
Lv2Log::error(SS("DNS/SD service shutting down."));
/* Some kind of failure happened while we were registering our services */
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
break;
case AVAHI_ENTRY_GROUP_UNCOMMITED:
SetState(ServiceState::Uncommited);
break;
case AVAHI_ENTRY_GROUP_REGISTERING:;
SetState(ServiceState::Registering);
break;
}
}
@@ -142,11 +233,14 @@ static int toRawDns(char *rawResult, size_t size, const std::string &name)
}
void AvahiService::create_group(AvahiClient *c)
{
this->createPending = false;
char *n;
int ret;
assert(c);
/* If this is the first time we're called, let's create a new
* entry group if necessary */
SetState(ServiceState::Requested);
if (!group)
{
if (!(group = avahi_entry_group_new(c, entry_group_callback, (void *)this)))
@@ -157,20 +251,26 @@ void AvahiService::create_group(AvahiClient *c)
}
/* If the group is empty (either because it was just created, or
* because it was reset previously, add our entries. */
if (avahi_entry_group_is_empty(group))
if (this->makeAnnouncement && avahi_entry_group_is_empty(group))
{
Lv2Log::debug(SS("Adding service '" << name << "'"));
Lv2Log::debug(SS("Adding service '" << avahiNameString << "'"));
std::string instanceTxtRecord = SS("id=" << this->instanceId);
#define PIPEDAL_SERVICE_TYPE "_pipedal._tcp"
if (avahiNameString)
{
avahi_free(avahiNameString);
avahiNameString = nullptr;
}
avahiNameString = avahi_strdup(serviceName.c_str());
if ((ret = avahi_entry_group_add_service(
group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0,
name,
avahiNameString,
PIPEDAL_SERVICE_TYPE,
NULL,
NULL,
@@ -186,55 +286,31 @@ void AvahiService::create_group(AvahiClient *c)
goto fail;
}
if (this->addTestGroup)
{
Lv2Log::info("Added tests DNS/SD service.");
std::string instanceTxtRecord = SS("id=" << "0a6045b0-1753-4104-b3e4-b9713b9cc360");
if ((ret = avahi_entry_group_add_service(
group,
AVAHI_IF_UNSPEC,
AVAHI_PROTO_INET, // IPv4 for now, until we figure out why dnsqmasq borks IPv6 routing.)
(AvahiPublishFlags)0,
"Ed's PiPedal",
PIPEDAL_SERVICE_TYPE,
NULL,
NULL,
portNumber,
// txt records.
instanceTxtRecord.c_str(),
NULL)) < 0)
{
if (ret == AVAHI_ERR_COLLISION)
goto collision;
Lv2Log::error(SS("Failed to add _pipedal._tcp service: " << avahi_strerror(ret)));
goto fail;
}
}
/* Tell the server to register the service */
if ((ret = avahi_entry_group_commit(group)) < 0)
{
Lv2Log::error(SS("Failed to commit entry group: " << avahi_strerror(ret)));
goto fail;
}
Lv2Log::info(SS("DNS/SD service announced."));
Lv2Log::info(SS("DNS/SD service announced. (" << this->avahiNameString << ")"));
}
return;
collision:
/* A service name collision with a local service happened. Let's
* pick a new name */
n = avahi_alternative_service_name(name);
avahi_free(name);
name = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << name << "'"));
* pick a new avahiNameString */
n = avahi_alternative_service_name(avahiNameString);
avahi_free(avahiNameString);
avahiNameString = n;
Lv2Log::warning(SS("Service name collision, renaming service to '" << avahiNameString << "'"));
avahi_entry_group_reset(group);
create_group(c);
return;
fail:
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
Lv2Log::error("DNS/SD service shutting down.");
SetState(ServiceState::Failed);
}
void AvahiService::client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void *userdata)
@@ -250,7 +326,7 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
{
case AVAHI_CLIENT_S_RUNNING:
/* The server has startup successfully and registered its host
* name on the network, so it's time to create our services */
* name on the network, so it's time to create our services */
create_group(c);
break;
case AVAHI_CLIENT_FAILURE:
@@ -259,11 +335,11 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
if (this->clientErrno == AVAHI_ERR_DISCONNECTED)
{
// tear the client down and restart it
Lv2Log::info("Avahi connection lost. Reconnecting.");
Lv2Log::info("DNS/SD connection lost. Reconnecting.");
if (group)
{
avahi_entry_group_reset(group);
avahi_entry_group_free(group);
group = nullptr;
}
@@ -278,28 +354,35 @@ void AvahiService::ClientCallback(AvahiClient *c, AvahiClientState state)
if (!client)
{
Lv2Log::error(SS("Failed to create client: " << avahi_strerror(error)));
terminated = true;
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
}
}
else
{
Lv2Log::error(SS("Client failure: " << avahi_strerror(avahi_client_errno(c))));
terminated = true;
Lv2Log::error(SS("DNS/SD client failure: " << avahi_strerror(avahi_client_errno(c))));
avahi_threaded_poll_quit(threadedPoll);
SetState(ServiceState::Failed);
}
break;
case AVAHI_CLIENT_S_COLLISION:
/* Let's drop our registered services. When the server is back
* in AVAHI_SERVER_RUNNING state we will register them
* again with the new host name. */
* in AVAHI_SERVER_RUNNING state we will register them
* again with the new host name. */
case AVAHI_CLIENT_S_REGISTERING:
/* The server records are now being established. This
* might be caused by a host name change. We need to wait
* for our own records to register until the host name is
* properly esatblished. */
* might be caused by a host name change. We need to wait
* for our own records to register until the host name is
* properly esatblished. */
if (group)
avahi_entry_group_reset(group);
SetState(ServiceState::Settling);
break;
case AVAHI_CLIENT_CONNECTING:
SetState(ServiceState::Settling);
break;
case AVAHI_CLIENT_CONNECTING:;
}
}
@@ -310,7 +393,8 @@ void AvahiService::Start()
int ret = 1;
struct timeval tv;
this->clientErrno = 0;
while (true)
SetState(ServiceState::Initializing);
for (int retry = 0; retry < 3; ++retry)
{
/* Allocate main loop object */
if (!this->threadedPoll)
@@ -339,30 +423,129 @@ void AvahiService::Start()
return;
fail:
Stop();
sleep(1);
SetState(ServiceState::Failed);
}
return;
}
void AvahiService::Stop()
{
if (stopped)
return;
this->stopped = true;
/* Cleanup things */
if (group)
{
avahi_entry_group_reset(group);
avahi_entry_group_free(group);
group = nullptr;
}
if (client)
{
avahi_client_free(client);
client = nullptr;
}
Unannounce(true);
sleep(1); // let traffic setting. avahi doens't seem to shut down cleany otherwise.
if (threadedPoll)
{
avahi_threaded_poll_lock(threadedPoll);
if (group)
{
avahi_entry_group_free(group);
group = nullptr;
}
avahi_threaded_poll_unlock(threadedPoll);
avahi_threaded_poll_lock(threadedPoll);
if (client)
{
avahi_client_free(client);
client = nullptr;
}
avahi_threaded_poll_unlock(threadedPoll);
avahi_threaded_poll_stop(threadedPoll);
avahi_threaded_poll_free(threadedPoll);
SetState(ServiceState::Closed);
threadedPoll = nullptr;
}
/* Cleanup things */
if (avahiNameString)
{
avahi_free(avahiNameString);
avahiNameString = nullptr;
}
Lv2Log::info("DNS/SD service stopped.");
}
void AvahiService::Wait()
{
using clock = std::chrono::steady_clock;
auto start = clock::now();
auto waitTime = start + std::chrono::duration_cast<clock::duration>(std::chrono::seconds(5));
while (true)
{
bool done;
switch (serviceState)
{
default:
case ServiceState::Unitialized:
throw std::runtime_error("Invalid state");
case ServiceState::Initializing:
case ServiceState::Settling:
case ServiceState::Requested:
case ServiceState::Uncommited:
case ServiceState::Registering:
case ServiceState::Collision:
done = false;
break;
case ServiceState::Reset:
case ServiceState::Established:
case ServiceState::Failed:
case ServiceState::Closed:
done = true;
break;
};
if (done) break;
if (clock::now() > waitTime)
{
Lv2Log::error("DNS/SD announcement timed out.");
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
void AvahiService::WaitForUnannounce()
{
using clock = std::chrono::steady_clock;
auto start = clock::now();
auto waitTime = start + std::chrono::duration_cast<clock::duration>(std::chrono::seconds(5));
while (true)
{
bool done;
switch (serviceState)
{
default:
throw std::runtime_error("Invalid state");
case ServiceState::Initializing:
case ServiceState::Settling:
case ServiceState::Requested:
case ServiceState::Registering:
case ServiceState::Collision:
done = false;
break;
case ServiceState::Uncommited:
case ServiceState::Unitialized:
case ServiceState::Reset:
case ServiceState::Established:
case ServiceState::Failed:
case ServiceState::Closed:
done = true;
break;
};
if (done) break;
if (clock::now() > waitTime)
{
Lv2Log::error("DNS/SD unannounce timed out.");
}
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
}
+32 -5
View File
@@ -26,10 +26,12 @@
#include <thread>
#include <string>
#include <atomic>
// forward declarations.
class AvahiEntryGroup;
class AvahiThreadedPoll;
#include <atomic>
#include <avahi-client/client.h>
@@ -38,12 +40,37 @@ namespace pipedal {
class AvahiService {
public:
~AvahiService() { Unannounce(); }
~AvahiService() { Stop(); }
void Announce(
int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName,bool addTestGroup = false);
int portNumber, const std::string &name, const std::string &instanceId, const std::string&mdnsName, bool wait);
void Unannounce();
void Unannounce(bool wait);
private:
enum class ServiceState {
Unitialized,
Initializing,
Settling,
Requested,
Uncommited,
Registering,
Collision,
Reset,
Established,
Failed,
Closed
};
std::atomic<ServiceState> serviceState;
void Wait();
void WaitForUnannounce();
void SetState(ServiceState serviceState);
std::atomic<bool> terminated = false;
bool stopped = false;
bool started = false;
bool createPending = false;
bool makeAnnouncement = false;
void Start();
void Stop();
static void entry_group_callback(AvahiEntryGroup*g, AvahiEntryGroupState state, void *userData);
@@ -59,13 +86,13 @@ namespace pipedal {
int portNumber = -1;
std::string instanceId;
std::string serviceName;
std::string mdnsName;
bool addTestGroup = false;
AvahiClient *client = NULL;
AvahiEntryGroup *group = NULL;
AvahiThreadedPoll *threadedPoll = NULL;
char*name = NULL;
char*avahiNameString = NULL;
};
+1 -1
View File
@@ -96,7 +96,7 @@ endif()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set (USE_SANITIZE False)
set (USE_SANITIZE true)
if (!ENABLE_VST3)
+1
View File
@@ -1295,6 +1295,7 @@ void RequireNetworkManager()
throw std::runtime_error("The current OS is not using NetworkManager.");
}
}
int main(int argc, char **argv)
{
CommandLineParser parser;
+97 -79
View File
@@ -54,13 +54,12 @@ namespace pipedal::impl
std::vector<std::string> GetKnownWifiNetworks();
virtual PostHandle Post(PostCallback&&fn) override;
virtual PostHandle PostDelayed(const clock::duration&delay,PostCallback&&fn) override;
virtual PostHandle Post(PostCallback &&fn) override;
virtual PostHandle PostDelayed(const clock::duration &delay, PostCallback &&fn) override;
virtual bool CancelPost(PostHandle handle) override;
virtual void SetNetworkChangingListener(NetworkChangingListener &&listener) override;
private:
enum class State
{
@@ -104,8 +103,7 @@ namespace pipedal::impl
void UpdateKnownNetworks(
std::vector<ssid_t> &knownSsids,
std::vector<AccessPoint::ptr> &allAccessPoints
);
std::vector<AccessPoint::ptr> &allAccessPoints);
void FireNetworkChanging();
@@ -151,7 +149,6 @@ using namespace pipedal::impl;
HotspotManagerImpl::HotspotManagerImpl()
{
SetDBusLogLevel(DBusLogLevel::None);
}
void HotspotManagerImpl::Open()
@@ -167,7 +164,24 @@ void HotspotManagerImpl::Open()
void HotspotManagerImpl::onClose()
{
StopHotspot();
this->closed = true; // avoids a memory barrier probelm.
CancelDeviceChangedTimer();
CancelWaitForNetworkManagerTimer();
CancelAccessPointsChangedTimer();
if (networkManager && activeConnection)
{
try {
networkManager->DeactivateConnection(activeConnection->getObjectPath());
activeConnection = nullptr;
} catch (const std::exception&e)
{
// nothrow.
}
}
ReleaseNetworkManager();
Lv2Log::debug("HotspotManager: state=Closed");
@@ -250,6 +264,7 @@ void HotspotManagerImpl::UpdateNetworkManagerStatus()
this->StartWaitForNetworkManagerTimer();
}
}
void HotspotManagerImpl::CancelDeviceChangedTimer()
{
if (devicesChangedTimerHandle)
@@ -386,7 +401,6 @@ void HotspotManagerImpl::onStartMonitoring()
StartScanTimer();
onAccessPointChanged();
}
catch (const std::exception &e)
{
@@ -397,6 +411,7 @@ void HotspotManagerImpl::onStartMonitoring()
void HotspotManagerImpl::StartWaitForNetworkManagerTimer()
{
CancelWaitForNetworkManagerTimer();
networkManagerTimerHandle = dbusDispatcher.PostDelayed(
std::chrono::seconds(5),
@@ -408,6 +423,7 @@ void HotspotManagerImpl::StartWaitForNetworkManagerTimer()
}
void HotspotManagerImpl::CancelWaitForNetworkManagerTimer()
{
if (networkManagerTimerHandle)
{
dbusDispatcher.CancelPost(networkManagerTimerHandle);
@@ -436,7 +452,7 @@ void HotspotManagerImpl::onReload()
case State::Error:
// ignore.
return;
default:
default:
MaybeStartHotspot();
return;
}
@@ -473,8 +489,6 @@ void HotspotManagerImpl::Close()
}
}
HotspotManager::ptr HotspotManager::Create()
{
return std::make_unique<HotspotManagerImpl>();
@@ -496,8 +510,8 @@ public:
}
};
struct NetworkSortRecord {
struct NetworkSortRecord
{
std::string ssid;
bool connected = false;
bool knownNetwork = false;
@@ -506,12 +520,11 @@ struct NetworkSortRecord {
};
void HotspotManagerImpl::UpdateKnownNetworks(
std::vector<ssid_t> &knownSsids,
std::vector<AccessPoint::ptr> & allAccessPoints
)
std::vector<AccessPoint::ptr> &allAccessPoints)
{
std::map<std::string,NetworkSortRecord> map;
std::map<std::string, NetworkSortRecord> map;
for (auto &knownSsid: knownSsids)
for (auto &knownSsid : knownSsids)
{
std::string ssid = ssidToString(knownSsid);
if (ssid.length() != 0)
@@ -521,17 +534,22 @@ void HotspotManagerImpl::UpdateKnownNetworks(
record.knownNetwork = true;
}
}
for (auto&accessPoint: allAccessPoints)
for (auto &accessPoint : allAccessPoints)
{
uint8_t strength = accessPoint->Strength();
auto vSsid = accessPoint->Ssid();
std::string ssid = ssidToString(vSsid);
if (ssid.length() != 0)
try {
uint8_t strength = accessPoint->Strength();
auto vSsid = accessPoint->Ssid();
std::string ssid = ssidToString(vSsid);
if (ssid.length() != 0)
{
NetworkSortRecord &record = map[ssid];
record.ssid = ssid;
record.visibleNetwork = true;
record.strength = strength;
}
} catch (const std::exception&ignored)
{
NetworkSortRecord&record = map[ssid];
record.ssid = ssid;
record.visibleNetwork = true;
record.strength = strength;
// race to get the info before it changes. np.
}
}
if (this->wlanDevice)
@@ -539,24 +557,23 @@ void HotspotManagerImpl::UpdateKnownNetworks(
auto activeConnectionPath = this->wlanDevice->ActiveConnection();
if (activeConnectionPath.length() > 2) // "/" -> no connection. Be paranoid about "".
{
auto activeConnection = ActiveConnection::Create(dbusDispatcher,activeConnectionPath);
auto activeConnection = ActiveConnection::Create(dbusDispatcher, activeConnectionPath);
std::string activeSsid = activeConnection->Id();
auto it = map.find(activeSsid);
if (it != map.end())
{
it->second.connected = true;
}
}
}
std::vector<NetworkSortRecord> records;
records.reserve(map.size());
for (auto & mapEntry: map)
for (auto &mapEntry : map)
{
records.push_back(mapEntry.second);
}
std::sort(records.begin(),records.end(), [](const NetworkSortRecord&left, const NetworkSortRecord &right)
{
std::sort(records.begin(), records.end(), [](const NetworkSortRecord &left, const NetworkSortRecord &right)
{
if (left.connected != right.connected)
{
return left.connected > right.connected;
@@ -573,27 +590,25 @@ void HotspotManagerImpl::UpdateKnownNetworks(
{
return left.strength > right.strength;
}
return false;
});
return false; });
if (records.size() > 10)
{
records.resize(10);
}
std::vector<std::string> result;
result.reserve(records.size());
for (auto &record: records)
for (auto &record : records)
{
result.push_back(std::move(record.ssid));
}
{
std::lock_guard lock { this->knownWifiNetworksMutex };
std::lock_guard lock{this->knownWifiNetworksMutex};
this->knownWifiNetworks = std::move(result);
}
}
std::vector<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints() {
std::vector<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints()
{
std::vector<AccessPoint::ptr> accessPoints;
for (const auto &accessPointPath : wlanWirelessDevice->AccessPoints())
@@ -602,15 +617,14 @@ std::vector<AccessPoint::ptr> HotspotManagerImpl::GetAllAccessPoints() {
accessPoints.push_back(std::move(accessPoint));
}
return accessPoints;
}
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetKnownVisibleAccessPoints(const std::vector<ssid_t>&allAccessPoints)
std::vector<std::vector<uint8_t>> HotspotManagerImpl::GetKnownVisibleAccessPoints(const std::vector<ssid_t> &allAccessPoints)
{
std::vector<std::vector<uint8_t>> knownSsids = this->GetAllAutoConnectSsids();
std::unordered_set<std::vector<uint8_t>, VectorHash<uint8_t>> index{knownSsids.begin(), knownSsids.end()};
std::vector<ssid_t> result;
std::vector<ssid_t> result;
std::vector<AccessPoint::ptr> accessPoints;
for (const auto &accessPoint : allAccessPoints)
@@ -684,14 +698,16 @@ void HotspotManagerImpl::onAccessPointChanged()
}
}
static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessPoint::ptr>&accessPoints)
static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessPoint::ptr> &accessPoints)
{
std::vector<std::vector<uint8_t>> result;
for (const auto&accessPoint: accessPoints)
for (const auto &accessPoint : accessPoints)
{
try {
try
{
result.push_back(accessPoint->Ssid());
} catch (const std::exception&ignored)
}
catch (const std::exception &ignored)
{
// race to get a disappearing ssid. Ignore the error.
}
@@ -700,8 +716,10 @@ static std::vector<std::vector<uint8_t>> GetAccessPointSsids(std::vector<AccessP
}
void HotspotManagerImpl::MaybeStartHotspot()
{
if (this->state == State::Error) return;
if (this->closed) return;
if (this->state == State::Error)
return;
if (this->closed)
return;
if (!wlanDevice || !wlanWirelessDevice)
{
@@ -711,12 +729,12 @@ void HotspotManagerImpl::MaybeStartHotspot()
std::vector<AccessPoint::ptr> allAccessPoints = GetAllAccessPoints();
std::vector<std::vector<uint8_t>> allAccessPointSsids = GetAccessPointSsids(allAccessPoints);
std::vector<std::vector<uint8_t>> connectableSsids = GetKnownVisibleAccessPoints(allAccessPointSsids); // all the ssids currently visible for which we have credentials.
this->UpdateKnownNetworks(connectableSsids,allAccessPoints);
bool wantsHotspot = this->wifiConfigSettings.WantsHotspot(
this->UpdateKnownNetworks(connectableSsids, allAccessPoints);
this->ethernetConnected,connectableSsids,allAccessPointSsids);
bool wantsHotspot = this->wifiConfigSettings.WantsHotspot(
this->ethernetConnected, connectableSsids, allAccessPointSsids);
if (this->state == State::Monitoring && wantsHotspot)
{
@@ -764,7 +782,7 @@ Connection::ptr HotspotManagerImpl::FindExistingConnection()
}
}
}
return nullptr;
}
@@ -874,28 +892,26 @@ void HotspotManagerImpl::StopHotspot()
{
if (activeConnection)
{
FireNetworkChanging();
networkManager->DeactivateConnection(activeConnection->getObjectPath());
activeConnection = nullptr;
FireNetworkChanging();
}
}
catch (const std::exception &e)
{
Lv2Log::error("HotspotManager: Failed to deactivate hotspot.");
Lv2Log::error(SS("HotspotManager: Failed to deactivate hotspot. " << e.what()));
activeConnection = nullptr;
}
if (this->state == State::HotspotConnected || this->state == State::HotspotConnecting)
{
Lv2Log::info("HotspotManager: state=HotspotMonitoring");
Lv2Log::debug("HotspotManager: state=HotspotMonitoring");
SetState(State::Monitoring);
Lv2Log::info("HotspotManager: PiPedal hotspot disabled.");
}
}
static const std::chrono::seconds scanInterval { 60};
static const std::chrono::seconds scanInterval{60};
void HotspotManagerImpl::StartScanTimer()
{
@@ -904,7 +920,8 @@ void HotspotManagerImpl::StartScanTimer()
}
void HotspotManagerImpl::StopScanTimer()
{
if (this->scanTimerHandle) {
if (this->scanTimerHandle)
{
dbusDispatcher.CancelPost(this->scanTimerHandle);
this->scanTimerHandle = 0;
}
@@ -913,14 +930,17 @@ void HotspotManagerImpl::ScanNow()
{
this->scanTimerHandle = 0;
if (wlanWirelessDevice) {
if (wlanWirelessDevice)
{
std::map<std::string, sdbus::Variant> options;
try {
try
{
Lv2Log::debug("Scanning");
wlanWirelessDevice->RequestScan(options);
} catch (const std::exception &e)
}
catch (const std::exception &e)
{
Lv2Log::error(SS("HotspotMonitor: Wi-Fi RequestScan failed." << e.what()));
return;
@@ -928,44 +948,42 @@ void HotspotManagerImpl::ScanNow()
this->scanTimerHandle = this->dbusDispatcher.PostDelayed(
std::chrono::duration_cast<std::chrono::steady_clock::duration>(scanInterval),
[this]() {
[this]()
{
ScanNow();
}
);
});
}
}
HotspotManagerImpl::PostHandle HotspotManagerImpl::Post(PostCallback&&fn)
HotspotManagerImpl::PostHandle HotspotManagerImpl::Post(PostCallback &&fn)
{
return dbusDispatcher.Post(std::move(fn));
}
HotspotManagerImpl::PostHandle HotspotManagerImpl::PostDelayed(const clock::duration&delay,PostCallback&&fn)
HotspotManagerImpl::PostHandle HotspotManagerImpl::PostDelayed(const clock::duration &delay, PostCallback &&fn)
{
return dbusDispatcher.PostDelayed(delay,std::move(fn));
return dbusDispatcher.PostDelayed(delay, std::move(fn));
}
bool HotspotManagerImpl::CancelPost(PostHandle handle) {
bool HotspotManagerImpl::CancelPost(PostHandle handle)
{
return dbusDispatcher.CancelPost(handle);
}
void HotspotManagerImpl::SetNetworkChangingListener(NetworkChangingListener &&listener)
void HotspotManagerImpl::SetNetworkChangingListener(NetworkChangingListener &&listener)
{
std::lock_guard<std::recursive_mutex> lock { this->networkChangingListenerMutex};
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
this->networkChangingListener = std::move(listener);
}
void HotspotManagerImpl::FireNetworkChanging() {
std::lock_guard<std::recursive_mutex> lock { this->networkChangingListenerMutex};
void HotspotManagerImpl::FireNetworkChanging()
{
std::lock_guard<std::recursive_mutex> lock{this->networkChangingListenerMutex};
if (this->networkChangingListener)
{
this->networkChangingListener(this->ethernetConnected,!!activeConnection);
this->networkChangingListener(this->ethernetConnected, !!activeConnection);
}
}
std::vector<std::string> HotspotManagerImpl::GetKnownWifiNetworks()
std::vector<std::string> HotspotManagerImpl::GetKnownWifiNetworks()
{
std::lock_guard lock(knownWifiNetworksMutex); // pushed off the service thread.
return this->knownWifiNetworks;
}
+84 -30
View File
@@ -39,6 +39,9 @@
#include "DBusToLv2Log.hpp"
#include "SysExec.hpp"
#include "Updater.hpp"
#include "util.hpp"
#include "DBusLog.hpp"
#include "AvahiService.hpp"
#ifndef NO_MLOCK
#include <sys/mman.h>
@@ -88,6 +91,7 @@ PiPedalModel::PiPedalModel()
DbusLogToLv2Log();
SetDBusLogLevel(DBusLogLevel::Info);
hotspotManager = HotspotManager::Create();
hotspotManager->SetNetworkChangingListener(
@@ -100,24 +104,41 @@ PiPedalModel::PiPedalModel()
void PiPedalModel::Close()
{
std::lock_guard<std::recursive_mutex> lock(mutex);
std::unique_ptr<AudioHost> oldAudioHost;
{
std::lock_guard<std::recursive_mutex> lock(mutex);
if (closed)
{
return;
}
closed = true;
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->Close();
}
delete[] t;
if (avahiService) {
this->avahiService = nullptr; // and close.
}
if (audioHost)
// take a snapshot incase a client unsusbscribes in the notification handler (in which case the mutex won't protect us)
IPiPedalModelSubscriber **t = new IPiPedalModelSubscriber *[this->subscribers.size()];
for (size_t i = 0; i < subscribers.size(); ++i)
{
t[i] = this->subscribers[i];
}
size_t n = this->subscribers.size();
for (size_t i = 0; i < n; ++i)
{
t[i]->Close();
}
delete[] t;
this->subscribers.resize(0);
oldAudioHost = std::move(this->audioHost);
} // end lock.
// lockless to avoid deadlocks while shutting down the audio thread.
if (oldAudioHost)
{
audioHost->Close();
oldAudioHost->Close();
}
}
@@ -977,11 +998,11 @@ void PiPedalModel::SetWifiConfigSettings(const WifiConfigSettings &wifiConfigSet
#if NEW_WIFI_CONFIG
if (this->storage.SetWifiConfigSettings(wifiConfigSettings))
{
this->UpdateDnsSd();
if (this->hotspotManager)
{
this->hotspotManager->Reload();
}
this->UpdateDnsSd();
}
#else
this->storage.SetWifiConfigSettings(wifiConfigSettings);
@@ -1022,20 +1043,28 @@ static std::string GetP2pdName()
void PiPedalModel::UpdateDnsSd()
{
// avahiService.Unannounce(); let Announce decide whether it wants to unannounce or update.
if (!avahiService)
{
throw std::runtime_error("Not ready.");
}
ServiceConfiguration deviceIdFile;
deviceIdFile.Load();
std::string p2pdName = GetP2pdName();
if (p2pdName != "")
WifiConfigSettings wifiSettings;
wifiSettings.Load();
std::string serviceName = wifiSettings.hotspotName_;
if (serviceName == "")
{
deviceIdFile.deviceName = p2pdName;
serviceName = deviceIdFile.deviceName;
}
if (serviceName == "")
{
serviceName = "pipedal";
}
if (deviceIdFile.deviceName != "" && deviceIdFile.uuid != "")
std::string hostName = GetHostName();
if (serviceName != "" && deviceIdFile.uuid != "")
{
avahiService.Announce(webPort, deviceIdFile.deviceName, deviceIdFile.uuid, "pipedal");
avahiService->Announce(webPort, serviceName, deviceIdFile.uuid, hostName,true);
}
else
{
@@ -1376,12 +1405,19 @@ void PiPedalModel::UpdateRealtimeVuSubscriptions()
addedInstances.insert(activeVuSubscriptions[i].instanceid);
}
}
std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end());
audioHost->SetVuSubscriptions(instanceids);
if (audioHost)
{
std::vector<int64_t> instanceids(addedInstances.begin(), addedInstances.end());
audioHost->SetVuSubscriptions(instanceids);
}
}
void PiPedalModel::UpdateRealtimeMonitorPortSubscriptions()
{
if (!audioHost)
{
return;
}
audioHost->SetMonitorPortSubscriptions(this->activeMonitorPortSubscriptions);
}
@@ -1484,7 +1520,10 @@ void PiPedalModel::SendSetPatchProperty(
clientId, instanceId, urid, atomValue, nullptr, onError);
outstandingParameterRequests.push_back(request);
this->audioHost->sendRealtimeParameterRequest(request);
if (this->audioHost)
{
this->audioHost->sendRealtimeParameterRequest(request);
}
}
void PiPedalModel::SendGetPatchProperty(
@@ -1545,7 +1584,10 @@ void PiPedalModel::SendGetPatchProperty(
std::lock_guard<std::recursive_mutex> lock(mutex);
outstandingParameterRequests.push_back(request);
this->audioHost->sendRealtimeParameterRequest(request);
if (this->audioHost)
{
this->audioHost->sendRealtimeParameterRequest(request);
}
}
BankIndex PiPedalModel::GetBankIndex() const
@@ -1802,7 +1844,10 @@ void PiPedalModel::DeleteAtomOutputListeners(int64_t clientId)
--i;
}
}
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
if (audioHost)
{
audioHost->SetListenForAtomOutput(atomOutputListeners.size() != 0);
}
}
void PiPedalModel::DeleteMidiListeners(int64_t clientId)
@@ -1816,7 +1861,10 @@ void PiPedalModel::DeleteMidiListeners(int64_t clientId)
--i;
}
}
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
if (audioHost)
{
audioHost->SetListenForMidiEvent(midiEventListeners.size() != 0);
}
}
void PiPedalModel::OnPatchSetReply(uint64_t instanceId, LV2_URID patchSetProperty, const LV2_Atom *atomValue)
@@ -2248,6 +2296,12 @@ static bool HasAlsaDevice(const std::vector<AlsaDeviceInfo> devices, const std::
void PiPedalModel::StartHotspotMonitoring()
{
this->avahiService = std::make_unique<AvahiService>();
SetThreadName("avahi"); // hack to name the avahi service thread.
UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
SetThreadName("main");
this->hotspotManager->Open();
}
+3 -2
View File
@@ -36,7 +36,6 @@
#include "WifiConfigSettings.hpp"
#include "WifiDirectConfigSettings.hpp"
#include "AdminClient.hpp"
#include "AvahiService.hpp"
#include <thread>
#include "Promise.hpp"
#include "AtomConverter.hpp"
@@ -49,6 +48,7 @@ namespace pipedal
struct RealtimeNextMidiProgramRequest;
class Lv2PluginChangeMonitor;
class Updater;
class AvahiService;
class IPiPedalModelSubscriber
{
@@ -113,7 +113,7 @@ namespace pipedal
std::vector<MidiBinding> systemMidiBindings;
AvahiService avahiService;
std::unique_ptr<AvahiService> avahiService;
uint16_t webPort;
PiPedalAlsaDevices alsaDevices;
@@ -197,6 +197,7 @@ namespace pipedal
std::vector<RealtimePatchPropertyRequest *> outstandingParameterRequests;
IPiPedalModelSubscriber *GetNotificationSubscriber(int64_t clientId);
std::atomic<bool> closed = false;
private: // IAudioHostCallbacks
virtual void OnNotifyLv2StateChanged(uint64_t instanceId) override;
+25
View File
@@ -516,26 +516,47 @@ private:
std::mutex activePortMonitorsMutex;
std::vector<std::shared_ptr<PortMonitorSubscription>> activePortMonitors;
std::atomic<bool> closed = false;
public:
virtual int64_t GetClientId() { return clientId; }
virtual ~PiPedalSocketHandler()
{
if (!closed)
{
FinalCleanup();
}
}
bool finalCleanup = false;
void FinalCleanup()
{
if (finalCleanup) return;
finalCleanup = true;
// avoid use after free.
for (int i = 0; i < this->activePortMonitors.size(); ++i)
{
model.UnmonitorPort(activePortMonitors[i]->subscriptionHandle);
}
activePortMonitors.resize(0);
for (int i = 0; i < this->activeVuSubscriptions.size(); ++i)
{
model.RemoveVuSubscription(activeVuSubscriptions[i].subscriptionHandle);
}
activeVuSubscriptions.resize(0);
model.RemoveNotificationSubsription(this);
}
virtual void Close()
{
if (closed) return;
closed = true;
FinalCleanup(); // do it while we can. &model will no longer be valid after this. ( :-( )
SocketHandler::Close();
}
@@ -935,6 +956,10 @@ public:
}
return;
}
if (closed)
{
this->SendError(replyTo, "Server has shut down.");
}
if (message == "setControl")
{
ControlChangedBody message;
+4 -1
View File
@@ -1181,7 +1181,7 @@ void Updater::ValidateSignature(const std::filesystem::path &file, const std::fi
<< " --verify "
<< signatureFile << " " << file;
Lv2Log::info(SS("/usr/bin/gpg " << ss.str()));
Lv2Log::debug(SS("/usr/bin/gpg " << ss.str()));
auto gpgOutput = sysExecForOutput("/usr/bin/gpg", ss.str());
if (gpgOutput.exitCode != EXIT_SUCCESS)
{
@@ -1191,17 +1191,20 @@ void Updater::ValidateSignature(const std::filesystem::path &file, const std::fi
if (!IsSignatureGood(gpgText))
{
Lv2Log::error(gpgOutput.output);
throw std::runtime_error("Update signature is not valid.");
}
std::string keyId = getFingerprint(gpgText);
if (keyId != UPDATE_GPG_FINGERPRINT && keyId != UPDATE_GPG_FINGERPRINT2)
{
Lv2Log::error(gpgOutput.output);
throw std::runtime_error(SS("Update signature has the wrong id: " << keyId));
}
std::string origin = getAddress(gpgText);
if (origin != UPDATE_GPG_ADDRESS && origin != UPDATE_GPG_ADDRESS2)
{
Lv2Log::error(gpgOutput.output);
throw std::runtime_error(SS("Update signature has an incorrect address." << origin));
}
}
+2 -12
View File
@@ -557,16 +557,6 @@ pipedal::last_modified(const std::filesystem::path &path)
}
}
static std::string getHostName()
{
char buff[512];
if (gethostname(buff, sizeof(buff)) == 0)
{
buff[511] = '\0';
return buff;
}
return "";
}
static std::string getIpv4Address(const std::string interface)
{
@@ -1165,7 +1155,7 @@ namespace pipedal
std::stringstream ss;
ss << port;
// m_endpoint.listen(this->address, ss.str());
//m_endpoint.listen(this->address, ss.str());
m_endpoint.listen(tcp::v6(), (uint16_t)port);
m_endpoint.start_accept();
@@ -1305,7 +1295,7 @@ std::shared_ptr<WebServer> pipedal::WebServer::create(
void WebServerImpl::DisplayIpAddresses()
{
std::string hostName = getHostName();
std::string hostName = GetHostName();
if (hostName.length() != 0)
{
std::stringstream ss;
+7 -7
View File
@@ -117,7 +117,7 @@ public:
/// Write a string message to the given channel
/**
* @param channel The channel to write to
* @param channel The channel to write tosu
* @param msg The message to write
*/
void write(level channel, std::string const & msg) {
@@ -126,19 +126,19 @@ public:
{
case elevel::devel:
case elevel::library:
Lv2Log::debug(msg);
Lv2Log::debug("WebServer: %s",msg.c_str());
break;
case elevel::info:
Lv2Log::info(msg);
Lv2Log::info("WebServer: %s",msg.c_str());
break;
case elevel::warn:
Lv2Log::warning(msg);
Lv2Log::warning("WebServer: %s",msg.c_str());
break;
case elevel::rerror:
Lv2Log::error(msg);
Lv2Log::error("WebServer: %s",msg.c_str());
break;
case elevel::fatal:
Lv2Log::error("Fatal error: " + msg);
Lv2Log::error("WebServer fatal error: %s",msg.c_str());
break;
default:
break;
@@ -159,7 +159,7 @@ public:
}
bool dynamic_test(level channel) {
return (m_channels) & channel != 0;
return (m_channels & channel) != 0;
}
protected:
+12 -5
View File
@@ -81,10 +81,18 @@ static bool isJackServiceRunning()
return std::filesystem::exists(path);
}
static void AsanCheck()
{
char *t = new char[5];
t[5] = 'x';
delete t;
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[])
{
#ifndef WIN32
umask(002); // newly created files in /var/pipedal get 775-ish permissions, which improves debugging/live-service interaction.
#endif
@@ -269,7 +277,6 @@ int main(int argc, char *argv[])
(unsigned long)getpid());
}
model.StartHotspotMonitoring();
model.WaitForAudioDeviceToComeOnline();
@@ -317,9 +324,9 @@ int main(int argc, char *argv[])
{
server->RunInBackground(-1);
SetThreadName("avahi");
model.UpdateDnsSd(); // now that the server is running, publish a DNS-SD announcement.
SetThreadName("main");
model.StartHotspotMonitoring();
{
sigwait(&sigSet, &sig);