@@ -63,10 +63,19 @@ RESULT_DIR="$RELAY_DIR/result"
6363
6464cleanup () {
6565 echo " [server] Shutting down..."
66+ # Kill any running command (guard all reads with || true to prevent set -e
67+ # from aborting the trap and leaving stale marker files)
68+ running_pid=$( cut -d: -f2 " $RELAY_DIR /running" 2> /dev/null) || true
69+ if [[ -n " $running_pid " ]]; then
70+ pkill -P " $running_pid " 2> /dev/null || true
71+ kill " $running_pid " 2> /dev/null || true
72+ fi
6673 # Kill any child processes in our process group
6774 pkill -P $$ 2> /dev/null || true
6875 rm -f " $RELAY_DIR /server.ready"
6976 rm -f " $RELAY_DIR /handshake.done"
77+ rm -f " $RELAY_DIR /running"
78+ rm -f " $RELAY_DIR /cancel"
7079 exit 0
7180}
7281trap cleanup SIGINT SIGTERM
@@ -140,20 +149,90 @@ while true; do
140149 fi
141150
142151 for cmd_file in " $CMD_DIR " /* .sh; do
152+ # Guard against command files deleted by the client between glob expansion
153+ # and processing (e.g., client timeout on a queued command)
154+ [[ -f " $cmd_file " ]] || continue
155+
143156 cmd_id=" $( basename " $cmd_file " .sh) "
144- cmd_content=$( cat " $cmd_file " )
157+ # Tolerate file disappearing between guard and read (TOCTOU with client timeout)
158+ cmd_content=$( cat " $cmd_file " 2> /dev/null) || continue
159+ # Remove command file immediately after reading to prevent re-execution
160+ # and to avoid TOCTOU with client timeout deleting it during execution
161+ rm -f " $cmd_file "
145162 echo " [server] Executing command $cmd_id : $cmd_content "
146163
147- # Execute the command, tee stdout+stderr to console and result file
148- (cd " $WORKDIR " && bash " $cmd_file " 2>&1 ) | tee " $RESULT_DIR /$cmd_id .log" || true
149- exit_code=${PIPESTATUS[0]}
150-
151- # Atomic write of exit code (signal to client that result is ready)
164+ # Clear any stale cancel file from a previous timed-out client
165+ rm -f " $RELAY_DIR /cancel"
166+
167+ # Create log file and stream output to server console via tail
168+ : > " $RESULT_DIR /$cmd_id .log"
169+ tail -f " $RESULT_DIR /$cmd_id .log" &
170+ tail_pid=$!
171+
172+ # Run from cmd_content (not the file) since we already removed it
173+ (cd " $WORKDIR " && bash -c " $cmd_content " ) >> " $RESULT_DIR /$cmd_id .log" 2>&1 &
174+ cmd_pid=$!
175+
176+ # Track the running command (ID and PID) — atomic write to prevent partial reads
177+ echo " $cmd_id :$cmd_pid " > " $RELAY_DIR /running.tmp"
178+ mv " $RELAY_DIR /running.tmp" " $RELAY_DIR /running"
179+
180+ # Wait for completion or cancellation
181+ cancelled=" "
182+ while kill -0 " $cmd_pid " 2> /dev/null; do
183+ if [[ -f " $RELAY_DIR /cancel" ]]; then
184+ # Verify cancel targets this command (reject empty or mismatched signals)
185+ cancel_target=$( cat " $RELAY_DIR /cancel" 2> /dev/null) || true
186+ if [[ " $cancel_target " != " $cmd_id " ]]; then
187+ rm -f " $RELAY_DIR /cancel"
188+ sleep " $POLL_INTERVAL "
189+ continue
190+ fi
191+ echo " [server] Cancelling command $cmd_id (PID $cmd_pid )..."
192+ # Send SIGTERM to children first, then parent
193+ pkill -P " $cmd_pid " 2> /dev/null || true
194+ kill " $cmd_pid " 2> /dev/null || true
195+ # Wait up to 5s for graceful exit, then escalate to SIGKILL
196+ for _ in $( seq 1 5) ; do
197+ kill -0 " $cmd_pid " 2> /dev/null || break
198+ sleep 1
199+ done
200+ if kill -0 " $cmd_pid " 2> /dev/null; then
201+ echo " [server] Process $cmd_pid did not exit, sending SIGKILL..."
202+ pkill -9 -P " $cmd_pid " 2> /dev/null || true
203+ kill -9 " $cmd_pid " 2> /dev/null || true
204+ fi
205+ wait " $cmd_pid " 2> /dev/null || true
206+ cancelled=" true"
207+ rm -f " $RELAY_DIR /cancel"
208+ echo " [cancelled]" >> " $RESULT_DIR /$cmd_id .log"
209+ echo " [server] Command $cmd_id cancelled."
210+ break
211+ fi
212+ sleep " $POLL_INTERVAL "
213+ done
214+
215+ # Determine exit code (|| exit_code=$? prevents set -e from killing the
216+ # server when the command exits non-zero)
217+ if [[ -n " $cancelled " ]]; then
218+ exit_code=130
219+ else
220+ exit_code=0
221+ wait " $cmd_pid " 2> /dev/null || exit_code=$?
222+ fi
223+
224+ # Stop console streaming
225+ kill " $tail_pid " 2> /dev/null || true
226+ wait " $tail_pid " 2> /dev/null || true
227+
228+ # Write exit code BEFORE removing the running marker, so any observer
229+ # that sees running disappear can immediately find the result
152230 echo " $exit_code " > " $RESULT_DIR /$cmd_id .exit.tmp"
153231 mv " $RESULT_DIR /$cmd_id .exit.tmp" " $RESULT_DIR /$cmd_id .exit"
154232
155- # Remove the command file to mark it as processed
156- rm -f " $cmd_file "
233+ # Now safe to remove markers
234+ rm -f " $RELAY_DIR /running"
235+ rm -f " $RELAY_DIR /cancel"
157236
158237 echo " [server] Command $cmd_id finished (exit=$exit_code )"
159238 done
0 commit comments