public async Task CreateAndRun(string rHome, string rhostDirectory = null, string rCommandLineArguments = null, int timeout = 3000, CancellationToken ct = default(CancellationToken)) { await TaskUtilities.SwitchToBackgroundThread(); rhostDirectory = rhostDirectory ?? Path.GetDirectoryName(typeof(RHost).Assembly.GetAssemblyPath()); rCommandLineArguments = rCommandLineArguments ?? string.Empty; string rhostExe = Path.Combine(rhostDirectory, RHostExe); string rBinPath = Path.Combine(rHome, RBinPathX64); if (!File.Exists(rhostExe)) { throw new RHostBinaryMissingException(); } // Grab an available port from the ephemeral port range (per RFC 6335 8.1.2) for the server socket. WebSocketServer server = null; var rnd = new Random(); const int ephemeralRangeStart = 49152; var ports = from port in Enumerable.Range(ephemeralRangeStart, 0x10000 - ephemeralRangeStart) let pos = rnd.NextDouble() orderby pos select port; foreach (var port in ports) { ct.ThrowIfCancellationRequested(); server = new WebSocketServer(port) { ReuseAddress = false }; server.AddWebSocketService("/", CreateWebSocketMessageTransport); try { server.Start(); break; } catch (SocketException ex) { if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse) { server = null; } else { throw new MessageTransportException(ex); } } catch (WebSocketException ex) { throw new MessageTransportException(ex); } } if (server == null) { throw new MessageTransportException(new SocketException((int)SocketError.AddressAlreadyInUse)); } var psi = new ProcessStartInfo { FileName = rhostExe, UseShellExecute = false }; psi.EnvironmentVariables["R_HOME"] = rHome; psi.EnvironmentVariables["PATH"] = Environment.GetEnvironmentVariable("PATH") + ";" + rBinPath; if (_name != null) { psi.Arguments += " --rhost-name " + _name; } psi.Arguments += Invariant($" --rhost-connect ws://127.0.0.1:{server.Port}"); if (!showConsole) { psi.CreateNoWindow = true; } if (!string.IsNullOrWhiteSpace(rCommandLineArguments)) { psi.Arguments += Invariant($" {rCommandLineArguments}"); } using (this) using (_process = Process.Start(psi)) { _log.RHostProcessStarted(psi); _process.EnableRaisingEvents = true; _process.Exited += delegate { Dispose(); }; try { ct = CancellationTokenSource.CreateLinkedTokenSource(ct, _cts.Token).Token; // Timeout increased to allow more time in test and code coverage runs. await Task.WhenAny(_transportTcs.Task, Task.Delay(timeout)).Unwrap(); if (!_transportTcs.Task.IsCompleted) { _log.FailedToConnectToRHost(); throw new RHostTimeoutException("Timed out waiting for R host process to connect"); } await Run(null, ct); } catch (Exception) { // TODO: delete when we figure out why host occasionally times out in code coverage runs. //await _log.WriteFormatAsync(MessageCategory.Error, "Exception running R Host: {0}", ex.Message); throw; } finally { if (!_process.HasExited) { try { _process.WaitForExit(500); if (!_process.HasExited) { _process.Kill(); _process.WaitForExit(); } } catch (InvalidOperationException) { } } _log.RHostProcessExited(); } } }