Skip to content

Added logging in TCP and improved exception handling (removed logging because of other PR)#101

Open
gitnes94 wants to merge 8 commits intomainfrom
feature/tcplogger
Open

Added logging in TCP and improved exception handling (removed logging because of other PR)#101
gitnes94 wants to merge 8 commits intomainfrom
feature/tcplogger

Conversation

@gitnes94
Copy link

@gitnes94 gitnes94 commented Feb 26, 2026

Summary by CodeRabbit

  • Bug Fixes

    • Server now exits cleanly on interruption, uses per-connection timeouts, ensures sockets are closed on error, and returns a guarded internal 500 response when needed.
  • Refactor

    • Simplified connection/request lifecycle, reduced noisy startup output, centralized internal error handling, and added quiet-close cleanup behavior.
  • Tests

    • Improved tests to better simulate socket states and verify response content and UTF-8 encoding.

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

Reworks TcpServer accept loop to be interruption-aware, adds socket timeouts (server + client), decouples accept from client handling with new helpers, centralizes best-effort 500 handling and quiet socket/handler cleanup, and updates tests to mock connected sockets and assert response contents.

Changes

Cohort / File(s) Summary
Core server
src/main/java/org/example/TcpServer.java
Replaced blocking accept with timeout-driven, interruption-aware loop; added acceptAndHandleClient and startClientTask helpers; set ServerSocket and client socket timeouts; added handleInternalServerError, closeHandler, and closeQuietly; moved client lifecycle to try-with-resources; changed startup error to throw IllegalStateException; reduced noisy prints.
Tests
src/test/java/org/example/TcpServerTest.java
Updated socket mocks to simulate connected/open state; decode response with explicit UTF-8; adjusted assertions to check for "500", "Internal Server Error", and presence of Content-Type header; minor formatting/comments added.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client Socket
    participant Server as TcpServer
    participant Factory as ConnectionFactory
    participant Handler as ConnectionHandler
    participant Stream as OutputStream

    Client->>Server: connect()
    Server->>Server: accept() (timeout-driven, interruption-aware)
    Server->>Factory: createConnection(clientSocket)
    Factory-->>Handler: ConnectionHandler
    Server->>Server: startClientTask(clientSocket)
    Server->>Handler: processRequest() (try-with-resources)
    alt normal response
        Handler->>Stream: write response
        Stream-->>Client: response bytes
    else internal error
        Server->>Server: handleInternalServerError(clientSocket)
        Server->>Stream: write 500 response (best-effort)
        Stream-->>Client: 500 response bytes
    end
    Server->>Client: close (closeQuietly / try-with-resources)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nibbled at the accept loop till it learned to wake,
I set soft timeouts so no lonely bytes would take,
When things go sideways, I try a gentle "500" song,
I tuck each socket closed and hop along. 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions 'Added logging' and 'removed logging' which contradicts the actual changes—the PR focuses on exception handling improvements and refactoring without net logging additions. Update the title to accurately reflect the main change: 'Improve TCP server exception handling and refactor connection management' or similar, removing the confusing logging reference.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/tcplogger

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/org/example/TcpServer.java (1)

29-29: Consider TLS for production deployments with sensitive data.

The static analysis tool flagged that ServerSocket transmits data unencrypted. If this server handles sensitive information in production, consider using SSLServerSocketFactory to create an encrypted socket. For internal services or when TLS termination is handled by a load balancer/reverse proxy, plain TCP may be acceptable.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/TcpServer.java` at line 29, The code currently
creates an unencrypted listener with new ServerSocket(port) in the TcpServer
class; for production handling sensitive data replace that with an SSL/TLS
server socket created via SSLServerSocketFactory or an SSLContext (e.g.,
SSLServerSocketFactory.getDefault() or
sslContext.getServerSocketFactory().createServerSocket(port)), load a keystore
(KeyManagerFactory) and configure protocols/cipher suites and client-auth as
required, and make this behavior conditional on configuration so non-TLS
local/testing runs can still use the existing path; update the
accept/connection-handling code to use the resulting SSLServerSocket/SSLSocket
APIs and ensure proper certificate/key management and error handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/example/TcpServer.java`:
- Around line 29-43: The accept() loop uses
Thread.currentThread().isInterrupted() but accept() blocks and won't observe
interrupts; before the loop call serverSocket.setSoTimeout(timeoutMillis) to
make accept() periodically throw SocketTimeoutException, then catch
SocketTimeoutException around serverSocket.accept() and simply continue to
re-check Thread.currentThread().isInterrupted(), while preserving other
exceptions (log and break/cleanup); alternatively ensure your shutdown logic
closes the ServerSocket from another thread to unblock
serverSocket.accept()—refer to ServerSocket, serverSocket.accept(),
serverSocket.setSoTimeout(...), and SocketTimeoutException handling in your
changes.

