private async Task EnsureVirtualConnectionClientCreated() { // Asynchronous equivalent to a 'lock(...) { ... }' await _connectionCreationSemaphore.WaitAsync(); try { if (_virtualConnectionClient == null) { _physicalConnection = StreamConnection.Create(); var connection = await _physicalConnection.Open(_socketAddress); _virtualConnectionClient = new VirtualConnectionClient(connection); _virtualConnectionClient.OnError += (ex) => { // This callback is fired only if there's a protocol-level failure (e.g., child process disconnected // unexpectedly). It does *not* fire when RPC calls return errors. Since there's been a protocol-level // failure, this Node instance is no longer usable and should be discarded. _connectionHasFailed = true; OutputLogger.LogError(0, ex, ex.Message); }; } } finally { _connectionCreationSemaphore.Release(); } }
private async Task <VirtualConnectionClient> GetOrCreateVirtualConnectionClientAsync() { var client = _currentVirtualConnectionClient; if (client == null) { await _clientModificationSemaphore.WaitAsync(); try { if (_currentVirtualConnectionClient == null) { var address = _addressForNextConnection; if (string.IsNullOrEmpty(address)) { // This shouldn't happen, because we always await 'EnsureReady' before getting here. throw new InvalidOperationException("Cannot open connection to Node process until it has signalled that it is ready"); } _currentPhysicalConnection = StreamConnection.Create(); var connection = await _currentPhysicalConnection.Open(address); _currentVirtualConnectionClient = new VirtualConnectionClient(connection); _currentVirtualConnectionClient.OnError += (ex) => { // TODO: Log the exception properly. Need to change the chain of calls up to this point to supply // an ILogger or IServiceProvider etc. Console.WriteLine(ex.Message); ExitNodeProcess(); // We'll restart it next time there's a request to it }; } return(_currentVirtualConnectionClient); } finally { _clientModificationSemaphore.Release(); } } else { return(client); } }