Skip to content

Commit ecfa1f1

Browse files
committed
Better
1 parent d7ede34 commit ecfa1f1

File tree

4 files changed

+147
-48
lines changed

4 files changed

+147
-48
lines changed

dotnet/src/webdriver/DriverService.cs

Lines changed: 140 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
using System.Net.Http;
2828
using System.Threading.Tasks;
2929
using OpenQA.Selenium.Internal.Logging;
30+
using System.Threading;
31+
using OpenQA.Selenium.Internal;
3032

3133
namespace OpenQA.Selenium;
3234

@@ -170,40 +172,47 @@ public int ProcessId
170172
/// </summary>
171173
protected virtual bool HasShutdown => true;
172174

175+
static TaskFactory _tf = new TaskFactory();
176+
173177
/// <summary>
174178
/// Gets a value indicating whether the service is responding to HTTP requests.
175179
/// </summary>
176-
protected virtual bool IsInitialized
180+
protected virtual async Task<bool> IsInitialized()
177181
{
178-
get
179-
{
180-
bool isInitialized = false;
182+
bool isInitialized = false;
181183

182-
try
184+
try
185+
{
186+
using (var httpClient = new HttpClient())
183187
{
184-
using (var httpClient = new HttpClient())
188+
//httpClient.DefaultRequestHeaders.ConnectionClose = true;
189+
//httpClient.Timeout = TimeSpan.FromSeconds(2);
190+
191+
Uri serviceHealthUri = new Uri(this.ServiceUrl, new Uri(DriverCommand.Status, UriKind.Relative));
192+
193+
_logger.Debug($"Probing HTTP: {serviceHealthUri}");
194+
195+
_logger.Debug("Sending GET");
196+
197+
using (var response = await httpClient.GetAsync(serviceHealthUri).ConfigureAwait(false))
185198
{
186-
httpClient.DefaultRequestHeaders.ConnectionClose = true;
187-
httpClient.Timeout = TimeSpan.FromSeconds(5);
199+
// Checking the response from the 'status' end point. Note that we are simply checking
200+
// that the HTTP status returned is a 200 status, and that the response has the correct
201+
// Content-Type header. A more sophisticated check would parse the JSON response and
202+
// validate its values. At the moment we do not do this more sophisticated check.
203+
isInitialized = response.StatusCode == HttpStatusCode.OK && response.Content.Headers.ContentType is { MediaType: string mediaType } && mediaType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
188204

189-
Uri serviceHealthUri = new Uri(this.ServiceUrl, new Uri(DriverCommand.Status, UriKind.Relative));
190-
using (var response = Task.Run(async () => await httpClient.GetAsync(serviceHealthUri)).GetAwaiter().GetResult())
191-
{
192-
// Checking the response from the 'status' end point. Note that we are simply checking
193-
// that the HTTP status returned is a 200 status, and that the response has the correct
194-
// Content-Type header. A more sophisticated check would parse the JSON response and
195-
// validate its values. At the moment we do not do this more sophisticated check.
196-
isInitialized = response.StatusCode == HttpStatusCode.OK && response.Content.Headers.ContentType is { MediaType: string mediaType } && mediaType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase);
197-
}
205+
_logger.Debug($"Probed HTTP: {isInitialized}");
198206
}
199207
}
200-
catch (Exception ex) when (ex is HttpRequestException || ex is TaskCanceledException)
201-
{
202-
// Do nothing. The exception is expected, meaning driver service is not initialized.
203-
}
204-
205-
return isInitialized;
206208
}
209+
catch (Exception ex) when (ex is HttpRequestException || ex is TaskCanceledException)
210+
{
211+
_logger.Warn($"Probing failed: {ex}");
212+
// Do nothing. The exception is expected, meaning driver service is not initialized.
213+
}
214+
215+
return isInitialized;
207216
}
208217

209218
/// <summary>
@@ -215,11 +224,15 @@ public void Dispose()
215224
GC.SuppressFinalize(this);
216225
}
217226