---

Nitpick comments:
In `@src/main/java/org/example/TcpServer.java`:
- Line 29: The code currently creates an unencrypted listener with new
ServerSocket(port) in the TcpServer class; for production handling sensitive
data replace that with an SSL/TLS server socket created via
SSLServerSocketFactory or an SSLContext (e.g.,
SSLServerSocketFactory.getDefault() or
sslContext.getServerSocketFactory().createServerSocket(port)), load a keystore
(KeyManagerFactory) and configure protocols/cipher suites and client-auth as
required, and make this behavior conditional on configuration so non-TLS
local/testing runs can still use the existing path; update the
accept/connection-handling code to use the resulting SSLServerSocket/SSLSocket
APIs and ensure proper certificate/key management and error handling.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa1599a and 9aa87bc.

📒 Files selected for processing (1)
  • src/main/java/org/example/TcpServer.java

@EdvinSandgren EdvinSandgren self-requested a review February 26, 2026 09:44
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/main/java/org/example/TcpServer.java (1)

31-33: ⚠️ Potential issue | 🟠 Major

accept() still blocks shutdown despite interrupt-aware loop.

Line 31 checks interruption, but Line 32 can block indefinitely on accept(), so the loop may never re-check the flag during shutdown.

Suggested fix
 import java.net.ServerSocket;
 import java.net.Socket;
