Skip to content
This repository was archived by the owner on Sep 28, 2025. It is now read-only.

Commit 446863f

Browse files
committed
Fixup: Trying to handle closing
1 parent 25f5256 commit 446863f

File tree

3 files changed

+281
-125
lines changed

3 files changed

+281
-125
lines changed

src/StreamMaster.Streams/Broadcasters/SourceBroadcaster.cs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -273,37 +273,63 @@ public async Task StopAsync()
273273
{
274274
await _stopLock.WaitAsync().ConfigureAwait(false);
275275
Task? taskToAwait = null;
276+
bool alreadyStopped = false;
276277
try
277278
{
278279
if (Interlocked.CompareExchange(ref _isStopped, 1, 0) == 0)
279280
{
280281
if (_cancellationTokenSource?.IsCancellationRequested != true)
281282
{
283+
logger.LogDebug("StopAsync: Cancelling CancellationTokenSource for {Name}", Name);
282284
_cancellationTokenSource?.Cancel();
283285
}
286+
else
287+
{
288+
logger.LogDebug("StopAsync: CancellationTokenSource already cancelled for {Name}", Name);
289+
}
284290
taskToAwait = _streamingTask;
291+
logger.LogDebug("StopAsync: Task to await is {Status} for {Name}", taskToAwait?.Status, Name);
292+
}
293+
else
294+
{
295+
logger.LogDebug("StopAsync: Already stopped for {Name}", Name);
296+
alreadyStopped = true;
285297
}
286298
}
287299
finally
288300
{
289301
_stopLock.Release();
290302
}
291303

292-
if (taskToAwait != null)
304+
if (taskToAwait != null && !alreadyStopped)
293305
{
306+
logger.LogInformation("StopAsync: Waiting for streaming task completion for {Name}", Name);
294307
try
295308
{
296309
await taskToAwait.ConfigureAwait(false);
310+
logger.LogInformation("StopAsync: Streaming task completed for {Name}", Name);
297311
}
298312
catch (OperationCanceledException)
299313
{
300-
logger.LogDebug("Task was already cancelled");
314+
logger.LogInformation("StopAsync: Streaming task cancelled as expected for {Name}", Name);
301315
}
302316
catch (Exception ex)
303317
{
304-
logger.LogError(ex, "Error during SourceBroadcaster streaming task completion wait.");
318+
logger.LogError(ex, "StopAsync: Error awaiting SourceBroadcaster streaming task completion for {Name}.", Name);
319+
}
320+
finally
321+
{
322+
logger.LogDebug("StopAsync: Finished awaiting task for {Name}", Name);
305323
}
306324
}
325+
else if (alreadyStopped)
326+
{
327+
logger.LogDebug("StopAsync: Skipping await as stop was already initiated for {Name}", Name);
328+
}
329+
else
330+
{
331+
logger.LogDebug("StopAsync: Skipping await as there was no task to await for {Name}", Name);
332+
}
307333
}
308334

309335
/// <inheritdoc/>

src/StreamMaster.Streams/Factories/CommandExecutor.cs

Lines changed: 72 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -143,122 +143,123 @@ private void CleanupFailedExecution(Process? process, StreamWriter? writer)
143143
}
144144
}
145145