227+
// Replaced Task-based readers with dedicated threads
228+
private Thread? _outputThread;
229+
private Thread? _errorThread;
230+
218231
/// <summary>
219232
/// Starts the DriverService if it is not already running.
220233
/// </summary>
221234
[MemberNotNull(nameof(driverServiceProcess))]
222-
public void Start()
235+
public async Task Start()
223236
{
224237
if (this.driverServiceProcess != null)
225238
{
@@ -249,8 +262,8 @@ public void Start()
249262
this.driverServiceProcess.StartInfo.RedirectStandardOutput = true;
250263
this.driverServiceProcess.StartInfo.RedirectStandardError = true;
251264

252-
this.driverServiceProcess.OutputDataReceived += this.OnDriverProcessDataReceived;
253-
this.driverServiceProcess.ErrorDataReceived += this.OnDriverProcessDataReceived;
265+
//this.driverServiceProcess.OutputDataReceived += this.OnDriverProcessDataReceived;
266+
//this.driverServiceProcess.ErrorDataReceived += this.OnDriverProcessDataReceived;
254267

255268
DriverProcessStartingEventArgs eventArgs = new DriverProcessStartingEventArgs(this.driverServiceProcess.StartInfo);
256269
this.OnDriverProcessStarting(eventArgs);
@@ -262,10 +275,83 @@ public void Start()
262275

263276
// Important: Start the process and immediately begin reading the output and error streams to avoid IO deadlocks.
264277
this.driverServiceProcess.Start();
265-
this.driverServiceProcess.BeginOutputReadLine();
266-
this.driverServiceProcess.BeginErrorReadLine();
278+
//this.driverServiceProcess.BeginOutputReadLine();
279+
//this.driverServiceProcess.BeginErrorReadLine();
267280

268-
bool serviceAvailable = this.WaitForServiceInitialization();
281+
if (_logger.IsEnabled(LogEventLevel.Trace))
282+
{
283+
// Capture a stable reference to the process to avoid races with Stop() nulling the field
284+
var proc = this.driverServiceProcess;
285+
286+
_outputThread = new Thread(() =>
287+
{
288+
try
289+
{
290+
if (proc == null)
291+
{
292+
return;
293+
}
294+
295+
using (var reader = proc.StandardOutput)
296+
{
297+
string? line;
298+
while ((line = reader.ReadLine()) != null)
299+
{
300+
OnDriverProcessDataReceived(line);
301+
}
302+
}
303+
}
304+
catch (ObjectDisposedException)
305+
{
306+
// Process or stream disposed during shutdown; ignore
307+
}
308+
catch (IOException)
309+
{
310+
// Stream closed; ignore
311+
}
312+
})
313+
{
314+
IsBackground = true,
315+
Name = "DriverService-stdout"
316+
};
317+
318+
_errorThread = new Thread(() =>
319+
{
320+
try
321+
{
322+
if (proc == null)
323+
{
324+
return;
325+
}
326+
327+
using (var reader = proc.StandardError)
328+
{
329+
string? line;
330+
while ((line = reader.ReadLine()) != null)
331+
{
332+
OnDriverProcessDataReceived(line);
333+
}
334+
}
335+
}
336+
catch (ObjectDisposedException)
337+
{
338+
// Process or stream disposed during shutdown; ignore
339+
}
340+
catch (IOException)
341+
{
342+
// Stream closed; ignore
343+
}
344+
})
345+
{
346+
IsBackground = true,
347+
Name = "DriverService-stderr"
348+
};
349+
350+
_outputThread.Start();
351+
_errorThread.Start();
352+
}
353+
354+
bool serviceAvailable = await this.WaitForServiceInitialization().ConfigureAwait(false);
269355

270356
DriverProcessStartedEventArgs processStartedEventArgs = new DriverProcessStartedEventArgs(this.driverServiceProcess);
271357
this.OnDriverProcessStarted(processStartedEventArgs);
@@ -301,8 +387,8 @@ protected virtual void Dispose(bool disposing)
301387

302388
if (this.driverServiceProcess is not null)
303389
{
304-
this.driverServiceProcess.OutputDataReceived -= this.OnDriverProcessDataReceived;
305-
this.driverServiceProcess.ErrorDataReceived -= this.OnDriverProcessDataReceived;
390+
//this.driverServiceProcess.OutputDataReceived -= this.OnDriverProcessDataReceived;
391+
//this.driverServiceProcess.ErrorDataReceived -= this.OnDriverProcessDataReceived;
306392
}
307393
}
308394

@@ -343,14 +429,14 @@ protected virtual void OnDriverProcessStarted(DriverProcessStartedEventArgs even
343429
/// </summary>
344430
/// <param name="sender">The sender of the event.</param>
345431
/// <param name="args">The data received event arguments.</param>
346-
protected virtual void OnDriverProcessDataReceived(object sender, DataReceivedEventArgs args)
432+
protected virtual void OnDriverProcessDataReceived(string? data)
347433
{
348-
if (string.IsNullOrEmpty(args.Data))
434+
if (string.IsNullOrEmpty(data))
349435
return;
350436

351437
if (_logger.IsEnabled(LogEventLevel.Trace))
352438
{
353-
_logger.Trace(args.Data);
439+
_logger.Trace(data);
354440
}
355441
}
356442

@@ -378,7 +464,7 @@ private void Stop()
378464
// we'll retry. We wait for exit here, since catching the exception
379465
// for a failed HTTP request due to a closed socket is particularly
380466
// expensive.
381-
using (var response = Task.Run(async () => await httpClient.GetAsync(shutdownUrl)).GetAwaiter().GetResult())
467+
using (var response = Task.Run(async () => await httpClient.GetAsync(shutdownUrl).ConfigureAwait(false)).GetAwaiter().GetResult())
382468
{
383469

384470
}
@@ -404,8 +490,17 @@ private void Stop()
404490
}
405491
}
406492