+import java.net.SocketTimeoutException;

 ...
     try (ServerSocket serverSocket = new ServerSocket(port)) {
+        serverSocket.setSoTimeout(1000);
         while (!Thread.currentThread().isInterrupted()) {
-            Socket clientSocket = serverSocket.accept();
+            Socket clientSocket;
+            try {
+                clientSocket = serverSocket.accept();
+            } catch (SocketTimeoutException e) {
+                continue; // periodic interrupt check
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/TcpServer.java` around lines 31 - 33, The accept()
call inside TcpServer's interrupt-aware loop blocks shutdown; make accept
interruptible by configuring the ServerSocket with a timeout and handling
SocketTimeoutException (e.g., call serverSocket.setSoTimeout(...)) or by closing
serverSocket from the shutdown path so accept() throws, then catch the resulting
exception and re-check Thread.currentThread().isInterrupted() to exit the loop;
update the loop that calls serverSocket.accept() to catch the
timeout/SocketException and continue/terminate appropriately.
🧹 Nitpick comments (1)
src/main/java/org/example/TcpServer.java (1)

55-57: Include throwable in warning log to preserve diagnostics.

Line 56 logs only the message text; include the exception object to retain stack trace/context.

Suggested refactor
-        } catch (IOException e) {
-            logger.log(Level.WARNING, () -> "Network error with client: " + e.getMessage());
+        } catch (IOException e) {
+            logger.log(Level.WARNING, "Network error with client", e);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/TcpServer.java` around lines 55 - 57, In the
TcpServer IOException catch block update the logger call to pass the caught
exception object so the stack trace and context are preserved: instead of
logging only e.getMessage() inside the lambda, call logger.log(Level.WARNING,
"Network error with client", e) (or equivalent logger.log overload) so the
throwable is included; modify the catch(IOException e) handler where logger is
used to include the exception parameter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main/java/org/example/TcpServer.java`:
- Around line 31-33: The accept() call inside TcpServer's interrupt-aware loop
blocks shutdown; make accept interruptible by configuring the ServerSocket with
a timeout and handling SocketTimeoutException (e.g., call
serverSocket.setSoTimeout(...)) or by closing serverSocket from the shutdown
path so accept() throws, then catch the resulting exception and re-check
Thread.currentThread().isInterrupted() to exit the loop; update the loop that
calls serverSocket.accept() to catch the timeout/SocketException and
continue/terminate appropriately.

---

Nitpick comments:
In `@src/main/java/org/example/TcpServer.java`:
- Around line 55-57: In the TcpServer IOException catch block update the logger
call to pass the caught exception object so the stack trace and context are
preserved: instead of logging only e.getMessage() inside the lambda, call
logger.log(Level.WARNING, "Network error with client", e) (or equivalent
logger.log overload) so the throwable is included; modify the catch(IOException
e) handler where logger is used to include the exception parameter.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa87bc and f33fb61.

📒 Files selected for processing (1)
  • src/main/java/org/example/TcpServer.java

@JohanHiths JohanHiths self-requested a review February 26, 2026 10:11
Copy link

@AnnaZiafar AnnaZiafar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Våra pr krockar med varandra. Har ett pr (create server logger) där jag skapar en centraliserad logger (xml) som kan användas av alla klasser. Lägger även till den i tcp-servern. Frågan är om man kanske vill köra med den eftersom alla klasser får enhetliga loggers då. Men jag tycker att du har fixat väldigt välstrukturerad och snygg felhantering. Vad tror ni om att bara mergea felhanteringen och använda den centralicerade loggern i den andra pr:en?

@JohanHiths
Copy link

Jag ser att du utvecklat koden ordentligt och den ser proffsig ut med snygg dokumentation och all felhantering! Nice

@gitnes94
Copy link
Author

gitnes94 commented Feb 26, 2026

Låter som ett jättebra förslag om du redan har fixat ett centraliserat system!

@gitnes94 gitnes94 changed the title Added logging in TCP and improved exception handling Added logging in TCP and improved exception handling (removed logging because of other PR) Feb 26, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/java/org/example/TcpServer.java (1)

61-63: Socket state check may give false confidence.

Socket.isConnected() returns true once connected and doesn't reflect if the remote peer has closed the connection. The check !client.isConnected() will rarely be false after a successful accept(). Similarly, isClosed() only reflects local close operations.

This is acceptable for a best-effort error response, but be aware these checks don't guarantee the write will succeed. The silent IOException catch at line 74 handles actual failures appropriately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/TcpServer.java` around lines 61 - 63, The socket
state checks in handleInternalServerError (the calls to client.isClosed() and
client.isConnected()) are misleading because isConnected() stays true after
accept and isClosed() only reflects local state; remove the optimistic state
gating and instead attempt the error response write unconditionally inside the
existing try/catch so any IOException will be handled as before (or, at minimum,
drop the isConnected() check and keep only isClosed() if you must guard
locally); update the comment to state that writes are best-effort and rely on
IOException handling in handleInternalServerError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/org/example/TcpServer.java`:
- Around line 61-63: The socket state checks in handleInternalServerError (the
calls to client.isClosed() and client.isConnected()) are misleading because
isConnected() stays true after accept and isClosed() only reflects local state;
remove the optimistic state gating and instead attempt the error response write
unconditionally inside the existing try/catch so any IOException will be handled
as before (or, at minimum, drop the isConnected() check and keep only isClosed()
if you must guard locally); update the comment to state that writes are
best-effort and rely on IOException handling in handleInternalServerError.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f33fb61 and bc4fb27.

📒 Files selected for processing (2)
  • src/main/java/org/example/TcpServer.java
  • src/test/java/org/example/TcpServerTest.java

@gitnes94 gitnes94 requested a review from AnnaZiafar February 26, 2026 12:09
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/main/java/org/example/TcpServer.java (1)

23-24: ⚠️ Potential issue | 🟠 Major

accept() can still block shutdown despite the interrupt check.

The loop checks interruption, but ServerSocket.accept() blocks and won’t notice thread interruption on its own. This can stall graceful shutdown.

🔧 Proposed fix (timeout-based interrupt polling)
 try (ServerSocket serverSocket = new ServerSocket(port)) {
+    serverSocket.setSoTimeout(1000);
     while (!Thread.currentThread().isInterrupted()) {
-        Socket clientSocket = serverSocket.accept();
+        Socket clientSocket;
+        try {
+            clientSocket = serverSocket.accept();
+        } catch (java.net.SocketTimeoutException ignored) {
+            continue;
+        }
 
         try {
             clientSocket.setSoTimeout(10000);
             Thread.ofVirtual().start(() -> handleClient(clientSocket));
-        } catch (Exception _) {
+        } catch (Exception ignored) {
             closeQuietly(clientSocket);
         }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/TcpServer.java` around lines 23 - 24, The accept()
call can block shutdown—modify TcpServer to set a accept timeout on serverSocket
(call serverSocket.setSoTimeout(...), e.g., 1000 ms) before entering the loop,
then wrap Socket clientSocket = serverSocket.accept() in a try/catch that
catches java.net.SocketTimeoutException and continues so the loop can re-check
Thread.currentThread().isInterrupted(); also keep existing IOException handling
for accept() and ensure any cleanup runs after the loop. This uses serverSocket,
accept(), and Thread.currentThread().isInterrupted() from the current code to
implement timeout-based interrupt polling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/example/TcpServer.java`:
- Around line 43-45: The catch blocks in TcpServer (the catch(Exception _) and
the other block around lines 56-60) are swallowing exceptions; update those
handlers to emit a structured log and/or metric and preserve visibility of the
failure (e.g., log with a logger.error or metrics.increment including the
exception message and stack trace, and if appropriate rethrow or propagate a
wrapped RuntimeException). Locate the catch(Exception _) in class TcpServer (and
the second catch block referenced) and replace the empty handler with a
structured logging call that includes context (method name/connection id) and
the caught exception, and ensure you either rethrow or signal the central error
handler after logging.

---

Duplicate comments:
In `@src/main/java/org/example/TcpServer.java`:
- Around line 23-24: The accept() call can block shutdown—modify TcpServer to
set a accept timeout on serverSocket (call serverSocket.setSoTimeout(...), e.g.,
1000 ms) before entering the loop, then wrap Socket clientSocket =
serverSocket.accept() in a try/catch that catches
java.net.SocketTimeoutException and continues so the loop can re-check
Thread.currentThread().isInterrupted(); also keep existing IOException handling
for accept() and ensure any cleanup runs after the loop. This uses serverSocket,
accept(), and Thread.currentThread().isInterrupted() from the current code to
implement timeout-based interrupt polling.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc4fb27 and c0bd68b.

📒 Files selected for processing (1)
  • src/main/java/org/example/TcpServer.java

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/example/TcpServer.java`:
- Around line 64-66: The current guard in TcpServer that uses client.isClosed()
|| !client.isConnected() can miss sockets that have been shut down for output
(remote closed) because isConnected() remains true; update the guard in the
TcpServer method containing that snippet to also check client.isOutputShutdown()
(e.g., return early if client.isClosed() || client.isOutputShutdown() ||
!client.isConnected()) so you detect sockets that are unusable for writing
before proceeding.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0bd68b and 309e204.

📒 Files selected for processing (1)
  • src/main/java/org/example/TcpServer.java

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/java/org/example/TcpServer.java (1)

25-25: Extract timeout literals to named constants.

Line [25] and Line [48] use raw timeout values. Naming these improves readability and prevents drift when tuning.

Suggested refactor
 public class TcpServer {
+    private static final int ACCEPT_TIMEOUT_MS = 1_000;
+    private static final int CLIENT_TIMEOUT_MS = 10_000;
@@
-            serverSocket.setSoTimeout(1000);
+            serverSocket.setSoTimeout(ACCEPT_TIMEOUT_MS);
@@
-            clientSocket.setSoTimeout(10000);
+            clientSocket.setSoTimeout(CLIENT_TIMEOUT_MS);

Also applies to: 48-48

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/TcpServer.java` at line 25, Replace the raw timeout
literals with named constants in the TcpServer class: define descriptive static
final int constants (e.g., SERVER_SOCKET_TIMEOUT_MS and CLIENT_READ_TIMEOUT_MS
or a single SOCKET_TIMEOUT_MS if both timeouts are the same) and use those
constants instead of the hard-coded 1000 values where
serverSocket.setSoTimeout(1000) and the other timeout at the referenced location
are called; update any related comments to reflect the constant names so future
tuning uses the constant rather than scattered magic numbers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/java/org/example/TcpServer.java`:
- Line 25: Replace the raw timeout literals with named constants in the
TcpServer class: define descriptive static final int constants (e.g.,
SERVER_SOCKET_TIMEOUT_MS and CLIENT_READ_TIMEOUT_MS or a single
SOCKET_TIMEOUT_MS if both timeouts are the same) and use those constants instead
of the hard-coded 1000 values where serverSocket.setSoTimeout(1000) and the
other timeout at the referenced location are called; update any related comments
to reflect the constant names so future tuning uses the constant rather than
scattered magic numbers.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 309e204 and 272bc8a.

📒 Files selected for processing (1)
  • src/main/java/org/example/TcpServer.java

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants