diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 4bb311b..fd0f367 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -11,6 +11,10 @@ Server::Server(int port) : port(port), serverSocket(INVALID_SOCKET), eventLoop = makeEventLoop(); + userSessionManager.set_on_session_expired( + [this](SocketType sock) + { onSessionExpired(sock); }); + #ifdef _WIN32 WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) @@ -134,6 +138,9 @@ void Server::stop() std::cout << "[SERVER] Stop requested...\n"; running = false; + for (auto &[sock, conn] : connections) + CloseSocket(sock); + connections.clear(); userSessionManager.close_all_sessions(); CloseSocket(serverSocket); // Forces accept() loop to unblock by breaking the file descriptor channel @@ -154,6 +161,18 @@ void Server::runEventLoop() while (running) { + // Close all the expired connections + { + std::vector close; + { + std::lock_guard lock(expiredMutex); + close.swap(expiredSockets); + } + for (auto &sock : close){ + std::cout << "[SERVER] Closing expired connection, socket " << sock << "\n"; + closeConnection(sock); + } + } int numEvents = eventLoop->wait(events); if (numEvents == -1) continue; // wait() itself failed, try again @@ -298,4 +317,11 @@ void Server::messageHandler(SocketType clientSocket, const SessionKey &sessionKe std::string response = "No command was received\n"; send(clientSocket, response.c_str(), response.length(), 0); } +} + +void Server::onSessionExpired(SocketType sock) +{ + std::cout << "[SERVER] Session expired, queuing socket " << sock << " for close\n"; + std::lock_guard lock(expiredMutex); + expiredSockets.push_back(sock); } \ No newline at end of file diff --git a/src/Server/Server.h b/src/Server/Server.h index 5605944..3b8a163 100644 --- a/src/Server/Server.h +++ b/src/Server/Server.h @@ -6,7 +6,7 @@ #include #include -#include "UserSessionBackgroundWorker.h" +#include "../Worker/UserSessionBackgroundWorker.h" #include "../RAM/HashMap.h" #include "../Storage/Persistence.h" #include "../Worker/ThreadPool.h" @@ -42,6 +42,8 @@ class Server std::thread eventLoopThread; /** Thread that runs runEventLoop() */ std::mutex busyMutex; /** Guards busySockets */ std::unordered_set busySockets; /** Sockets with a recv job already queued/running */ + std::mutex expiredMutex; /** Mutex to guard the expiredSockets vector */ + std::vector expiredSockets; /** Notifys the event loop of connections to remove */ /** * @brief Runs continuously on eventLoopThread: waits for socket readiness @@ -91,6 +93,12 @@ class Server * @param sessionKey The session tied to this client. */ void messageHandler(SocketType clientSocket, const SessionKey &sessionKey); + + /** + * @brief Adds sockets that are expired into the expired vector to be removed later by eventloop + * @param sock which is the client socket that is to be removed + */ + void onSessionExpired(SocketType sock); }; #endif \ No newline at end of file diff --git a/src/UserSession/UserSession.cpp b/src/UserSession/UserSession.cpp index 6a8d318..1a26428 100644 --- a/src/UserSession/UserSession.cpp +++ b/src/UserSession/UserSession.cpp @@ -43,6 +43,9 @@ void UserSessionManager::cleanup_expired_sessions() { for (auto it = users_sessions.begin(); it != users_sessions.end(); ) { if (it->second->is_expired()) { + // Notify Server before erasing so it can tear down + if(on_session_expired) + on_session_expired(it->second->get_socket()); it = users_sessions.erase(it); // Terminate Will call Automatically } else { ++it; @@ -56,6 +59,11 @@ void UserSessionManager::close_all_sessions() { } +void UserSessionManager::set_on_session_expired(std::function cb) { + on_session_expired = std::move(cb); +} + + // --- UserSession Implementation --- UserSessionManager::UserSession::UserSession(SocketType socket, const sockaddr_in& client_addr, std::chrono::seconds max_idle) @@ -79,7 +87,11 @@ void UserSessionManager::UserSession::update_last_activity_time() { } void UserSessionManager::UserSession::terminate_session() { - CloseSocket(user_socket); + /** Socket closure is Server's responsibility (see closeConnection()). + Closing it here would race Server's connections/eventLoop bookkeeping. */ + + + // CloseSocket(user_socket); } bool UserSessionManager::UserSession::is_expired() const { diff --git a/src/UserSession/UserSession.h b/src/UserSession/UserSession.h index 9c98b0d..2ef8036 100644 --- a/src/UserSession/UserSession.h +++ b/src/UserSession/UserSession.h @@ -11,6 +11,7 @@ #include #include #include +#include /** @@ -88,7 +89,17 @@ class UserSessionManager { */ void close_all_sessions(); + /** + * @brief Registers a callback fired when a session expires from idle timeout. + * @param cb Invoked with the session's socket, before the session is erased. + */ + void set_on_session_expired(std::function cb); + private: + + /** @brief Fired from cleanup_expired_sessions() so Server can tear down the connection. */ + std::function on_session_expired; + /** * @class UserSession * @brief Represents an individual user's active session state. @@ -121,7 +132,8 @@ class UserSessionManager { void update_last_activity_time(); /** - * @brief Gracefully shuts down and terminates the network session. + * @brief Releases session state. Does not close the socket - + * Server owns socket lifetime and closes it via closeConnection(). */ void terminate_session(); @@ -133,6 +145,12 @@ class UserSessionManager { const std::string& get_ip_str() const { return cached_ip_str; } const std::string& get_port_str() const { return cached_port_str; } + /** + * @brief Gets the socket owned by this session. + * @return The session's socket. + */ + SocketType get_socket() const { return user_socket; } + private: SocketType user_socket; /**< The network socket for this specific user. */ std::chrono::steady_clock::time_point last_activity_time; /**< Timestamp of the last recorded user activity. */ diff --git a/src/Worker/UserSessionBackgroundWorker.cpp b/src/Worker/UserSessionBackgroundWorker.cpp index c362f8a..884bc24 100644 --- a/src/Worker/UserSessionBackgroundWorker.cpp +++ b/src/Worker/UserSessionBackgroundWorker.cpp @@ -29,7 +29,7 @@ void UserSessionBackgroundWorker::run() { if (!active) { break; } - std::cout << "UserSessionBackgroundWorker::run()" << std::endl; + std::cout << "[USBM] Running looking for idle connections" << std::endl; lock.unlock(); this->user_sessions.cleanup_expired_sessions(); }