diff --git a/dotnet/src/webdriver/DriverService.cs b/dotnet/src/webdriver/DriverService.cs index a8907cf976769..9aee2990b9f2f 100644 --- a/dotnet/src/webdriver/DriverService.cs +++ b/dotnet/src/webdriver/DriverService.cs @@ -176,33 +176,41 @@ protected virtual bool IsInitialized { get { - bool isInitialized = false; + return Task.Run(this.IsInitializedAsync).GetAwaiter().GetResult(); + } + } - try + /// + /// Gets a value indicating whether the service is responding to HTTP requests. + /// + /// A task that represents the asynchronous initialization check operation. + protected async virtual Task IsInitializedAsync() + { + bool isInitialized = false; + try + { + using (var httpClient = new HttpClient()) { - using (var httpClient = new HttpClient()) - { - httpClient.DefaultRequestHeaders.ConnectionClose = true; - httpClient.Timeout = TimeSpan.FromSeconds(5); + httpClient.DefaultRequestHeaders.ConnectionClose = true; + httpClient.Timeout = TimeSpan.FromSeconds(5); - Uri serviceHealthUri = new Uri(this.ServiceUrl, new Uri(DriverCommand.Status, UriKind.Relative)); - using (var response = Task.Run(async () => await httpClient.GetAsync(serviceHealthUri)).GetAwaiter().GetResult()) - { - // Checking the response from the 'status' end point. Note that we are simply checking - // that the HTTP status returned is a 200 status, and that the resposne has the correct - // Content-Type header. A more sophisticated check would parse the JSON response and - // validate its values. At the moment we do not do this more sophisticated check. - isInitialized = response.StatusCode == HttpStatusCode.OK && response.Content.Headers.ContentType is { MediaType: string mediaType } && mediaType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase); - } + Uri serviceHealthUri = new Uri(this.ServiceUrl, new Uri(DriverCommand.Status, UriKind.Relative)); + using (var response = await httpClient.GetAsync(serviceHealthUri)) + { + // Checking the response from the 'status' end point. Note that we are simply checking + // that the HTTP status returned is a 200 status, and that the response has the correct + // Content-Type header. A more sophisticated check would parse the JSON response and + // validate its values. At the moment we do not do this more sophisticated check. + isInitialized = response.StatusCode == HttpStatusCode.OK && response.Content.Headers.ContentType is { MediaType: string mediaType } && mediaType.StartsWith("application/json", StringComparison.OrdinalIgnoreCase); } } - catch (Exception ex) when (ex is HttpRequestException || ex is TaskCanceledException) - { - // Do nothing. The exception is expected, meaning driver service is not initialized. - } - - return isInitialized; } + catch (Exception ex) when (ex is HttpRequestException || ex is TaskCanceledException) + { + // Do nothing. The exception is expected, meaning driver service is not initialized. + } + + return isInitialized; } /// @@ -217,39 +225,55 @@ public void Dispose() /// /// Starts the DriverService if it is not already running. /// - [MemberNotNull(nameof(driverServiceProcess))] public void Start() { - if (this.driverServiceProcess != null) - { - return; - } - - this.driverServiceProcess = new Process(); + Task.Run(this.StartAsync).GetAwaiter().GetResult(); + } - if (this.DriverServicePath != null) + /// + /// Starts the DriverService if it is not already running. + /// + /// A task that represents the asynchronous start operation. + public async Task StartAsync() + { + if (this.driverServiceProcess == null) { - if (this.DriverServiceExecutableName is null) + this.driverServiceProcess = new Process(); + + try { - throw new InvalidOperationException("If the driver service path is specified, the driver service executable name must be as well"); - } + if (this.DriverServicePath != null) + { + if (this.DriverServiceExecutableName is null) + { + throw new InvalidOperationException("If the driver service path is specified, the driver service executable name must be as well"); + } - this.driverServiceProcess.StartInfo.FileName = Path.Combine(this.DriverServicePath, this.DriverServiceExecutableName); - } - else - { - this.driverServiceProcess.StartInfo.FileName = new DriverFinder(this.GetDefaultDriverOptions()).GetDriverPath(); - } + this.driverServiceProcess.StartInfo.FileName = Path.Combine(this.DriverServicePath, this.DriverServiceExecutableName); + } + else + { + this.driverServiceProcess.StartInfo.FileName = new DriverFinder(this.GetDefaultDriverOptions()).GetDriverPath(); + } - this.driverServiceProcess.StartInfo.Arguments = this.CommandLineArguments; - this.driverServiceProcess.StartInfo.UseShellExecute = false; - this.driverServiceProcess.StartInfo.CreateNoWindow = this.HideCommandPromptWindow; + this.driverServiceProcess.StartInfo.Arguments = this.CommandLineArguments; + this.driverServiceProcess.StartInfo.UseShellExecute = false; + this.driverServiceProcess.StartInfo.CreateNoWindow = this.HideCommandPromptWindow; - DriverProcessStartingEventArgs eventArgs = new DriverProcessStartingEventArgs(this.driverServiceProcess.StartInfo); - this.OnDriverProcessStarting(eventArgs); + DriverProcessStartingEventArgs eventArgs = new DriverProcessStartingEventArgs(this.driverServiceProcess.StartInfo); + this.OnDriverProcessStarting(eventArgs); - this.driverServiceProcess.Start(); - bool serviceAvailable = this.WaitForServiceInitialization(); + this.driverServiceProcess.Start(); + } + catch + { + this.driverServiceProcess.Dispose(); + this.driverServiceProcess = null; + throw; + } + } + + bool serviceAvailable = await this.WaitForServiceInitializationAsync().ConfigureAwait(false); DriverProcessStartedEventArgs processStartedEventArgs = new DriverProcessStartedEventArgs(this.driverServiceProcess); this.OnDriverProcessStarted(processStartedEventArgs); @@ -275,13 +299,33 @@ protected virtual void Dispose(bool disposing) { if (disposing) { - this.Stop(); + Task.Run(this.StopAsync).GetAwaiter().GetResult(); } this.isDisposed = true; } } + /// + /// Releases all resources associated with this . + /// + /// A task that represents the asynchronous dispose operation. + public async ValueTask DisposeAsync() + { + await DisposeAsyncCore(); + Dispose(false); + GC.SuppressFinalize(this); + } + + /// + /// Releases all resources associated with this type in the instance's type chain. Override to dispose more resources. + /// + /// A task that represents the asynchronous dispose operation. + protected virtual async ValueTask DisposeAsyncCore() + { + await this.StopAsync().ConfigureAwait(false); + } + /// /// Raises the event. /// @@ -313,7 +357,7 @@ protected void OnDriverProcessStarted(DriverProcessStartedEventArgs eventArgs) /// /// Stops the DriverService. /// - private void Stop() + private async Task StopAsync() { if (this.IsRunning) { @@ -334,7 +378,7 @@ private void Stop() // we'll retry. We wait for exit here, since catching the exception // for a failed HTTP request due to a closed socket is particularly // expensive. - using (var response = Task.Run(async () => await httpClient.GetAsync(shutdownUrl)).GetAwaiter().GetResult()) + using (var response = await httpClient.GetAsync(shutdownUrl).ConfigureAwait(false)) { } @@ -371,7 +415,7 @@ private void Stop() /// /// if the service is properly started and receiving HTTP requests; /// otherwise; . - private bool WaitForServiceInitialization() + private async Task WaitForServiceInitializationAsync() { bool isInitialized = false; DateTime timeout = DateTime.Now.Add(this.InitializationTimeout); @@ -383,7 +427,7 @@ private bool WaitForServiceInitialization() break; } - isInitialized = this.IsInitialized; + isInitialized = await this.IsInitializedAsync().ConfigureAwait(false); } return isInitialized; diff --git a/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs b/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs index 0f1d341c8c353..3f17f809cae2a 100644 --- a/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs +++ b/dotnet/src/webdriver/Remote/DriverServiceCommandExecutor.cs @@ -117,7 +117,7 @@ public async Task ExecuteAsync(Command commandToExecute) Response toReturn; if (commandToExecute.Name == DriverCommand.NewSession) { - this.service.Start(); + await this.service.StartAsync().ConfigureAwait(false); } // Use a try-catch block to catch exceptions for the Quit