Skip to content

Commit 7d5de1a

Browse files
committed
refactor: Enhance input validation and interrupt handling in TimeoutCommand
1 parent e4b7a88 commit 7d5de1a

File tree

1 file changed

+48
-10
lines changed

1 file changed

+48
-10
lines changed

src/main/java/com/mycmd/commands/TimeoutCommand.java

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@
66
import java.util.concurrent.atomic.AtomicBoolean;
77

88
public class TimeoutCommand implements Command {
9+
/**
10+
* Execute the timeout command.
11+
*
12+
* <p>This command will wait for the specified number of seconds before continuing. If the user
13+
* presses Enter before the timeout expires, the command will terminate immediately.
14+
*
15+
* @param args The arguments to the command.
16+
* @param context The context of the shell.
17+
* @throws IOException If an I/O error occurs.
18+
*/
919
@Override
1020
public void execute(String[] args, ShellContext context) {
1121
int seconds = -1;
@@ -44,6 +54,9 @@ public void execute(String[] args, ShellContext context) {
4454
} else if (args[i].equalsIgnoreCase("/t")) {
4555
System.out.println("Error: Invalid syntax. Value expected for '/t'.");
4656
return;
57+
} else {
58+
System.out.println("Error: Invalid syntax. Unrecognized argument: " + args[i]);
59+
return;
4760
}
4861
}
4962

@@ -66,24 +79,35 @@ public void execute(String[] args, ShellContext context) {
6679
}
6780

6881
AtomicBoolean interrupted = new AtomicBoolean(false);
82+
AtomicBoolean stopInput = new AtomicBoolean(false);
6983
Thread inputThread = null;
7084

7185
if (!noBreak) {
7286
inputThread =
7387
new Thread(
7488
() -> {
7589
try {
76-
int r;
77-
// Read until a newline is encountered so we only exit on Enter
78-
while ((r = System.in.read()) != -1) {
79-
if (r == '\n') {
80-
interrupted.set(true);
81-
break;
90+
// Poll non-blocking so we can stop the thread
91+
// deterministically.
92+
while (!stopInput.get()) {
93+
if (System.in.available() > 0) {
94+
int r = System.in.read();
95+
// Treat CR or LF as Enter across platforms
96+
if (r == '\n' || r == '\r') {
97+
interrupted.set(true);
98+
break;
99+
}
100+
} else {
101+
try {
102+
Thread.sleep(25);
103+
} catch (InterruptedException ie) {
104+
Thread.currentThread().interrupt();
105+
break;
106+
}
82107
}
83108
}
84109
} catch (IOException e) {
85-
// Ignore: if System.in is closed or an I/O error occurs we
86-
// cannot reliably wait for Enter; treat as no-interrupt.
110+
// Best-effort only; fall through.
87111
}
88112
});
89113
inputThread.setDaemon(true);
@@ -95,8 +119,13 @@ public void execute(String[] args, ShellContext context) {
95119
if (!noBreak && interrupted.get()) {
96120
System.out.println("\r");
97121
System.out.println();
98-
if (inputThread != null && inputThread.isAlive()) {
99-
inputThread.interrupt();
122+
if (inputThread != null) {
123+
stopInput.set(true);
124+
try {
125+
inputThread.join(200);
126+
} catch (InterruptedException ie) {
127+
Thread.currentThread().interrupt();
128+
}
100129
}
101130
return;
102131
}
@@ -119,6 +148,15 @@ public void execute(String[] args, ShellContext context) {
119148
}
120149
}
121150

151+
// Normal completion: stop the input thread before draining.
152+
if (!noBreak && inputThread != null) {
153+
stopInput.set(true);
154+
try {
155+
inputThread.join(200);
156+
} catch (InterruptedException ie) {
157+
Thread.currentThread().interrupt();
158+
}
159+
}
122160
try {
123161
// Drain any remaining bytes so subsequent commands don't immediately see
124162
// leftover input. This is a best-effort drain; System.in.available() may

0 commit comments

Comments
 (0)