66import java .util .concurrent .atomic .AtomicBoolean ;
77
88public 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