private static CirceComConnection TryCreateOpenedConnection([NotNull] string portName, [CanBeNull] Action<CirceComConnection> attachHandlersCallback, [CanBeNull] Action<CirceComConnection> detachHandlersCallback) { CirceComConnection connection = null; Exception error; try { connection = new CirceComConnection(portName); attachHandlersCallback?.Invoke(connection); connection.Open(); return connection; } catch (InvalidOperationException ex) { // This may happen when you unplug the serial port cable while writing to the port. // The exception indicates that the port has been closed, so writing fails. error = ex; } catch (UnauthorizedAccessException ex) { error = ex; } catch (SecurityException ex) { error = ex; } catch (IOException ex) { error = ex; } Log.Debug($"Failed to open port {portName}: {error.GetType()}: {error.Message}"); if (connection != null) { detachHandlersCallback?.Invoke(connection); connection.Dispose(); } return null; }
private void CreateSession() { Log.Debug("Entering CreateSession."); // Block until we have successfully sent a Login operation. Exception connectError; CirceComConnection newConnection; do { // Block until we have obtained a COM port. bool raisedWaitEvent = false; Log.Debug("Entering wait loop until at least one port found."); #if DEBUGGING_HACKS string comPortName = "COM3"; // Force single COM port #else string comPortName = portRotator.GetNextPortName(); #endif while (comPortName == null) { if (disposeRequestedWaitHandle.IsSet) { Log.Debug("CreateSession: Disposal has been requested while scanning for ports."); OnStateChanged(ControllerConnectionState.Disconnected, null); return; } if (!raisedWaitEvent) { OnStateChanged(ControllerConnectionState.WaitingForComPort, null); raisedWaitEvent = true; } Thread.Sleep(TryNextComPortDelayInMilliseconds); comPortName = portRotator.GetNextPortName(); } if (disposeRequestedWaitHandle.IsSet) { Log.Debug("CreateSession: Disposal has been requested after port detection."); OnStateChanged(ControllerConnectionState.Disconnected, null); return; } OnStateChanged(ControllerConnectionState.Connecting, comPortName); seenProtocolVersionMismatch.Value = null; newConnection = new CirceComConnection(comPortName); newConnection.PacketSending += NewConnectionOnPacketSending; newConnection.PacketReceived += NewConnectionOnPacketReceived; newConnection.OperationReceived += NewConnectionOnOperationReceived; // The caller has closed a previous connection and paused the outgoing queue. So we // are guaranteed to be the only thread that is writing to the port. So locking // for exclusive write access is not needed here. if (TryDirectSend(newConnection, true, new LoginOperation(), out connectError)) { // A KeepAlive response should be received shortly, so we'll set the lifetime to one // second in the past, which leaves a single second for the mediator to respond. lastRefreshTimeInUtc.Value = SystemContext.UtcNow().AddSeconds(-1); } if (connectError != null) { Log.Warn($"Failed to open port {comPortName} and send Login operation.", connectError); // Because the lifetime of this instance is longer than the lifetime of a connection, it // is critical to detach event handlers so the garbage collector can run. Failing to detach // event handlers will cause a memory leak that increases at each reconnect. newConnection.PacketSending -= NewConnectionOnPacketSending; newConnection.PacketReceived -= NewConnectionOnPacketReceived; newConnection.OperationReceived -= NewConnectionOnOperationReceived; newConnection.Dispose(); OnStateChanged(ControllerConnectionState.Disconnected, comPortName); } } while (connectError != null); activeConnection.Value = newConnection; }