From 8313a3b7770bc91513685744f6d852dc383e0fc4 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 28 Mar 2026 13:01:13 +0100 Subject: [PATCH 1/5] Add: lastActivity field to Session and update from SessionReader --- .../cs108/casono/server/network/sessions/Session.java | 11 +++++++++++ .../casono/server/network/sessions/SessionReader.java | 1 + 2 files changed, 12 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java index d6e2094..4cdda34 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java @@ -6,12 +6,14 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.CommandParserDispat import ch.unibas.dmi.dbis.cs108.casono.server.network.response.PrimitiveResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer; import java.io.IOException; +import java.time.Instant; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; /** Represents a client session in the network server. */ public class Session { private final SessionId id; + private Instant lastActivity; private final TransportLayer transport; private final BlockingQueue responseQueue; private final CommandParserDispatcher dispatcher; @@ -31,6 +33,7 @@ public class Session { CommandParserDispatcher dispatcher, CommandRouter router) { this.id = new SessionId(); + this.lastActivity = Instant.now(); this.transport = transport; this.dispatcher = dispatcher; this.router = router; @@ -46,6 +49,14 @@ public class Session { return this.id; } + public Instant getLastInboundActivity() { + return lastActivity; + } + + public void updateLastInboundActivity() { + this.lastActivity = Instant.now(); + } + /** * Returns the TransportLayer of this session * diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionReader.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionReader.java index cd74de9..ad90468 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionReader.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionReader.java @@ -43,6 +43,7 @@ public class SessionReader implements Runnable { RawPacket rawPacket = null; try { rawPacket = transport.read(); + session.updateLastInboundActivity(); logger.debug("Recieved: {}", rawPacket); RawRequest rawRequest = ProtocolParser.parse(rawPacket.payload()); From c1cf6c1f83599600086baf992ca2cd50bbc6b8df Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 28 Mar 2026 13:01:43 +0100 Subject: [PATCH 2/5] Add: getAllSessions method to SessionManager --- .../casono/server/network/sessions/SessionManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java index b3d52a6..8dae02d 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java @@ -6,8 +6,10 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.CommandParserDispatcher; import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer; import java.io.IOException; +import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -109,4 +111,8 @@ public class SessionManager { return handle.session(); } + + public Collection getAllSessions() { + return sessions.values().stream().map(SessionHandle::session).collect(Collectors.toList()); + } } From 1547eda410fa84e79e1b8199342e7239689b0354 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 28 Mar 2026 13:11:59 +0100 Subject: [PATCH 3/5] Add: SessionDisconnectJob to publish DisconnectEvent when client timeouts --- .../sessions/SessionDisconnectJob.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionDisconnectJob.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionDisconnectJob.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionDisconnectJob.java new file mode 100644 index 0000000..6fd481d --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionDisconnectJob.java @@ -0,0 +1,40 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; +import java.time.Duration; +import java.time.Instant; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class SessionDisconnectJob implements Runnable { + private final Logger logger; + private final SessionManager sessionManager; + private final EventBus eventBus; + private final Duration timeoutThreshold; + + public SessionDisconnectJob( + SessionManager sessionManager, EventBus eventBus, Duration timeoutThreshold) { + this.logger = LogManager.getLogger(SessionDisconnectJob.class); + this.sessionManager = sessionManager; + this.eventBus = eventBus; + this.timeoutThreshold = timeoutThreshold; + } + + @Override + public void run() { + logger.debug("Job started."); + Instant threshold = Instant.now().minus(timeoutThreshold); + + for (Session session : sessionManager.getAllSessions()) { + if (session.getLastInboundActivity().isBefore(threshold)) { + eventBus.publish(new DisconnectEvent(session.getId())); + logger.info( + "Initiated disconnect of {}, as it hasn't been active since a while", + session.getId()); + } + } + + logger.debug("Job finished."); + } +} From fc4a88ee95afc1443cf3ab2a671ce0bead497987 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 28 Mar 2026 13:12:30 +0100 Subject: [PATCH 4/5] Add: SessionDisconnectJob scheduling to ServerApp --- .../dbis/cs108/casono/server/ServerApp.java | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index 2391450..e76e877 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -7,6 +7,7 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.command.CommandRouter; import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; import ch.unibas.dmi.dbis.cs108.casono.server.network.parser.CommandParserDispatcher; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionDisconnectJob; import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; import java.time.Duration; import java.util.concurrent.Executors; @@ -17,9 +18,12 @@ import org.apache.logging.log4j.Logger; /** Application class for starting the server. */ public class ServerApp { - public static final int USER_CLEANUP_JOB_DELAY = 0; - public static final int USER_CLEANUP_JOB_PERIOD = 10; - public static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10; + private static final int USER_CLEANUP_JOB_DELAY = 0; + private static final int USER_CLEANUP_JOB_PERIOD = 10; + private static final int USER_CLEANUP_JOB_RECONNECT_THRESHOLD = 10; + private static final int SESSION_DISCONNECT_JOB_DELAY = 0; + private static final int SESSION_DISCONNECT_JOB_PERIOD = 2; + private static final int SESSION_DISCONNECT_JOB_TIMEOUT = 5; public static void start(String arg) { int port = Integer.parseInt(arg); @@ -45,6 +49,14 @@ public class ServerApp { USER_CLEANUP_JOB_DELAY, USER_CLEANUP_JOB_PERIOD, TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate( + new SessionDisconnectJob( + sessionManager, + eventBus, + Duration.ofSeconds(SESSION_DISCONNECT_JOB_TIMEOUT)), + SESSION_DISCONNECT_JOB_DELAY, + SESSION_DISCONNECT_JOB_PERIOD, + TimeUnit.SECONDS); networkManager.start(); } From 1f4ccb10333b6cf77987e105104bfb64dc6afcf9 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sat, 28 Mar 2026 13:12:43 +0100 Subject: [PATCH 5/5] Docs: JavaDoc for Session --- .../dbis/cs108/casono/server/network/sessions/Session.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java index 4cdda34..3fc97e5 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java @@ -49,10 +49,16 @@ public class Session { return this.id; } + /** + * Gets the timestamp of the last inbound activity for this session. + * + * @return an {@link Instant} representing the time of the last inbound activity + */ public Instant getLastInboundActivity() { return lastActivity; } + /** Updates the timestamp of the last inbound activity for this session. */ public void updateLastInboundActivity() { this.lastActivity = Instant.now(); }