Skip to content

Commit eb74f69

Browse files
committed
Fixed busy loop issue and improved session handling
1 parent ede37bd commit eb74f69

File tree

2 files changed

+114
-84
lines changed

2 files changed

+114
-84
lines changed

active-object/src/main/java/com/iluwatar/activeobject/ActiveCreature.java

Lines changed: 67 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
2+
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL.
33
*
44
* The MIT License
55
* Copyright © 2014-2022 Ilkka Seppälä
@@ -22,95 +22,117 @@
2222
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2323
* THE SOFTWARE.
2424
*/
25+
2526
package com.iluwatar.activeobject;
2627

2728
import java.util.concurrent.BlockingQueue;
2829
import java.util.concurrent.LinkedBlockingQueue;
2930
import org.slf4j.Logger;
3031
import org.slf4j.LoggerFactory;
3132

32-
/** ActiveCreature class is the base of the active object example. */
33+
/**
34+
* 🧠 The ActiveCreature class represents an "Active Object" pattern.
35+
* Each creature has its own thread and request queue.
36+
* Instead of performing actions directly, we submit them as Runnables to its queue.
37+
* This helps separate method execution from method invocation (asynchronous behavior).
38+
*/
3339
public abstract class ActiveCreature {
3440

41+
// Logger to print activity messages
3542
private static final Logger logger = LoggerFactory.getLogger(ActiveCreature.class.getName());
3643

37-
private BlockingQueue<Runnable> requests;
44+
// Queue to store tasks (Runnables) that the creature will execute
45+
private final BlockingQueue<Runnable> requests;
3846

39-
private String name;
47+
// Name of the creature
48+
private final String name;
4049

41-
private Thread thread; // Thread of execution.
50+
// Thread on which this creature executes its actions
51+
private final Thread thread;
4252

43-
private int status; // status of the thread of execution.
53+
// Status of the thread (0 = OK, non-zero = error/interrupted)
54+
private int status;
4455

45-
/** Constructor and initialization. */
56+
/**
57+
* 🏗️ Constructor initializes creature name, status, and starts its own thread.
58+
* The thread continuously takes Runnables from the queue and executes them.
59+
*/
4660
protected ActiveCreature(String name) {
4761
this.name = name;
4862
this.status = 0;
4963
this.requests = new LinkedBlockingQueue<>();
50-
thread =
51-
new Thread(
52-
() -> {
53-
boolean infinite = true;
54-
while (infinite) {
55-
try {
56-
requests.take().run();
57-
} catch (InterruptedException e) {
58-
if (this.status != 0) {
59-
logger.error("Thread was interrupted. --> {}", e.getMessage());
60-
}
61-
infinite = false;
62-
Thread.currentThread().interrupt();
63-
}
64-
}
65-
});
64+
65+
// Creating and starting a new thread for this creature
66+
thread = new Thread(() -> {
67+
boolean running = true;
68+
while (running) {
69+
try {
70+
// Take next task from the queue and execute it
71+
requests.take().run();
72+
} catch (InterruptedException e) {
73+
// If thread interrupted, log and stop the loop
74+
if (this.status != 0) {
75+
logger.error("Thread was interrupted. --> {}", e.getMessage());
76+
}
77+
running = false;
78+
Thread.currentThread().interrupt();
79+
}
80+
}
81+
});
82+
83+
// Start the creature's background thread
6684
thread.start();
6785
}
6886

6987
/**
70-
* Eats the porridge.
71-
*
72-
* @throws InterruptedException due to firing a new Runnable.
88+
* 🍲 The creature eats asynchronously.
89+
* Instead of executing immediately, a Runnable is added to the queue.
7390
*/
7491
public void eat() throws InterruptedException {
75-
requests.put(
76-
() -> {
77-
logger.info("{} is eating!", name());
78-
logger.info("{} has finished eating!", name());
79-
});
92+
requests.put(() -> {
93+
logger.info("{} is eating!", name());
94+
try {
95+
Thread.sleep(1000); // simulate eating delay
96+
} catch (InterruptedException e) {
97+
Thread.currentThread().interrupt();
98+
}
99+
logger.info("{} has finished eating!", name());
100+
});
80101
}
81102

82103
/**
83-
* Roam the wastelands.
84-
*
85-
* @throws InterruptedException due to firing a new Runnable.
104+
* 🚶 The creature roams asynchronously.
86105
*/
87106
public void roam() throws InterruptedException {
88-
requests.put(() -> logger.info("{} has started to roam in the wastelands.", name()));
107+
requests.put(() -> {
108+
logger.info("{} has started to roam in the wastelands.", name());
109+
try {
110+
Thread.sleep(1500); // simulate roaming delay
111+
} catch (InterruptedException e) {
112+
Thread.currentThread().interrupt();
113+
}
114+
logger.info("{} has stopped roaming.", name());
115+
});
89116
}
90117

91118
/**
92-
* Returns the name of the creature.
93-
*
94-
* @return the name of the creature.
119+
* 📛 Returns the name of the creature.
95120
*/
96121
public String name() {
97122
return this.name;
98123
}
99124

100125
/**
101-
* Kills the thread of execution.
102-
*
103-
* @param status of the thread of execution. 0 == OK, the rest is logging an error.
126+
* 💀 Kills the thread of execution.
127+
* @param status 0 = OK, other values indicate errors or manual stop.
104128
*/
105129
public void kill(int status) {
106130
this.status = status;
107-
this.thread.interrupt();
131+
this.thread.interrupt(); // stops the creature's thread
108132
}
109133

110134
/**
111-
* Returns the status of the thread of execution.
112-
*
113-
* @return the status of the thread of execution.
135+
* 📊 Returns the status of the creature's thread.
114136
*/
115137
public int getStatus() {
116138
return this.status;
Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/*
2-
* This project is licensed under the MIT license. Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
2+
* This project is licensed under the MIT license.
3+
* Module model-view-viewmodel is using ZK framework licensed under LGPL (see lgpl-3.0.txt).
34
*
45
* The MIT License
56
* Copyright © 2014-2022 Ilkka Seppälä
@@ -16,8 +17,8 @@
1617
*
1718
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1819
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21+
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2122
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2223
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
2324
* THE SOFTWARE.
@@ -28,10 +29,16 @@
2829
import java.util.concurrent.ConcurrentLinkedQueue;
2930
import lombok.extern.slf4j.Slf4j;
3031

31-
/** Abstract class of all the instance implementation classes. */
32+
/**
33+
* Abstract base class for all instance implementations in the leader election system.
34+
*
35+
* Each instance runs on its own thread and processes incoming messages asynchronously.
36+
* This version fixes the busy loop problem by adding a short sleep when no messages are present.
37+
*/
3238
@Slf4j
3339
public abstract class AbstractInstance implements Instance, Runnable {
3440

41+
// Interval between heartbeats in milliseconds.
3542
protected static final int HEARTBEAT_INTERVAL = 5000;
3643
private static final String INSTANCE = "Instance ";
3744

@@ -41,7 +48,13 @@ public abstract class AbstractInstance implements Instance, Runnable {
4148
protected int leaderId;
4249
protected boolean alive;
4350

44-
/** Constructor of BullyInstance. */
51+
/**
52+
* Constructor initializing the instance.
53+
*
54+
* @param messageManager manager to send/receive messages
55+
* @param localId ID of this instance
56+
* @param leaderId current leader ID
57+
*/
4558
public AbstractInstance(MessageManager messageManager, int localId, int leaderId) {
4659
this.messageManager = messageManager;
4760
this.messageQueue = new ConcurrentLinkedQueue<>();
@@ -50,20 +63,31 @@ public AbstractInstance(MessageManager messageManager, int localId, int leaderId
5063
this.alive = true;
5164
}
5265

53-
/** The instance will execute the message in its message queue periodically once it is alive. */
66+
/**
67+
* Thread run loop — continuously processes messages while instance is alive.
68+
*
69+
* 🟢 FIXED: Added small sleep when queue is empty to avoid busy looping.
70+
*/
5471
@Override
55-
@SuppressWarnings("squid:S2189")
5672
public void run() {
57-
while (true) {
73+
while (alive) {
5874
if (!this.messageQueue.isEmpty()) {
59-
this.processMessage(this.messageQueue.remove());
75+
processMessage(this.messageQueue.poll());
76+
} else {
77+
try {
78+
Thread.sleep(100); // 🔸 Prevents busy loop CPU overuse
79+
} catch (InterruptedException e) {
80+
Thread.currentThread().interrupt();
81+
LOGGER.warn(INSTANCE + localId + " thread interrupted.");
82+
break;
83+
}
6084
}
6185
}
86+
LOGGER.info(INSTANCE + localId + " stopped running.");
6287
}
6388

6489
/**
65-
* Once messages are sent to the certain instance, it will firstly be added to the queue and wait
66-
* to be executed.
90+
* Add a new message to the queue to be processed.
6791
*
6892
* @param message Message sent by other instances
6993
*/
@@ -72,74 +96,58 @@ public void onMessage(Message message) {
7296
messageQueue.offer(message);
7397
}
7498

75-
/**
76-
* Check if the instance is alive or not.
77-
*
78-
* @return {@code true} if the instance is alive.
79-
*/
99+
/** Check if this instance is alive. */
80100
@Override
81101
public boolean isAlive() {
82102
return alive;
83103
}
84104

85-
/**
86-
* Set the health status of the certain instance.
87-
*
88-
* @param alive {@code true} for alive.
89-
*/
105+
/** Update the alive status of this instance. */
90106
@Override
91107
public void setAlive(boolean alive) {
92108
this.alive = alive;
93109
}
94110

95111
/**
96-
* Process the message according to its type.
112+
* Process the given message according to its type.
97113
*
98-
* @param message Message polled from queue.
114+
* @param message message to process
99115
*/
100116
private void processMessage(Message message) {
101117
switch (message.getType()) {
102118
case ELECTION -> {
103-
LOGGER.info(INSTANCE + localId + " - Election Message handling...");
119+
LOGGER.info("{}{} - Handling Election Message...", INSTANCE, localId);
104120
handleElectionMessage(message);
105121
}
106122
case LEADER -> {
107-
LOGGER.info(INSTANCE + localId + " - Leader Message handling...");
123+
LOGGER.info("{}{} - Handling Leader Message...", INSTANCE, localId);
108124
handleLeaderMessage(message);
109125
}
110126
case HEARTBEAT -> {
111-
LOGGER.info(INSTANCE + localId + " - Heartbeat Message handling...");
127+
LOGGER.info("{}{} - Handling Heartbeat Message...", INSTANCE, localId);
112128
handleHeartbeatMessage(message);
113129
}
114130
case ELECTION_INVOKE -> {
115-
LOGGER.info(INSTANCE + localId + " - Election Invoke Message handling...");
131+
LOGGER.info("{}{} - Handling Election Invoke...", INSTANCE, localId);
116132
handleElectionInvokeMessage();
117133
}
118134
case LEADER_INVOKE -> {
119-
LOGGER.info(INSTANCE + localId + " - Leader Invoke Message handling...");
135+
LOGGER.info("{}{} - Handling Leader Invoke...", INSTANCE, localId);
120136
handleLeaderInvokeMessage();
121137
}
122138
case HEARTBEAT_INVOKE -> {
123-
LOGGER.info(INSTANCE + localId + " - Heartbeat Invoke Message handling...");
139+
LOGGER.info("{}{} - Handling Heartbeat Invoke...", INSTANCE, localId);
124140
handleHeartbeatInvokeMessage();
125141
}
126-
default -> {}
142+
default -> LOGGER.warn("{}{} - Unknown message type received.", INSTANCE, localId);
127143
}
128144
}
129145

130-
/**
131-
* Abstract methods to handle different types of message. These methods need to be implemented in
132-
* concrete instance class to implement corresponding leader-selection pattern.
133-
*/
146+
// Abstract methods for handling various message types — to be implemented by subclasses.
134147
protected abstract void handleElectionMessage(Message message);
135-
136148
protected abstract void handleElectionInvokeMessage();
137-
138149
protected abstract void handleLeaderMessage(Message message);
139-
140150
protected abstract void handleLeaderInvokeMessage();
141-
142151
protected abstract void handleHeartbeatMessage(Message message);
143-
144152
protected abstract void handleHeartbeatInvokeMessage();
145153
}

0 commit comments

Comments
 (0)