493+
// Wait for output/error reader threads to finish to avoid IO deadlocks
494+
_outputThread?.Join();
495+
_errorThread?.Join();
496+
407497
this.driverServiceProcess.Dispose();
408498
this.driverServiceProcess = null;
499+
500+
if (_logger.IsEnabled(LogEventLevel.Debug))
501+
{
502+
_logger.Debug("Driver service is stopped");
503+
}
409504
}
410505
}
411506

@@ -415,29 +510,32 @@ private void Stop()
415510
/// </summary>
416511
/// <returns><see langword="true"/> if the service is properly started and receiving HTTP requests;
417512
/// otherwise; <see langword="false"/>.</returns>
418-
private bool WaitForServiceInitialization()
513+
private async Task<bool> WaitForServiceInitialization()
419514
{
420515
if (_logger.IsEnabled(LogEventLevel.Debug))
421516
{
422517
_logger.Debug("Waiting until driver service is initialized");
423518
}
424519

520+
var sw = Stopwatch.StartNew();
521+
425522
bool isInitialized = false;
426523
DateTime timeout = DateTime.Now.Add(this.InitializationTimeout);
427524
while (!isInitialized && DateTime.Now < timeout)
428525
{
526+
_logger.Debug("LOOP");
429527
// If the driver service process has exited, we can exit early.
430-
if (!this.IsRunning)
431-
{
432-
break;
433-
}
528+
//if (!this.IsRunning)
529+
//{
530+
// break;
531+
//}
434532

435-
isInitialized = this.IsInitialized;
533+
isInitialized = await this.IsInitialized().ConfigureAwait(false);
436534
}
437535

438536
if (_logger.IsEnabled(LogEventLevel.Debug))
439537
{
440-
_logger.Debug($"Driver service initialization status: {isInitialized}");
538+
_logger.Debug($"Driver service initialization status: {isInitialized} {sw.Elapsed}");
441539
}
442540

443541
return isInitialized;

dotnet/src/webdriver/Firefox/FirefoxDriverService.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,21 +250,21 @@ protected override void OnDriverProcessStarting(DriverProcessStartingEventArgs e
250250
/// </summary>
251251
/// <param name="sender">The sender of the event.</param>
252252
/// <param name="args">The data received event arguments.</param>
253-
protected override void OnDriverProcessDataReceived(object sender, DataReceivedEventArgs args)
253+
protected override void OnDriverProcessDataReceived(string? data)
254254
{
255-
if (string.IsNullOrEmpty(args.Data))
255+
if (string.IsNullOrEmpty(data))
256256
return;
257257

258258
if (!string.IsNullOrEmpty(this.LogPath))
259259
{
260260
if (logWriter != null)
261261
{
262-
logWriter.WriteLine(args.Data);
262+
logWriter.WriteLine(data);
263263
}
264264
}
265265
else
266266
{
267-
base.OnDriverProcessDataReceived(sender, args);
267+
base.OnDriverProcessDataReceived(data);
268268
}
269269
}
270270

dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public async Task<Response> ExecuteAsync(Command commandToExecute)
115115
Response toReturn;
116116
if (commandToExecute.Name == DriverCommand.NewSession)
117117
{
118-
this.service.Start();
118+
await this.service.Start().ConfigureAwait(false);
119119
}
120120

121121
// Use a try-catch block to catch exceptions for the Quit

dotnet/src/webdriver/Remote/ICommandServer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
// </copyright>
1919

2020
using System;
21+
using System.Threading.Tasks;
2122

2223
namespace OpenQA.Selenium.Remote;
2324

@@ -29,5 +30,5 @@ public interface ICommandServer : IDisposable
2930
/// <summary>
3031
/// Starts the server.
3132
/// </summary>
32-
void Start();
33+
Task Start();
3334
}

0 commit comments

Comments
 (0)