1016 lines
34 KiB
C#
1016 lines
34 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
using System.Diagnostics;
|
|
using DTS.Common.Utilities.Logging;
|
|
using System.Net.Sockets;
|
|
using DTS.Common.Utilities;
|
|
using DTS.Common.Utils;
|
|
using static DTS.Common.Enums.Communication.CommunicationConstantsAndEnums;
|
|
using DTS.Common.Interface.Communication;
|
|
using DTS.Common.Interface.Connection;
|
|
using DTS.Common.Classes.Connection;
|
|
using DTS.Common.Enums.DASFactory;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace DTS.Common.ICommunication
|
|
{
|
|
public abstract class Communication<T> : Interface.DASFactory.ICommunication where T : IConnection, new()
|
|
{
|
|
/// <summary>
|
|
/// Indicates an error occurred during setup
|
|
/// http://manuscript.dts.local/f/cases/43289/SW-no-longer-warns-when-hardware-is-in-bootloader-mode
|
|
/// </summary>
|
|
public bool ErrorInSetup { get; set; } = false;
|
|
protected void Lock() { }
|
|
protected void Release() { }
|
|
|
|
protected T sock;
|
|
|
|
public string ConnectString => sock.ConnectString;
|
|
|
|
public bool Connected => sock.Connected;
|
|
|
|
public int CompareTo(Interface.DASFactory.ICommunication dev)
|
|
{
|
|
return string.Compare(sock.ConnectString.ToUpper(), dev.ConnectString.ToUpper(), StringComparison.Ordinal);
|
|
}
|
|
|
|
public int CompareTo(string conStr)
|
|
{
|
|
return string.Compare(sock.ConnectString.ToUpper(), conStr.ToUpper(), StringComparison.Ordinal);
|
|
}
|
|
|
|
public ICommunication_DASInfo DASInfo { get; set; }
|
|
protected SecureQueue<byte> SockDataBuffer;
|
|
protected ManualResetEvent SyncEvent;
|
|
|
|
public IConnection Transport
|
|
{
|
|
get => sock;
|
|
set => throw new NotSupportedException("Communication::Transport Can't replace socket");
|
|
}
|
|
|
|
public int ReceiveBufferSize { get; set; }
|
|
|
|
public string SerialNumber { get; set; }
|
|
|
|
private string _firmwareVersion;
|
|
public string FirmwareVersion
|
|
{
|
|
get => _firmwareVersion;
|
|
set
|
|
{
|
|
_firmwareVersion = value;
|
|
FirmwareVersionUpdated();
|
|
}
|
|
}
|
|
protected virtual void FirmwareVersionUpdated()
|
|
{
|
|
}
|
|
public byte ProtocolVersion { get; set; }
|
|
|
|
public Dictionary<DFConstantsAndEnums.ProtocolLimitedCommands, byte> MinimumProtocols { get; set; }
|
|
|
|
public abstract void InitMinProto();
|
|
|
|
public bool IsCommandSupported(DFConstantsAndEnums.ProtocolLimitedCommands command)
|
|
{
|
|
return ProtocolVersion >= GetMinProto(command);
|
|
// Not Supported
|
|
}
|
|
|
|
public byte GetMinProto(DFConstantsAndEnums.ProtocolLimitedCommands command)
|
|
{
|
|
if (null == MinimumProtocols) { throw new NotSupportedException("GetMinProto MinimumProtocols is null"); }
|
|
return !MinimumProtocols.ContainsKey(command) ? Convert.ToByte(0xFF) : Convert.ToByte(MinimumProtocols[command]);
|
|
}
|
|
|
|
public int FlushTimeoutMilliSec { get; set; }
|
|
|
|
#region Action data
|
|
|
|
public abstract class ActionData : IDisposable
|
|
{
|
|
protected readonly CommunicationCallback UserCallback;
|
|
protected readonly object UserCallbackObject;
|
|
public readonly int UserTimeout;
|
|
|
|
protected ActionData(CommunicationCallback cb, object obj, int to)
|
|
{
|
|
Debug.Assert(cb != null, "ActionData: callback can't be null");
|
|
UserCallback = cb;
|
|
UserCallbackObject = obj;
|
|
UserTimeout = to;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
KillTimer();
|
|
}
|
|
|
|
#region Timeout timer
|
|
|
|
private Timer _callbackTimer;
|
|
private bool _timerFired;
|
|
private object _timerLock;
|
|
|
|
public void StartTimer()
|
|
{
|
|
_timerLock = new object();
|
|
_timerFired = false;
|
|
_callbackTimer = new Timer(TimerCallback, this, UserTimeout, Timeout.Infinite);
|
|
}
|
|
|
|
private void TimerCallback(object state)
|
|
{
|
|
lock (_timerLock)
|
|
{
|
|
if (_callbackTimer == null || _timerFired)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// remember that we where here
|
|
_timerFired = true;
|
|
|
|
// stop it from happening again
|
|
_callbackTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
_callbackTimer.Dispose();
|
|
_callbackTimer = null;
|
|
|
|
// tell user we timed out
|
|
var sr = new CommunicationReport(UserCallbackObject, GetTimeoutResult());
|
|
UserCallback(sr);
|
|
}
|
|
}
|
|
|
|
public void KillTimer()
|
|
{
|
|
lock (_timerLock)
|
|
{
|
|
if (_callbackTimer == null || _timerFired)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
_callbackTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
_callbackTimer.Dispose();
|
|
_callbackTimer = null;
|
|
}
|
|
catch
|
|
{
|
|
// just to kill a stupid message...
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool TimerExpired => _timerFired;
|
|
|
|
#endregion
|
|
|
|
public bool ReportCanceled()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.Canceled);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
public abstract bool ReportFailure();
|
|
|
|
public abstract bool ReportSuccess();
|
|
|
|
protected abstract CommunicationResult GetTimeoutResult();
|
|
}
|
|
|
|
private class ConnectActionData : ActionData
|
|
{
|
|
public ConnectActionData(CommunicationCallback cb, object obj, int to)
|
|
: base(cb, obj, to)
|
|
{
|
|
}
|
|
|
|
public override bool ReportFailure()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.ConnectFailed);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
public override bool ReportSuccess()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.ConnectOK);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
protected override CommunicationResult GetTimeoutResult()
|
|
{
|
|
return CommunicationResult.ConnectTimeout;
|
|
}
|
|
}
|
|
|
|
private class DisconnectActionData : ActionData
|
|
{
|
|
public DisconnectActionData(CommunicationCallback cb, object obj, int to)
|
|
: base(cb, obj, to)
|
|
{
|
|
}
|
|
|
|
public override bool ReportFailure()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.DisconnectFailed);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
public override bool ReportSuccess()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.DisconnectOK);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
protected override CommunicationResult GetTimeoutResult()
|
|
{
|
|
return CommunicationResult.DisconnectTimeout;
|
|
}
|
|
}
|
|
|
|
public class ExecuteActionData : ActionData
|
|
{
|
|
public ExecuteActionData(CommunicationCallback cb, object obj, int to)
|
|
: base(cb, obj, to)
|
|
{
|
|
}
|
|
|
|
public override bool ReportFailure()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.SendFailed);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
public override bool ReportSuccess()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.SendOK);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
public bool ReportReadSuccess(byte[] data)
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.ReceiveOK);
|
|
sr.Data = data;
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
public bool ReportReadTimeout()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.ReceiveTimeout);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
public bool ReportReadDisconnected()
|
|
{
|
|
if (UserCallback == null) return false;
|
|
var sr = new CommunicationReport(UserCallbackObject, CommunicationResult.ReceiveFailed);
|
|
return UserCallback(sr);
|
|
}
|
|
|
|
protected override CommunicationResult GetTimeoutResult()
|
|
{
|
|
return CommunicationResult.SendTimeout;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public event EventHandler OnDisconnected;
|
|
|
|
#region Execution locking
|
|
|
|
private readonly object _executeBusyMtLock = new object();
|
|
private bool _executeIsBusy;
|
|
public bool ExecuteIsBusy
|
|
{
|
|
get
|
|
{
|
|
lock (_executeBusyMtLock)
|
|
{
|
|
return _executeIsBusy;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
// protected against multi-threaded entry
|
|
lock (_executeBusyMtLock)
|
|
{
|
|
if (value)
|
|
{
|
|
// caller trying to lock resource
|
|
if (_executeIsBusy)
|
|
{
|
|
// but it's already locked
|
|
Debug.Assert(false, "ExecuteIsBusy: re-entry");
|
|
}
|
|
_executeIsBusy = true;
|
|
}
|
|
else
|
|
{
|
|
// caller is leaving
|
|
if (!_executeIsBusy)
|
|
{
|
|
// but it isn't locked
|
|
Debug.Assert(false, "ExecuteIsBusy: extra unlock");
|
|
}
|
|
_executeIsBusy = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public Communication()
|
|
{
|
|
_disposed = false;
|
|
sock = new T();
|
|
sock.OnDisconnected += sock_OnDisconnected;
|
|
SockDataBuffer = new SecureQueue<byte>(SecureQueue<byte>.NullPolicy.SkipNull, "Communication.SockDataBuffer");
|
|
FlushTimeoutMilliSec = 5;
|
|
ReceiveBufferSize = (int)Math.Pow(2, 16);
|
|
SyncEvent = new ManualResetEvent(false);
|
|
InitMinProto();
|
|
}
|
|
|
|
public void Close(int timeout)
|
|
{
|
|
}
|
|
|
|
private void sock_OnDisconnected(object sender, EventArgs e)
|
|
{
|
|
OnDisconnected?.Invoke(this, null);
|
|
}
|
|
|
|
#region Exit handling
|
|
|
|
~Communication()
|
|
{
|
|
//AddTrace("~Communication");
|
|
try
|
|
{
|
|
Dispose(false);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
//AddTrace("Communication.Dispose");
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
private bool _disposed;
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (disposing)
|
|
{
|
|
if (sock != null)
|
|
{
|
|
sock.Dispose();
|
|
}
|
|
SockDataBuffer?.Dispose();
|
|
}
|
|
_disposed = true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Connect
|
|
|
|
public void Connect(string connectString, CommunicationCallback callback, object callbackObject, int callbackTimeout, string hostIPAddress)
|
|
{
|
|
if (sock == null || sock.Connected)
|
|
{
|
|
throw new Exception("Communication.Connect: Socket not initialized or already connected");
|
|
}
|
|
|
|
var action = new ConnectActionData(callback, callbackObject, callbackTimeout);
|
|
try
|
|
{
|
|
sock.Create(connectString, hostIPAddress);
|
|
|
|
action.StartTimer();
|
|
sock.BeginConnect(ConnectCallback, action);
|
|
}
|
|
catch
|
|
{
|
|
action.Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private void ConnectCallback(IAsyncResult ar)
|
|
{
|
|
var action = ar.AsyncState as ActionData;
|
|
try
|
|
{
|
|
// Complete the connection.
|
|
sock.EndConnect(ar);
|
|
|
|
action?.KillTimer();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log($"ConnectCallback failed - {sock.ConnectString}", ex);
|
|
|
|
action?.KillTimer();
|
|
|
|
if (null != action && !action.TimerExpired)
|
|
{
|
|
// no, we're fine.
|
|
action.ReportFailure();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// check if we're too late
|
|
if (null == action || action.TimerExpired) return;
|
|
// no, we're fine. Now setup our reader
|
|
SetupReader();
|
|
action.ReportSuccess();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Disconnect
|
|
|
|
public void Disconnect(bool reuseSocket,
|
|
CommunicationCallback callback,
|
|
object callbackObject,
|
|
int callbackTimeout)
|
|
{
|
|
if (sock == null)
|
|
{
|
|
throw new Exception("Communication.Disconnect: Socket not initialized or already disconnected");
|
|
}
|
|
|
|
CancelEvent.Set();
|
|
var action = new DisconnectActionData(callback, callbackObject, callbackTimeout);
|
|
try
|
|
{
|
|
action.StartTimer();
|
|
sock.BeginDisconnect(reuseSocket, DisconnectCallback, action);
|
|
}
|
|
catch
|
|
{
|
|
action.Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
private void DisconnectCallback(IAsyncResult ar)
|
|
{
|
|
CancelEvent.Set();
|
|
var action = ar.AsyncState as ActionData;
|
|
try
|
|
{
|
|
// Complete the disconnect.
|
|
sock.EndDisconnect(ar);
|
|
|
|
action?.KillTimer();
|
|
}
|
|
catch
|
|
{
|
|
action?.KillTimer();
|
|
|
|
// check if we're too late
|
|
if (null != action && !action.TimerExpired)
|
|
{
|
|
// no, we're fine.
|
|
action.ReportFailure();
|
|
}
|
|
return;
|
|
}
|
|
|
|
// check if we're too late
|
|
if (null != action && !action.TimerExpired)
|
|
{
|
|
// no, we're fine.
|
|
action.ReportSuccess();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Cancel functions
|
|
/// <summary>
|
|
/// event that will signal if cancel has happened
|
|
/// prior to this IsCanceled would have to be checked
|
|
/// or polled
|
|
/// 17600 Communication class improvements from 3.2
|
|
/// </summary>
|
|
public ManualResetEvent CancelEvent { get; } = new ManualResetEvent(false);
|
|
private bool _commandsAreCanceled;
|
|
|
|
public void ForceCancel()
|
|
{
|
|
_commandsAreCanceled = true;
|
|
_sleepAmount = 250;
|
|
CancelEvent.Set();
|
|
}
|
|
public void Cancel()
|
|
{
|
|
_commandsAreCanceled = true;
|
|
_sleepAmount = 500;
|
|
CancelEvent.Set();
|
|
}
|
|
|
|
public bool IsCanceled()
|
|
{
|
|
return _commandsAreCanceled;
|
|
}
|
|
|
|
private int _sleepAmount = 500;
|
|
public void ClearCancel()
|
|
{
|
|
if (!_commandsAreCanceled) return;
|
|
Thread.Sleep(_sleepAmount);
|
|
APILogger.LogString($"Communication.ClearCancel: Waited {_sleepAmount}ms, clearing");
|
|
_commandsAreCanceled = false;
|
|
CancelEvent.Reset();
|
|
}
|
|
#endregion
|
|
|
|
public void Flush(int timeoutMs)
|
|
{
|
|
if (DFConstantsAndEnums.ExtraCommunicationLogging)
|
|
{
|
|
APILogger.Log($"Flushing {sock.ConnectString}");
|
|
}
|
|
SockDataBuffer.Flush();
|
|
while (SockDataBuffer.WaitForData(timeoutMs))
|
|
{
|
|
SockDataBuffer.Flush();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Not currently implemented
|
|
/// </summary>
|
|
/// <param name="byteData"></param>
|
|
/// <param name="cbTimeout"></param>
|
|
/// <returns></returns>
|
|
public byte[] SyncExecute(byte[] byteData, int cbTimeout)
|
|
{
|
|
throw new NotSupportedException("SyncExecute not supported for DTS.DASLib.Communication.Communication");
|
|
}
|
|
|
|
#region Execute
|
|
/// <summary>
|
|
/// pseudo execute is for commands that don't need to send any data and just receive data.
|
|
/// an example of this is the G5 MonitorData command
|
|
///
|
|
/// this execute should be used in the place of a normal execute command, it will not clear out the buffer and will pick up form wherever the
|
|
/// previous pseudoexecute leaves off.
|
|
/// </summary>
|
|
/// <param name="byteData"></param>
|
|
/// <param name="cb"></param>
|
|
/// <param name="cbObject"></param>
|
|
/// <param name="cbTimeout"></param>
|
|
public void PseudoExecute(byte[] byteData, CommunicationCallback cb, object cbObject, int cbTimeout)
|
|
{
|
|
if (IsCanceled())
|
|
{
|
|
throw new CanceledException();
|
|
}
|
|
if (!Connected)
|
|
{
|
|
throw new NotConnectedException(DASResource.Strings.Communication_Execute_Err1);
|
|
}
|
|
|
|
var action = new ExecuteActionData(cb, cbObject, cbTimeout);
|
|
try
|
|
{
|
|
// since we aren't sending anything, we can go straight to the callback
|
|
PseudoSRSendCallback(new PseudoAsyncResult(action));
|
|
}
|
|
catch
|
|
{
|
|
action.Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// process the received data
|
|
/// </summary>
|
|
private void ProcessReceivedData(ExecuteActionData action, int timeout)
|
|
{
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
bool bufferHasData;
|
|
try
|
|
{
|
|
_beforeId = Thread.CurrentThread.ManagedThreadId;
|
|
|
|
bufferHasData = WaitWithCondition.Wait(SockDataBuffer.QueueWaitHandle, timeout, CancelEvent);
|
|
var afterId = Thread.CurrentThread.ManagedThreadId;
|
|
if (afterId != _beforeId)
|
|
{
|
|
// we shouldn't be here
|
|
APILogger.LogString("Communication.ProcessReceivedData: Before and after ID doesn't match, exiting");
|
|
return;
|
|
}
|
|
}
|
|
catch (WaitWithCondition.ConditionMetException)
|
|
{
|
|
if (!sock.Connected || !_receiveCallbackRunning)
|
|
{
|
|
APILogger.LogString("Communication.ProcessReceivedData: recorder is not connected");
|
|
action?.ReportReadDisconnected();
|
|
sock_OnDisconnected(this, null);
|
|
}
|
|
else
|
|
{
|
|
APILogger.LogString("Communication.ProcessReceivedData: WaitWithCancel threw a CanceledException");
|
|
action?.ReportCanceled();
|
|
}
|
|
return;
|
|
}
|
|
if (!bufferHasData)
|
|
{
|
|
// timeout!
|
|
action.ReportReadTimeout();
|
|
return;
|
|
}
|
|
// we're signaled
|
|
var buffer = SockDataBuffer.Dequeue(true);
|
|
if (buffer == null || buffer.Length <= 0) continue;
|
|
|
|
try
|
|
{
|
|
if (DFConstantsAndEnums.ExtraCommunicationLogging)
|
|
{
|
|
APILogger.Log("received ", buffer);
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
|
|
// the buffer has data
|
|
if (action.ReportReadSuccess(buffer))
|
|
{
|
|
// they want more
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log("MessageBox", DASResource.Strings.Communication_Execute, ex);
|
|
// Execution stops here, but I don't think I want to set not busy here
|
|
}
|
|
}
|
|
|
|
public void Execute(byte[] byteData, CommunicationCallback cb, object cbObject, int cbTimeout)
|
|
{
|
|
|
|
if (IsCanceled())
|
|
{
|
|
throw new CanceledException();
|
|
}
|
|
if (!Connected)
|
|
{
|
|
throw new NotConnectedException(DASResource.Strings.Communication_Execute_Err1);
|
|
}
|
|
CancelEvent.Reset();
|
|
var action = new ExecuteActionData(cb, cbObject, cbTimeout);
|
|
|
|
//FB 25582 Refactored to reduce method Cognitive Complexity
|
|
CheckAndFlushBuffer(action);
|
|
|
|
Task sendAsyncTask = SendDataToDevice(byteData, action);
|
|
|
|
ProcessData(cbTimeout, action, sendAsyncTask);
|
|
}
|
|
|
|
private void ProcessData(int cbTimeout, ExecuteActionData action, Task sendAsyncTask)
|
|
{
|
|
// Continue if the task was successful, if there is a timeout/ error in send task this code would not get called.
|
|
sendAsyncTask.ContinueWith(t =>
|
|
{
|
|
if (null != action && !action.TimerExpired)
|
|
{
|
|
//process the received data
|
|
ProcessReceivedData(action, cbTimeout);
|
|
}
|
|
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
|
|
|
// FB 18389 log the exception
|
|
// Continue if the task was faulted
|
|
sendAsyncTask.ContinueWith(t =>
|
|
{
|
|
if (t.Exception != null)
|
|
{
|
|
foreach (var e in t.Exception.Flatten().InnerExceptions)
|
|
{
|
|
APILogger.Log("Exception in sendAsyncTask ", e.ToString());
|
|
}
|
|
}
|
|
}, TaskContinuationOptions.OnlyOnFaulted);
|
|
}
|
|
|
|
private Task SendDataToDevice(byte[] byteData, ExecuteActionData action)
|
|
{
|
|
//Sending the data to the remote device.
|
|
return Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
//https://johnthiriet.com/configure-await/
|
|
var t = sock.SendAsync(byteData, 0, byteData.Length).ConfigureAwait(false);
|
|
action?.StartTimer();
|
|
await t;
|
|
action?.KillTimer();
|
|
if (IsCanceled())
|
|
{
|
|
action?.ReportCanceled();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
try
|
|
{
|
|
action?.KillTimer();
|
|
APILogger.LogString("Communication.Execute: sendAsyncTask threw an exception");
|
|
APILogger.LogException(e);
|
|
if (IsCanceled())
|
|
{
|
|
action?.ReportCanceled();
|
|
}
|
|
else
|
|
{
|
|
action?.ReportFailure();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.LogString("Communication.Execute: Exception handler threw exception!");
|
|
APILogger.LogException(ex);
|
|
throw;
|
|
// Execution stops here, but I don't think I want to set not busy here
|
|
}
|
|
throw;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void CheckAndFlushBuffer(ExecuteActionData action)
|
|
{
|
|
try
|
|
{
|
|
// make sure we don't have anything pending
|
|
if (!SockDataBuffer.IsEmpty())
|
|
{
|
|
if (DFConstantsAndEnums.ExtraCommunicationLogging)
|
|
{
|
|
APILogger.Log($"Flushing {sock.ConnectString}");
|
|
}
|
|
Flush(FlushTimeoutMilliSec);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
action.Dispose();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is the callback for the PseudoExecute function
|
|
/// this is identical to SRSendCallback with the exception that it
|
|
/// doesn't do sock.endsend (as nothing was sent)
|
|
/// </summary>
|
|
/// <param name="ar"></param>
|
|
private void PseudoSRSendCallback(IAsyncResult ar)
|
|
{
|
|
var action = ar.AsyncState as ExecuteActionData;
|
|
try
|
|
{
|
|
|
|
SRRecvCallback(action);
|
|
|
|
}
|
|
catch (Exception)
|
|
{
|
|
try
|
|
{
|
|
|
|
action?.ReportFailure();
|
|
}
|
|
catch (Exception exx)
|
|
{
|
|
APILogger.LogString("Communication.SRSendCallback: Exception handler threw exception!");
|
|
APILogger.LogException(exx);
|
|
// Execution stops here, but I don't think I want to set not busy here
|
|
}
|
|
}
|
|
}
|
|
|
|
private int _beforeId;
|
|
|
|
private void SRRecvCallback(object _action)
|
|
{
|
|
try
|
|
{
|
|
var action = _action as ExecuteActionData;
|
|
while (true)
|
|
{
|
|
bool bufferHasData;
|
|
try
|
|
{
|
|
_beforeId = Thread.CurrentThread.ManagedThreadId;
|
|
|
|
|
|
bufferHasData = WaitWithCondition.Wait(SockDataBuffer.QueueWaitHandle, action.UserTimeout, CancelEvent);
|
|
var afterId = Thread.CurrentThread.ManagedThreadId;
|
|
if (afterId != _beforeId)
|
|
{
|
|
// we shouldn't be here
|
|
APILogger.LogString("Communication.SRRecvCallback: Before and after ID doesn't match, exiting");
|
|
return;
|
|
}
|
|
}
|
|
catch (WaitWithCondition.ConditionMetException)
|
|
{
|
|
if (!sock.Connected || !_receiveCallbackRunning)
|
|
{
|
|
APILogger.LogString("Communication.SRRecvCallback: recorder is not connected");
|
|
action?.ReportReadDisconnected();
|
|
sock_OnDisconnected(this, null);
|
|
}
|
|
else
|
|
{
|
|
APILogger.LogString("Communication.SRRecvCallback: WaitWithCancel threw a CanceledException");
|
|
action?.ReportCanceled();
|
|
}
|
|
return;
|
|
}
|
|
if (!bufferHasData)
|
|
{
|
|
// timeout!
|
|
action.ReportReadTimeout();
|
|
return;
|
|
}
|
|
// we're signaled
|
|
var buffer = SockDataBuffer.Dequeue(true);
|
|
if (buffer == null || buffer.Length <= 0) continue;
|
|
// the buffer has data
|
|
if (action.ReportReadSuccess(buffer))
|
|
{
|
|
// they want more
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
APILogger.Log("MessageBox", DASResource.Strings.Communication_SRRecvCallbackFailed, ex);
|
|
// Execution stops here, but I don't think I want to set not busy here
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Reading thread
|
|
|
|
public void SetupReader()
|
|
{
|
|
var receiveDataBuffer = new byte[ReceiveBufferSize];
|
|
sock.BeginReceive(receiveDataBuffer, 0, ReceiveBufferSize,
|
|
ReceiveCallback, receiveDataBuffer);
|
|
}
|
|
|
|
private bool _receiveCallbackRunning = true;
|
|
|
|
// callback for Receive
|
|
private void ReceiveCallback(IAsyncResult ar)
|
|
{
|
|
try
|
|
{
|
|
var bytesRead = 0;
|
|
byte[] receiveBuffer = null;
|
|
if (null == sock || !sock.Connected)
|
|
{
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
// retrieve buffer
|
|
receiveBuffer = ar.AsyncState as byte[];
|
|
// Read data from the remote device.
|
|
bytesRead = sock.EndReceive(ar);
|
|
}
|
|
|
|
//APILogger.LogString("ReceiveCallback: " + this.ToString() + " got " + bytesRead.ToString());
|
|
if (bytesRead > 0)
|
|
{
|
|
var tmp = new byte[bytesRead];
|
|
Buffer.BlockCopy(receiveBuffer, 0, tmp, 0, bytesRead);
|
|
SockDataBuffer.Enqueue(tmp);
|
|
// Get the rest of the data.
|
|
sock.BeginReceive(receiveBuffer, 0, ReceiveBufferSize,
|
|
ReceiveCallback, receiveBuffer);
|
|
}
|
|
else
|
|
{
|
|
// we're disconnected
|
|
APILogger.LogString($"Communication.ReceiveCallback: sock.EndReceive returned 0, exiting, {this?.ConnectString}: Connected:{sock?.Connected}");
|
|
_receiveCallbackRunning = false;
|
|
|
|
// REPORT this as a disconnect!
|
|
// http://fogbugz/fogbugz/default.asp?14728 - Not notified if you lose a single slice6 on a slice6db
|
|
OnDisconnected?.Invoke(this, null);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (ex is SocketException)
|
|
{
|
|
var connectionString = "N/A";
|
|
if (sock != null && sock.ConnectString != null)
|
|
{
|
|
connectionString = sock.ConnectString;
|
|
}
|
|
APILogger.LogString(
|
|
$"{APILogger.GetCurrentMethod()} Connection:{connectionString} Message:{ex.Message}");
|
|
sock.KeepAliveErrorReceived();
|
|
}
|
|
else
|
|
{
|
|
APILogger.LogException(ex);
|
|
}
|
|
_receiveCallbackRunning = false;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
public class PseudoAsyncResult : IAsyncResult
|
|
{
|
|
private readonly ExecuteActionData _action;
|
|
|
|
public PseudoAsyncResult(ExecuteActionData action)
|
|
{
|
|
_action = action;
|
|
}
|
|
|
|
public bool IsCompleted => true;
|
|
|
|
public WaitHandle AsyncWaitHandle => throw new NotImplementedException();
|
|
|
|
public object AsyncState => _action;
|
|
|
|
public bool CompletedSynchronously => true;
|
|
}
|
|
}
|
|
|
|
public class CommunicationReport : ICommunicationReport
|
|
{
|
|
public object UserState { get; set; }
|
|
public CommunicationResult Result { get; set; }
|
|
public byte[] Data { get; set; }
|
|
|
|
public CommunicationReport(object state, CommunicationResult res)
|
|
{
|
|
UserState = state;
|
|
Result = res;
|
|
}
|
|
}
|
|
}
|