146-
private static void GracefullyTerminateProcessInternal(Process? processToTerminate, ILogger log)
146+
private void GracefullyTerminateProcessInternal(Process? processToTerminate, ILogger log)
147147
{
148-
if (processToTerminate == null)
148+
if (processToTerminate == null) { log.LogDebug("Terminate: Process is null."); return; }
149+
150+
int processId = -1;
151+
try { processId = processToTerminate.Id; } catch { /* Handle case where Id access fails */ }
152+
153+
bool alreadyExited = false;
154+
try { alreadyExited = processToTerminate.HasExited; }
155+
catch (Exception ex) when (ex is InvalidOperationException || ex is System.ComponentModel.Win32Exception)
156+
{
157+
log.LogWarning(ex, "Terminate: Error checking HasExited for process {ProcessId}. Assuming exited.", processId);
158+
alreadyExited = true;
159+
}
160+
161+
if (alreadyExited)
149162
{
150-
log.LogDebug("GracefullyTerminateProcessInternal called with null process.");
163+
log.LogDebug("Terminate: Process {ProcessId} already exited.", processId);
151164
return;
152165
}
153166

167+
log.LogDebug("Attempting graceful termination for process {ProcessId}", processId);
168+
154169
try
155170
{
156-
bool alreadyExited = false;
157-
try
171+
if (processToTerminate.StartInfo.RedirectStandardInput)
158172
{
159-
alreadyExited = processToTerminate.HasExited;
160-
}
161-
catch (InvalidOperationException)
162-
{
163-
log.LogWarning("Error checking HasExited for process {ProcessId} (may already be disposed or inaccessible). Assuming exited.", processToTerminate.Id);
164-
alreadyExited = true;
173+
log.LogDebug("Terminate: Attempting to send 'q' to stdin for process {ProcessId}", processId);
174+
try
175+
{
176+
processToTerminate.StandardInput.WriteLine("q");
177+
processToTerminate.StandardInput.Close();
178+
179+
if (processToTerminate.WaitForExit(1000))
180+
{
181+
log.LogInformation("Process {ProcessId} terminated gracefully after sending 'q'.", processId);
182+
return;
183+
}
184+
log.LogDebug("Terminate: Process {ProcessId} did not exit after sending 'q' and 1s wait.", processId);
185+
}
186+
catch (InvalidOperationException ioex)
187+
{
188+
log.LogWarning(ioex, "Terminate: Error accessing StandardInput for process {ProcessId} (likely already closed/exited).", processId);
189+
if (processToTerminate.WaitForExit(50))
190+
{
191+
log.LogInformation("Process {ProcessId} terminated shortly after stdin error.", processId);
192+
return;
193+
}
194+
}
195+
catch (Exception ex)
196+
{
197+
log.LogError(ex, "Terminate: Error sending 'q' to process {ProcessId}", processId);
198+
}
165199
}
166-
catch (System.ComponentModel.Win32Exception ex)
200+
else
167201
{
168-
log.LogWarning(ex, "Error checking HasExited for process {ProcessId}. Assuming exited.", processToTerminate.Id);
169-
alreadyExited = true;
202+
log.LogWarning("Terminate: Cannot send 'q' to process {ProcessId}, StandardInput not redirected.", processId);
170203
}
171204

172-
if (alreadyExited)
205+
if (processToTerminate.HasExited)
173206
{
174-
log.LogDebug("GracefullyTerminateProcessInternal: Process {ProcessId} already exited.", processToTerminate.Id);
207+
log.LogDebug("Terminate: Process {ProcessId} exited before signal attempt.", processId);
175208
return;
176209
}
177210

178-
log.LogDebug("Attempting to gracefully terminate process {ProcessId}", processToTerminate.Id);
179-
180211
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
181212
{
182-
log.LogDebug("Waiting for process {ProcessId} to exit (Windows)...", processToTerminate.Id);
183-
if (!processToTerminate.WaitForExit(3000))
184-
{
185-
log.LogWarning("Process {ProcessId} did not terminate gracefully after wait, forcing kill", processToTerminate.Id);
186-
bool exitedBeforeKill = false;
187-
try { exitedBeforeKill = processToTerminate.HasExited; } catch { }
188-
if (!exitedBeforeKill)
189-
{
190-
processToTerminate.Kill(true);
191-
}
192-
}
193-
else
213+
log.LogDebug("Terminate: Waiting up to 3s for process {ProcessId} to exit (Windows)...", processId);
214+
if (processToTerminate.WaitForExit(3000))
194215
{
195-
log.LogDebug("Process {ProcessId} exited gracefully after wait (Windows).", processToTerminate.Id);
216+
log.LogInformation("Process {ProcessId} exited gracefully after wait (Windows).", processId);
217+
return;
196218
}
219+
log.LogWarning("Process {ProcessId} did not terminate after wait, forcing kill (Windows)", processId);
220+
if (!processToTerminate.HasExited) processToTerminate.Kill(true);
197221
}
198222
else
199223
{
200-
log.LogDebug("Sending SIGTERM to process {ProcessId}...", processToTerminate.Id);
224+
log.LogDebug("Terminate: Sending SIGTERM to process {ProcessId}...", processId);
201225
Process.Start("kill", $"-TERM {processToTerminate.Id}");
202-
if (!processToTerminate.WaitForExit(3000))
226+
if (processToTerminate.WaitForExit(3000))
203227
{
204-
log.LogWarning("Process {ProcessId} did not terminate after SIGTERM, sending SIGKILL", processToTerminate.Id);
205-
bool exitedBeforeKill = false;
206-
try { exitedBeforeKill = processToTerminate.HasExited; } catch { }
207-
if (!exitedBeforeKill)
208-
{
209-
log.LogDebug("Sending SIGKILL to process {ProcessId}...", processToTerminate.Id);
210-
Process.Start("kill", $"-KILL {processToTerminate.Id}");
211-
processToTerminate.WaitForExit(500);
212-
}
228+
log.LogInformation("Process {ProcessId} terminated after SIGTERM.", processId);
229+
return;
213230
}
214-
else
231+
log.LogWarning("Process {ProcessId} did not terminate after SIGTERM, sending SIGKILL", processId);
232+
if (!processToTerminate.HasExited)
215233
{
216-
log.LogDebug("Process {ProcessId} terminated after SIGTERM.", processToTerminate.Id);
234+
log.LogDebug("Terminate: Sending SIGKILL to process {ProcessId}...", processId);
235+
Process.Start("kill", $"-KILL {processToTerminate.Id}");
236+
processToTerminate.WaitForExit(500);
217237
}
218238
}
219239
}
220-
catch (InvalidOperationException ex)
221-
{
222-
log.LogWarning(ex, "Error terminating process {ProcessId}. It might have already exited.", processToTerminate.Id);
223-
}
224-
catch (System.ComponentModel.Win32Exception ex)
225-
{
226-
log.LogError(ex, "Win32Error during termination of process {ProcessId}", processToTerminate.Id);
227-
}
228240
catch (Exception ex)
229241
{
230-
log.LogError(ex, "Error gracefully terminating process {ProcessId}", processToTerminate.Id);
242+
log.LogError(ex, "Error during signal/kill phase for process {ProcessId}", processId);
231243
try
232244
{
233-
bool exitedBeforeKill = false;
234-
try { exitedBeforeKill = processToTerminate.HasExited; } catch { }
235-
if (!exitedBeforeKill)
245+
if (!processToTerminate.HasExited)
236246
{
237-
log.LogWarning("Forcing kill on process {ProcessId} due to prior termination errors.", processToTerminate.Id);
247+
log.LogWarning("Forcing final kill on process {ProcessId} due to termination errors.", processId);
238248
processToTerminate.Kill(true);
239249
}
240250
}
241-
catch (Exception killEx)
242-
{
243-
log.LogError(killEx, "Failed to force kill process {ProcessId}", processToTerminate.Id);
244-
}
251+
catch (Exception killEx) { log.LogError(killEx, "Failed final force kill for process {ProcessId}", processId); }
245252
}
246253
finally
247254
{
248255
try
249256
{
250257
bool finalExitCheck = false;
251258
try { finalExitCheck = processToTerminate.HasExited; } catch { }
252-
if (!finalExitCheck)
253-
{
254-
log.LogWarning("Process {ProcessId} termination logic completed, but HasExited is still false.", processToTerminate.Id);
255-
}
256-
else
257-
{
258-
log.LogDebug("Process {ProcessId} confirmed exited after termination logic.", processToTerminate.Id);
259-
}
259+
if (!finalExitCheck) { log.LogWarning("Process {ProcessId} termination logic completed, but HasExited is still false.", processId); }
260+
else { log.LogDebug("Process {ProcessId} confirmed exited after termination logic.", processId); }
260261
}
261-
catch { /* Ignore final state check errors */ }
262+
catch { }
262263
}
263264
}
264265

@@ -320,7 +321,6 @@ private void Process_Exited(Process? process, StreamWriter? writer)
320321

321322
private static string BuildCommand(string command, string clientUserAgent, string streamUrl, int? secondsIn)
322323
{
323-
// (Implementation as before)
324324
string s = secondsIn.HasValue ? $"-ss {secondsIn} " : "";
325325
command = command.Replace("{clientUserAgent}", '"' + clientUserAgent + '"')
326326
.Replace("{streamUrl}", '"' + streamUrl + '"');
@@ -343,6 +343,7 @@ private static void ConfigureProcess(Process process, string commandExec, string
343343
process.StartInfo.UseShellExecute = false;
344344
process.StartInfo.RedirectStandardOutput = true;
345345
process.StartInfo.RedirectStandardError = true;
346+
process.StartInfo.RedirectStandardInput = true;
346347
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
347348
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
348349
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

0 commit comments

Comments
 (0)