summaryrefslogtreecommitdiff
path: root/indra/llcorehttp/llwebsocketmgr.cpp
diff options
context:
space:
mode:
authorRider Linden <rider@lindenlab.com>2025-08-22 18:32:45 -0700
committerRider Linden <rider@lindenlab.com>2025-10-07 09:19:32 -0700
commite27b363a9a315a5fce53d0d036095ab33e37eee3 (patch)
treec4f424593b42634a7b13097c1a0230e60cf75093 /indra/llcorehttp/llwebsocketmgr.cpp
parentdcf6d8c5024c831d54f0f5f140551e65748ed513 (diff)
Finish blocking run and adding documentation.
Diffstat (limited to 'indra/llcorehttp/llwebsocketmgr.cpp')
-rw-r--r--indra/llcorehttp/llwebsocketmgr.cpp287
1 files changed, 273 insertions, 14 deletions
diff --git a/indra/llcorehttp/llwebsocketmgr.cpp b/indra/llcorehttp/llwebsocketmgr.cpp
index 0c61e0f68d..9fc9027be3 100644
--- a/indra/llcorehttp/llwebsocketmgr.cpp
+++ b/indra/llcorehttp/llwebsocketmgr.cpp
@@ -36,6 +36,10 @@
#include <websocketpp/client.hpp>
#include <websocketpp/server.hpp>
+#include <thread>
+#include <atomic>
+#include <chrono>
+
//------------------------------------------------------------------------
namespace
{
@@ -149,10 +153,39 @@ void LLWebsocketMgr::stopAllServers()
mServers.clear();
}
-
//------------------------------------------------------------------------
+/**
+ * @struct Server_impl
+ * @brief Internal implementation wrapper for websocketpp server functionality
+ *
+ * This structure serves as a PIMPL (Pointer to Implementation) wrapper around
+ * the websocketpp::server template, providing a clean interface between the
+ * high-level WSServer class and the low-level websocketpp library. It handles
+ * all direct websocketpp interactions including server lifecycle management,
+ * event handling, and connection management.
+ *
+ * The Server_impl follows Linden Lab conventions while maintaining compatibility
+ * with the websocketpp library. It provides thread-safe operations where possible
+ * and integrates with the existing logging infrastructure.
+ *
+ * @note This class uses the websocketpp::config::asio configuration which provides
+ * ASIO-based networking without TLS/SSL support.
+ */
struct Server_impl
{
+ /**
+ * @brief Constructor - Initializes the websocketpp server with configuration
+ * @param owner Pointer to the owning WSServer instance (must not be null)
+ * @param port The port number to bind the server to (1-65535)
+ * @param local_only If true, binds only to localhost; if false, binds to all interfaces
+ *
+ * Sets up the websocketpp server instance and registers lambda-based event handlers
+ * for connection open, close, and message events. The handlers delegate back to the
+ * owning WSServer instance for processing, maintaining the abstraction layer.
+ *
+ * @warning The owner pointer must remain valid for the lifetime of this object
+ * @pre port must be a valid port number (typically > 1024 for non-privileged access)
+ */
Server_impl(LLWebsocketMgr::WSServer *owner, U16 port, bool local_only) :
mOwner(owner),
mPort(port),
@@ -165,6 +198,19 @@ struct Server_impl
~Server_impl() = default;
+ /**
+ * @brief Initialize the websocketpp server and configure listening
+ *
+ * Performs the initial setup of the websocketpp server by calling init_asio()
+ * to initialize the ASIO networking layer, then configures the server to listen
+ * on the specified port. The binding behavior depends on the mLocalOnly flag:
+ * - If mLocalOnly is true: binds to "127.0.0.1" (localhost only)
+ * - If mLocalOnly is false: binds to all available network interfaces
+ *
+ * @note This method must be called before attempting to start the server
+ * @pre The server must not already be initialized
+ * @post The server is ready to accept connections when start() is called
+ */
void init()
{
mServer.init_asio();
@@ -180,6 +226,20 @@ struct Server_impl
}
}
+ /**
+ * @brief Start the websocket server and begin accepting connections
+ * @return true if server started successfully, false on error
+ *
+ * Runs a controlled event loop that periodically checks the stop flag for clean shutdown.
+ * Instead of calling run() once and blocking indefinitely, this implementation uses
+ * run_for() with a timeout to process events in chunks, checking mOwner->mShouldStop between
+ * iterations to allow for responsive termination.
+ *
+ * @note This method blocks the calling thread until the server stops
+ * @pre init() must have been called successfully
+ * @post On success, the server is actively accepting connections
+ * @warning Any exceptions during startup are caught and logged as warnings
+ */
bool start()
{
if (!mServer.stopped())
@@ -187,19 +247,61 @@ struct Server_impl
LL_WARNS("WebSocket") << "WebSocket server is already running" << LL_ENDL;
return false;
}
+
try
{
mServer.start_accept();
- mServer.run();
+
+ // Run controlled event loop with periodic stop flag checking
+ while (!mOwner->mShouldStop && !mServer.stopped())
+ {
+ // Process events for up to 100ms, then check the stop flag
+ std::chrono::milliseconds timeout(100);
+ std::size_t handlers_run = mServer.get_io_service().run_for(timeout);
+
+ // If no handlers were run and the server isn't stopped,
+ // reset the io_service for the next iteration
+ if (handlers_run == 0 && !mServer.stopped() && !mOwner->mShouldStop)
+ {
+ mServer.get_io_service().restart();
+ }
+ }
+
+ LL_INFOS("WebSocket") << "WebSocket server event loop exited cleanly" << LL_ENDL;
+ return true;
+ }
+ catch (const websocketpp::exception& e)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server exception: " << e.what() << LL_ENDL;
+ return false;
}
catch (const std::exception& e)
{
- LL_WARNS("WebSocket") << "WebSocket server encountered an error: " << e.what() << LL_ENDL;
+ LL_WARNS("WebSocket") << "WebSocket server std::exception: " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (...)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server unknown exception" << LL_ENDL;
return false;
}
- return true;
}
+ /**
+ * @brief Stop the websocket server and cease accepting new connections
+ *
+ * Gracefully shuts down the server by first stopping the listener to prevent
+ * new connections, then stopping the ASIO event loop. Existing connections
+ * may remain active briefly during the shutdown process.
+ *
+ * The method performs a safe shutdown by checking if the server is already
+ * stopped before attempting shutdown operations. Any exceptions during shutdown
+ * are caught and logged but do not propagate.
+ *
+ * @note This method is non-blocking and safe to call multiple times
+ * @post The server will no longer accept new connections
+ * @post Existing connections will be cleanly terminated
+ */
void stop()
{
if (mServer.stopped())
@@ -217,22 +319,65 @@ struct Server_impl
}
}
+ /**
+ * @brief Handle new connection establishment event
+ * @param hdl WebSocket connection handle from websocketpp
+ *
+ * Called automatically by the websocketpp library when a new client connection
+ * is successfully established. This method serves as a bridge between the
+ * low-level websocketpp callback and the high-level WSServer interface.
+ *
+ * @note This is an internal callback method called by websocketpp
+ * @pre mOwner must be valid (assertion will fail if null)
+ * @post The owning WSServer will be notified of the new connection
+ */
void onOpen(websocketpp::connection_hdl hdl) const
{
- assert(mOwner); // mOwner should never be null. If it is, something is very wrong!
+ LL_ERRS_IF(!mOwner, "WebSocket") << "mOwner should never be null. If it is, something is very wrong!" << LL_ENDL;
mOwner->handleOpenConnection(hdl);
}
+ /**
+ * @brief Handle connection closure event
+ * @param hdl WebSocket connection handle from websocketpp
+ *
+ * Called automatically by the websocketpp library when a client connection
+ * is closed, either by the client, server, or due to a network error.
+ * This method delegates the event to the owning WSServer for processing.
+ *
+ * @note This is an internal callback method called by websocketpp
+ * @pre mOwner must be valid (assertion will fail if null)
+ * @post The owning WSServer will be notified of the connection closure
+ */
void onClose(websocketpp::connection_hdl hdl) const
{
- assert(mOwner); // mOwner should never be null
+ LL_ERRS_IF(!mOwner, "WebSocket") << "mOwner should never be null" << LL_ENDL;
mOwner->handleCloseConnection(hdl);
}
+ /**
+ * @brief Handle incoming message from client
+ * @param hdl WebSocket connection handle identifying the sender
+ * @param msg Shared pointer to the message object containing payload and metadata
+ *
+ * Called automatically by the websocketpp library when a complete message is
+ * received from a client. This method validates the connection exists, extracts
+ * the message payload, and forwards it to the appropriate connection handler.
+ *
+ * Currently handles text messages only. Binary message support and message
+ * fragmentation handling are noted as TODO items for future implementation.
+ *
+ * @note This is an internal callback method called by websocketpp
+ * @pre mOwner must be valid (assertion will fail if null)
+ * @post If connection exists, the message is forwarded for processing
+ * @todo Add support for binary messages
+ * @todo Implement fragmented message handling
+ * @todo Process connection close codes for graceful closure
+ */
void onMessage(websocketpp::connection_hdl hdl, Server_t::message_ptr msg) const
{
- assert(mOwner); // mOwner should never be null
+ LL_ERRS_IF(!mOwner, "WebSocket") << "mOwner should never be null" << LL_ENDL;
LLWebsocketMgr::WSConnection::ptr_t connection = mOwner->getConnection(hdl);
if (!connection)
{
@@ -247,10 +392,10 @@ struct Server_impl
}
//-------------------------------------------
- Server_t mServer;
- LLWebsocketMgr::WSServer* mOwner{ nullptr }; // Back-reference to the owning server, note that this pointer can never be null
- U16 mPort{ 0 };
- bool mLocalOnly{ true };
+ Server_t mServer; ///< The underlying websocketpp server instance
+ LLWebsocketMgr::WSServer* mOwner{ nullptr }; ///< Back-reference to the owning WSServer instance (guaranteed non-null)
+ U16 mPort{ 0 }; ///< TCP port number the server listens on
+ bool mLocalOnly{ true }; ///< Whether to bind to localhost only (true) or all interfaces (false)
};
//------------------------------------------------------------------------
@@ -266,6 +411,12 @@ LLWebsocketMgr::WSServer::WSServer(std::string_view name, U16 port, bool local_o
" on port " << mImpl->mPort << LL_ENDL;
}
+LLWebsocketMgr::WSServer::~WSServer()
+{
+ // Ensure the server is stopped before destruction
+ stop();
+}
+
LLWebsocketMgr::WSConnection::ptr_t LLWebsocketMgr::WSServer::connectionFactory(LLWebsocketMgr::WSServer::ptr_t server,
LLWebsocketMgr::connection_h handle)
{
@@ -275,19 +426,75 @@ LLWebsocketMgr::WSConnection::ptr_t LLWebsocketMgr::WSServer::connectionFactory(
bool LLWebsocketMgr::WSServer::start()
{
LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
- return mImpl->start();
+
+ LLMutexLock lock(&mThreadMutex);
+
+ // Check if already running
+ if (isRunning())
+ {
+ LL_WARNS("WebSocket") << "Server " << mServerName << " is already running" << LL_ENDL;
+ return false;
+ }
+
+ // Reset the stop flag
+ mShouldStop = false;
+
+ // Start the server thread
+ mServerThread = std::thread([this]() {
+ LL_INFOS("WebSocket") << "WebSocket server thread starting for: " << mServerName << LL_ENDL;
+
+ // Run the controlled server loop that checks the stop flag
+ // Server_impl accesses mShouldStop through the mOwner pointer
+ bool success = mImpl->start();
+
+ if (!success)
+ {
+ LL_WARNS("WebSocket") << "WebSocket server thread failed to start for: " << mServerName << LL_ENDL;
+ }
+
+ LL_INFOS("WebSocket") << "WebSocket server thread exiting for: " << mServerName << LL_ENDL;
+ });
+
+ LL_INFOS("WebSocket") << "Started WebSocket server thread: " << mServerName << LL_ENDL;
+ return true;
}
void LLWebsocketMgr::WSServer::stop()
{
LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
- mImpl->stop();
+
+ {
+ LLMutexLock lock(&mThreadMutex);
+
+ // Check if already stopped
+ if (!isRunning())
+ {
+ return;
+ }
+
+ LL_INFOS("WebSocket") << "Stopping WebSocket server: " << mServerName << LL_ENDL;
+
+ // Signal the thread to stop
+ mShouldStop = true;
+
+ // Stop the websocket server (this will cause the controlled run loop to exit)
+ mImpl->stop();
+ } // Release the lock here
+
+ // Wait for the thread to finish (outside the lock to avoid deadlock)
+ if (mServerThread.joinable())
+ {
+ mServerThread.join();
+ LL_INFOS("WebSocket") << "WebSocket server thread joined for: " << mServerName << LL_ENDL;
+ }
}
bool LLWebsocketMgr::WSServer::isRunning() const
{
LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
- return !mImpl->mServer.stopped();
+
+ // Check both the thread state, websocket server state, and the stop flag
+ return mServerThread.joinable() && !mImpl->mServer.stopped() && !mShouldStop;
}
void LLWebsocketMgr::WSServer::broadcastMessage(const std::string& message)
@@ -313,6 +520,41 @@ bool LLWebsocketMgr::WSServer::sendMessageTo(const connection_h& handle, const s
return true;
}
+bool LLWebsocketMgr::WSServer::closeConnection(const connection_h& handle, U16 code, const std::string& reason)
+{
+ LL_ERRS_IF(!mImpl, "WebSocket") << "WebSocket server " << mServerName << " implementation is null !" << LL_ENDL;
+
+ try
+ {
+ websocketpp::lib::error_code ec;
+ mImpl->mServer.close(handle, code, reason, ec);
+ if (ec)
+ {
+ LL_WARNS("WebSocket") << mServerName << " failed to close connection: " << ec.message() << LL_ENDL;
+ return false;
+ }
+
+ LL_INFOS("WebSocket") << mServerName << " initiated close for connection with code "
+ << code << " and reason: " << reason << LL_ENDL;
+ return true;
+ }
+ catch (const websocketpp::exception& e)
+ {
+ LL_WARNS("WebSocket") << mServerName << " exception closing connection: " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (const std::exception& e)
+ {
+ LL_WARNS("WebSocket") << mServerName << " std::exception closing connection: " << e.what() << LL_ENDL;
+ return false;
+ }
+ catch (...)
+ {
+ LL_WARNS("WebSocket") << mServerName << " unknown exception closing connection" << LL_ENDL;
+ return false;
+ }
+}
+
LLWebsocketMgr::WSConnection::ptr_t LLWebsocketMgr::WSServer::getConnection(const connection_h& handle)
{
LLMutexLock lock(&mConnectionMutex);
@@ -409,3 +651,20 @@ bool LLWebsocketMgr::WSConnection::sendMessage(const std::string& message)
}
return mServer->sendMessageTo(mConnectionHandle, message);
}
+
+void LLWebsocketMgr::WSConnection::closeConnection(U16 code, const std::string& reason)
+{
+ if (!mServer)
+ {
+ LL_WARNS("WebSocket") << "Attempted to close connection with null server reference" << LL_ENDL;
+ return;
+ }
+
+ LL_INFOS("WebSocket") << "WSConnection closing connection with code " << code
+ << " and reason: " << (reason.empty() ? "(no reason)" : reason) << LL_ENDL;
+
+ if (!mServer->closeConnection(mConnectionHandle, code, reason))
+ {
+ LL_WARNS("WebSocket") << "Failed to close connection through server" << LL_ENDL;
+ }
+}