Files
OpenRGB/NetworkServer.h
Ken Sanislo b3a2cccfed NetworkServer: per-client send thread for SignalUpdate notifications
RGBController_UpdateCallback sent the SignalUpdate packet synchronously
via SendRequest_RGBController_SignalUpdate, which runs inside
RGBController::SignalUpdate. A blocking send there (slow client) keeps
SignalCalls > 0, so WaitSignalCalls -- the callback drain that
Unregister/ClearCallbacks/Shutdown run while tearing callbacks down on a
rescan -- waits forever. That is the client/server rescan deadlock.

Each client now owns an outbound queue and a dedicated send thread. The
signal path builds the packet (brief AccessMutex, as before) and queues
it; the client's own thread does the blocking send. A slow client backs
up only its own queue and blocks only its own thread, so the signal path
and the callback drain are never blocked.

UPDATELEDS packets coalesce per controller (a newer frame replaces the
pending one), so a stalled client's queue stays bounded to one packet
per controller instead of growing without bound; non-coalescable events
fall back to a hard cap with drop-oldest. The client destructor stops
the thread with shutdown(SD_BOTH), which unblocks an in-flight send, so
the join at teardown cannot hang. A queue entry owns its packet header
and data buffer; the send thread frees the buffer after the send, so
there is no copy and the packet outlives the controller safely.

Follow-up: the send still takes the shared send_in_progress, so a slow
client can still stall sends to others; routing response sends through
the same per-client path would make that a per-client lock.

net_port: add SD_BOTH (SHUT_RDWR) for the POSIX socket shims.
2026-07-29 11:25:06 -05:00

337 lines
21 KiB
C++

/*---------------------------------------------------------*\
| NetworkServer.h |
| |
| OpenRGB SDK network server |
| |
| Adam Honse (CalcProgrammer1) 09 May 2020 |
| |
| This file is part of the OpenRGB project |
| SPDX-License-Identifier: GPL-2.0-or-later |
\*---------------------------------------------------------*/
#pragma once
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <list>
#include <map>
#include <mutex>
#include <thread>
#include <queue>
#include <vector>
#include "LogManager.h"
#include "RGBController.h"
#include "NetworkProtocol.h"
#include "net_port.h"
#include "PluginManagerInterface.h"
#include "ProfileManager.h"
#include "ResourceManager.h"
#include "SettingsManager.h"
#define MAXSOCK 32
#define TCP_TIMEOUT_SECONDS 5
/*---------------------------------------------------------*\
| Backstop cap on a client's outbound queue. UPDATELEDS |
| coalesces per controller, so this is only hit by a flood |
| of non-coalescable events to a stalled client; the oldest |
| are dropped rather than grow without bound. |
\*---------------------------------------------------------*/
#define NETWORKSERVER_SIGNAL_QUEUE_MAX 1024
typedef void (*NetServerCallback)(void*);
typedef struct
{
unsigned int controller_id;
bool coalescable;
NetPacketHeader header;
unsigned char* data;
} NetworkServerClientSendQueueEntry;
class NetworkClientInfo
{
public:
NetworkClientInfo();
~NetworkClientInfo();
SOCKET client_sock;
unsigned int client_flags;
std::thread* client_listen_thread;
std::string client_string;
unsigned int client_protocol_version;
std::string client_ip;
bool client_is_local;
bool client_is_local_client;
/*-----------------------------------------------------*\
| Per-client outbound queue for SignalUpdate packets, |
| drained by a dedicated send thread so a slow client |
| never blocks SignalUpdate (which would deadlock a |
| rescan). UPDATELEDS packets coalesce per controller, |
| keeping the queue bounded to one per controller. |
\*-----------------------------------------------------*/
std::list<NetworkServerClientSendQueueEntry> client_send_queue;
std::map<unsigned int, std::list<NetworkServerClientSendQueueEntry>::iterator> client_send_coalesce;
std::mutex client_send_mutex;
std::condition_variable client_send_cv;
std::thread* client_send_thread;
std::atomic<bool> client_send_running;
};
typedef struct
{
RGBController* controller;
unsigned int id;
} NetworkControllerID;
typedef struct
{
NetPacketHeader header;
unsigned char* data;
NetworkClientInfo* client_info;
} NetworkServerControllerThreadQueueEntry;
typedef struct
{
unsigned int id;
unsigned int index;
std::queue<NetworkServerControllerThreadQueueEntry> queue;
std::mutex queue_mutex;
std::mutex start_mutex;
std::condition_variable start_cv;
std::thread* thread;
std::atomic<bool> online;
} NetworkServerControllerThread;
class NetworkServer
{
public:
NetworkServer();
~NetworkServer();
/*-----------------------------------------------------*\
| Server Information functions |
\*-----------------------------------------------------*/
std::string GetHost();
unsigned short GetPort();
bool GetOnline();
bool GetListening();
unsigned int GetNumClients();
const char * GetClientString(unsigned int client_num);
const char * GetClientIP(unsigned int client_num);
unsigned int GetClientProtocolVersion(unsigned int client_num);
/*-----------------------------------------------------*\
| Signal that device list has been updated |
\*-----------------------------------------------------*/
void DeviceListUpdated();
/*-----------------------------------------------------*\
| Callback functions |
\*-----------------------------------------------------*/
void RegisterClientInfoChangeCallback(NetServerCallback, void* new_callback_arg);
void RegisterServerListeningChangeCallback(NetServerCallback, void* new_callback_arg);
/*-----------------------------------------------------*\
| Functions for forwarding callback sigals over network |
\*-----------------------------------------------------*/
void SignalLogManagerLoggedEntry(LogMessage& logged_entry);
void SignalProfileManagerUpdate(unsigned int update_reason);
void SignalResourceManagerUpdate(unsigned int update_reason);
/*-----------------------------------------------------*\
| Server Configuration functions |
\*-----------------------------------------------------*/
void SetHost(std::string host);
void SetLegacyWorkaroundEnable(bool enable);
void SetName(std::string new_name);
void SetPort(unsigned short new_port);
/*-----------------------------------------------------*\
| Server Control functions |
\*-----------------------------------------------------*/
void StartServer();
void StopServer();
/*-----------------------------------------------------*\
| Server Interface functions |
\*-----------------------------------------------------*/
void SetControllers(std::vector<RGBController*>);
void SetPluginManager(PluginManagerInterface* plugin_manager_pointer);
void SetProfileManager(ProfileManagerInterface* profile_manager_pointer);
void SetSettingsManager(SettingsManagerInterface* settings_manager_pointer);
void ProfileManager_ProfileAboutToLoad();
void SendRequest_ProfileManager_ActiveProfileChanged(std::string profile_name);
void SendRequest_ProfileManager_ProfileLoaded(std::string profile_json_string);
void SendRequest_RGBController_SignalUpdate(RGBController* controller_ptr, unsigned int update_reason);
private:
/*-----------------------------------------------------*\
| Server variables |
\*-----------------------------------------------------*/
std::string host;
bool legacy_workaround_enabled;
unsigned short port_num;
std::mutex send_in_progress;
unsigned int server_flags;
std::string server_name;
std::atomic<bool> server_online;
std::atomic<bool> server_listening;
SOCKET server_sock[MAXSOCK];
int socket_count;
/*-----------------------------------------------------*\
| Server controller list |
\*-----------------------------------------------------*/
std::vector<NetworkControllerID> controller_ids;
std::shared_mutex controller_ids_mutex;
unsigned int controller_next_idx;
std::vector<RGBController*> controllers;
std::vector<NetworkServerControllerThread*> controller_threads;
std::shared_mutex controller_threads_mutex;
bool controller_updating;
NetworkServerControllerThread* profilemanager_thread;
/*-----------------------------------------------------*\
| Server clients |
\*-----------------------------------------------------*/
std::mutex ServerClientsMutex;
std::vector<NetworkClientInfo*> ServerClients;
std::thread* ConnectionThread[MAXSOCK];
/*-----------------------------------------------------*\
| Client information change callbacks |
\*-----------------------------------------------------*/
std::mutex ClientInfoChangeMutex;
std::vector<NetServerCallback> ClientInfoChangeCallbacks;
std::vector<void*> ClientInfoChangeCallbackArgs;
/*-----------------------------------------------------*\
| Server listening change callbacks |
\*-----------------------------------------------------*/
std::mutex ServerListeningChangeMutex;
std::vector<NetServerCallback> ServerListeningChangeCallbacks;
std::vector<void*> ServerListeningChangeCallbackArgs;
/*-----------------------------------------------------*\
| Pointers to components that integrate with server |
\*-----------------------------------------------------*/
PluginManagerInterface* plugin_manager;
ProfileManagerInterface* profile_manager;
SettingsManagerInterface* settings_manager;
unsigned int profile_about_to_load_acks;
unsigned int profile_about_to_load_count;
private:
#ifdef WIN32
/*-----------------------------------------------------*\
| Windows-specific WSA data |
\*-----------------------------------------------------*/
WSADATA wsa;
#endif
/*-----------------------------------------------------*\
| Server callback signal functions |
\*-----------------------------------------------------*/
void SignalActiveProfileChanged();
void SignalClientInfoChanged();
void SignalDetectionCompleted();
void SignalDetectionProgress();
void SignalDetectionStarted();
void SignalDeviceListUpdated();
void SignalProfileListUpdated();
void SignalServerListeningChanged();
/*-----------------------------------------------------*\
| Server Thread functions |
\*-----------------------------------------------------*/
void ConnectionThreadFunction(int socket_idx);
void ControllerListenThread(NetworkServerControllerThread* this_thread);
void ListenThreadFunction(NetworkClientInfo* client_info);
void ClientSendThreadFunction(NetworkClientInfo* client_info);
void ProfileManagerListenThread(NetworkServerControllerThread* this_thread);
/*-----------------------------------------------------*\
| Server Protocol functions |
\*-----------------------------------------------------*/
NetPacketStatus ProcessRequest_ClientFlags(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_ClientProtocolVersion(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_ClientString(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_RescanDevices();
NetPacketStatus ProcessRequest_GetHIDDeviceInfo(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_GetI2CBusInfo(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_GetSerialPorts(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_GetUSBDeviceInfo(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_GetUSBSerialPorts(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_LogManager_ClearLogBuffer(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_LogManager_GetLogBuffer(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_LogManager_GetLogLevel(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_LogManager_SetLogLevel(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_ProfileManager_ClearActiveProfile(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_ProfileManager_DeleteProfile(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_ProfileManager_DownloadProfile(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_ProfileManager_GetActiveProfile(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_ProfileManager_GetProfileList(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_ProfileManager_LoadProfile(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_ProfileManager_SaveProfile(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_ProfileManager_UploadProfile(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_SettingsManager_GetSettings(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_SettingsManager_GetSettingsSchema(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_SettingsManager_ModifySettings(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_SettingsManager_SetSettings(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
NetPacketStatus ProcessRequest_SettingsManager_SaveSettings(NetworkClientInfo* client_info);
NetPacketStatus ProcessRequest_RGBController_AddSegment(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_ClearSegments(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_ConfigureZone(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_ResizeZone(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_ConfigureDevice(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_SetCustomMode(NetworkClientInfo* client_info, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_SetDeviceSpecificConfiguration(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_SetDeviceSpecificZoneConfiguration(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_SetHidden(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_UpdateLEDs(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_UpdateSaveMode(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id, bool save_mode);
NetPacketStatus ProcessRequest_RGBController_UpdateSingleLED(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_UpdateZoneLEDs(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
NetPacketStatus ProcessRequest_RGBController_UpdateZoneMode(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int controller_id);
void SendAck(NetworkClientInfo* client_info, unsigned int acked_pkt_dev_id, unsigned int acked_pkt_id, NetPacketStatus status);
void SendReply_ControllerCount(NetworkClientInfo* client_info);
void SendReply_ControllerData(NetworkClientInfo* client_info, unsigned int dev_id, unsigned int protocol_version);
void SendReply_ProtocolVersion(NetworkClientInfo* client_info);
void SendReply_ServerFlags(NetworkClientInfo* client_info);
void SendReply_ServerString(NetworkClientInfo* client_info);
void SendReply_PluginList(NetworkClientInfo* client_info);
void SendReply_PluginSpecific(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr, unsigned int pkt_id);
void SendRequest_DetectionCompleted(NetworkClientInfo* client_info);
void SendRequest_DetectionProgress(NetworkClientInfo* client_info, unsigned int detection_percent, std::string detection_string);
void SendRequest_DetectionStarted(NetworkClientInfo* client_info);
void SendRequest_DeviceListChanged(NetworkClientInfo* client_info);
void SendRequest_LoggedEntry(NetworkClientInfo* client_info, unsigned int data_size, unsigned char* data_ptr);
void SendRequest_ProfileManager_ActiveProfileChanged(NetworkClientInfo* client_info, std::string active_profile);
void SendRequest_ProfileManager_ProfileAboutToLoad();
void SendRequest_ProfileManager_ProfileListChanged(NetworkClientInfo* client_info, unsigned char* profile_list_description);
/*-----------------------------------------------------*\
| Private helper functions |
\*-----------------------------------------------------*/
int accept_select(int sockfd);
unsigned int index_from_id(unsigned int id, unsigned int protocol_version, bool* index_valid);
int recv_select(SOCKET s, char *buf, int len, int flags);
};