-
-
Notifications
You must be signed in to change notification settings - Fork 27.4k
fixing busy waiting in abstraction and eliminate busy-waiting #3302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
240e8a9
4ba85f1
df5d21a
c1a3999
c02dcea
f0fe72f
e9deb6e
447d5df
351cf60
093d086
9f845de
cc9578e
8c1dbc2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,12 +24,18 @@ | |
| */ | ||
| package com.iluwatar.sessionserver; | ||
|
|
||
|
|
||
| import java.util.HashMap; | ||
| import com.sun.net.httpserver.HttpServer; | ||
| import java.io.IOException; | ||
| import java.net.InetSocketAddress; | ||
| import java.time.Instant; | ||
| import java.util.HashMap; | ||
| import java.util.Iterator; | ||
| import java.time.Instant; | ||
| import java.util.concurrent.ConcurrentHashMap; | ||
| import java.util.concurrent.Executors; | ||
| import java.util.concurrent.ScheduledExecutorService; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.CountDownLatch; | ||
| import java.util.Map; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
|
|
@@ -57,6 +63,12 @@ public class App { | |
| private static Map<String, Instant> sessionCreationTimes = new HashMap<>(); | ||
|
Comment on lines
58
to
60
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Bug: The sessions and sessionCreationTimes maps are initialized as java.util.HashMap. The Impact: These maps are accessed concurrently by HTTP threads (via handlers) and the background expiration task. HashMap is not thread-safe. This will cause ConcurrentModificationException or silent data corruption. The code comments incorrectly state that ConcurrentHashMap handles safety, but the implementation still uses HashMap. |
||
| private static final long SESSION_EXPIRATION_TIME = 10000; | ||
|
|
||
| // Scheduler for session expiration task | ||
| private static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); | ||
| private static volatile boolean running = true; | ||
| private static final CountDownLatch shutdownLatch = new CountDownLatch(1); | ||
|
|
||
|
|
||
| /** | ||
| * Main entry point. | ||
| * | ||
|
|
@@ -78,39 +90,61 @@ public static void main(String[] args) throws IOException { | |
| sessionExpirationTask(); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Bug: The sessionExpirationTask() is called exactly once in main but is never scheduled to repeat. The Impact: The expiration logic will run once at startup and then never again. The ScheduledExecutorService defined at Line 64 is initialized but never actually used to schedule the task. |
||
|
|
||
| LOGGER.info("Server started. Listening on port 8080..."); | ||
| // Wait for shutdown signal | ||
| try { | ||
| shutdownLatch.await(); | ||
| } catch (InterruptedException e) { | ||
| LOGGER.error("Main thread interrupted", e); | ||
| Thread.currentThread().interrupt(); | ||
| } | ||
| } | ||
|
|
||
| private static void sessionExpirationTask() { | ||
| new Thread( | ||
| () -> { | ||
| while (true) { | ||
| try { | ||
| LOGGER.info("Session expiration checker started..."); | ||
| Thread.sleep(SESSION_EXPIRATION_TIME); // Sleep for expiration time | ||
| Instant currentTime = Instant.now(); | ||
| synchronized (sessions) { | ||
| synchronized (sessionCreationTimes) { | ||
| Iterator<Map.Entry<String, Instant>> iterator = | ||
| sessionCreationTimes.entrySet().iterator(); | ||
| while (iterator.hasNext()) { | ||
| Map.Entry<String, Instant> entry = iterator.next(); | ||
| if (entry | ||
| .getValue() | ||
| .plusMillis(SESSION_EXPIRATION_TIME) | ||
| .isBefore(currentTime)) { | ||
| sessions.remove(entry.getKey()); | ||
| iterator.remove(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| LOGGER.info("Session expiration checker finished!"); | ||
| } catch (InterruptedException e) { | ||
| LOGGER.error("An error occurred: ", e); | ||
| Thread.currentThread().interrupt(); | ||
| } | ||
| } | ||
| }) | ||
| .start(); | ||
| if (!running) { | ||
| return; | ||
| } | ||
| try { | ||
| LOGGER.info("Session expiration checker started..."); | ||
| Instant currentTime = Instant.now(); | ||
|
|
||
| // Use removeIf for efficient removal without explicit synchronization | ||
| // ConcurrentHashMap handles thread safety internally | ||
| sessionCreationTimes.entrySet().removeIf(entry -> { | ||
| if (entry.getValue().plusMillis(SESSION_EXPIRATION_TIME).isBefore(currentTime)) { | ||
| sessions.remove(entry.getKey()); | ||
| LOGGER.debug("Expired session: {}", entry.getKey()); | ||
| return true; | ||
| } | ||
| return false; | ||
| }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In |
||
|
|
||
| LOGGER.info("Session expiration checker finished! Active sessions: {}", sessions.size()); | ||
| } catch (Exception e) { | ||
| LOGGER.error("An error occurred during session expiration check: ", e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Gracefully shuts down the session expiration scheduler. | ||
| * This method is called by the shutdown hook. | ||
| */ | ||
| private static void shutdown() { | ||
| LOGGER.info("Shutting down session expiration scheduler..."); | ||
| running = false; | ||
| scheduler.shutdown(); | ||
|
|
||
| try { | ||
| if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { | ||
| LOGGER.warn("Scheduler did not terminate gracefully, forcing shutdown"); | ||
| scheduler.shutdownNow(); | ||
| } | ||
| } catch (InterruptedException e) { | ||
| LOGGER.warn("Shutdown interrupted, forcing immediate shutdown"); | ||
| scheduler.shutdownNow(); | ||
| Thread.currentThread().interrupt(); | ||
| } | ||
|
|
||
| shutdownLatch.countDown(); | ||
| LOGGER.info("Session expiration scheduler shut down complete"); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Bug: Logs are removed from the buffer using buffer.poll() (Line 171) before being sent to the centralLogStore.
The Impact: If centralLogStore.storeLog(entry) throws an exception (e.g., network failure), the current batch of logs is lost because they have already been removed from the queue. Additionally, the logCount will fall out of sync because it is only decremented after a successful store.