public async Task <uint> SendReliableMessage( string destination, byte[] data, IProgress <OutMessageProgressInfo> progress = null, uint messageId = 0, uint replyToMessageId = 0, byte messageKind = 0, CancellationToken token = default(CancellationToken) ) { using (_stateHelper.GetFuncWrapper()) { try { Assert.True(SamHelper.IsDestinationStringValid(destination)); Assert.NotNull(data); Assert.InRange(data.Length, 1, _settings.MaxMesageLength); if (messageId == 0) { messageId = await GetNextOutMessageId().ConfigureAwait(false); } DestinationInfo destinationInfo; using (await _destinationDbLockSem.GetDisposable(token: token).ConfigureAwait(false)) { if (!_destinationDb.ContainsKey(destination)) { _destinationDb.Add(destination, new DestinationInfo()); } destinationInfo = _destinationDb[destination]; } OutMessageInfo outMessageInfo; using (await destinationInfo.OutMessagesDbLockSem.GetDisposable(token: token).ConfigureAwait(false)) { if ( destinationInfo.OutMessagesDb.ContainsKey(messageId) ) { throw new EnumException <SendReliableMessageExcs>( SendReliableMessageExcs.ExistMessageId ); } outMessageInfo = new OutMessageInfo( data, replyToMessageId, _settings.MaxBlockSize, progress ); destinationInfo.OutMessagesDb.Add(messageId, outMessageInfo); } try { uint blocksCount = outMessageInfo.BlockCount; /* * Handshake */ using ( var compositeTokenSource = CancellationTokenSource.CreateLinkedTokenSource( _cts.Token, token ) ) { try { var messageStatusTask = MessageStatusReceived .Where( x => x.Destination == destination && x.MessageId == messageId ) .FirstAsync() .ToTask(compositeTokenSource.Token); byte[] messageHash = GetMessageHash( messageId, replyToMessageId, messageKind, data ); for ( outMessageInfo.HandshakeAttemptNo = 0; outMessageInfo.HandshakeAttemptNo <= _settings.HandshakeAttemptCount; outMessageInfo.HandshakeAttemptNo++ ) { if ( outMessageInfo.HandshakeAttemptNo == _settings.HandshakeAttemptCount ) { throw new EnumException <SendReliableMessageExcs>( SendReliableMessageExcs.HandshakeTimeout ); } await SendHandshakeStart( destination, messageId, replyToMessageId, (uint)data.Length, outMessageInfo.BlockSize, messageHash, outMessageInfo.Data[0], messageKind ).ConfigureAwait(false); var waitTasks = await Task.WhenAny( messageStatusTask, Task.Delay( TimeSpan.FromSeconds( _settings.HandshakeTimeoutSeconds ), compositeTokenSource.Token ) ).ConfigureAwait(false); if (waitTasks == messageStatusTask) { var messageStatus = await messageStatusTask.ConfigureAwait(false); if ( !( messageStatus.StatusCode == MessageStatusArgs.MessageStatusCode.HandshakeOk || ( messageStatus.StatusCode == MessageStatusArgs.MessageStatusCode.AllBlocksReceived && outMessageInfo.BlockCount == 1 ) ) ) { throw new EnumException <SendReliableMessageExcs>( SendReliableMessageExcs.HandshakeError, messageStatus.WriteObjectToJson() ); } break; } } outMessageInfo.Status = OutMessageStatus.HandshakeOk; outMessageInfo.BlockConfirmed[0] = true; if (outMessageInfo.BlockCount > 1) { // Handshake established, start sending blocks DateTime lastBlockConfirmationTime = DateTime.UtcNow; var blocksInProgress = new SortedSet <uint>(); var blocksInProgressLockSem = new SemaphoreSlim(1); var blockSentTimes = new DateTime[blocksCount]; using ( BlockConfirmationReceived .Where(_ => _.Destination == destination && _.MessageId == messageId ) .ObserveOn(TaskPoolScheduler.Default) .Subscribe(async x => { if ( x.BlockId < blocksCount && !outMessageInfo.BlockConfirmed[x.BlockId] ) { await outMessageInfo.ConfirmBlock(x.BlockId).ConfigureAwait(false); lastBlockConfirmationTime = DateTime.UtcNow; using (await blocksInProgressLockSem.GetDisposable().ConfigureAwait(false)) { blocksInProgress.Remove(x.BlockId); } } } ) ) { while (true) { compositeTokenSource.Token.ThrowIfCancellationRequested(); IEnumerable <int> blocksToSend; using ( await blocksInProgressLockSem.GetDisposable(token: compositeTokenSource.Token) .ConfigureAwait(false)) { if ( blocksInProgress.Count == 0 && outMessageInfo.BlockConfirmed.All(x => x) ) { break; } blocksInProgress.RemoveWhere( i => DateTime.UtcNow > ( blockSentTimes[i] + TimeSpan.FromSeconds( _settings .ConfirmationOneBlockTimeoutSeconds ) ) ); blocksToSend = Enumerable.Range(0, (int)blocksCount) .Where( i => !blocksInProgress.Contains((uint)i) && !outMessageInfo.BlockConfirmed[i] ) .Take( Math.Max( 0, _settings.WindowSize - blocksInProgress.Count ) ); } foreach (int blockId in blocksToSend) { await SendBlock( destination, messageId, (uint)blockId ).ConfigureAwait(false); await outMessageInfo.SendBlock( (uint)blockId ).ConfigureAwait(false); using ( await blocksInProgressLockSem.GetDisposable(token: compositeTokenSource.Token) .ConfigureAwait(false)) { blocksInProgress.Add((uint)blockId); } blockSentTimes[blockId] = DateTime.UtcNow; } if ( DateTime.UtcNow > lastBlockConfirmationTime + TimeSpan.FromSeconds( _settings.ConfirmationTimeoutSeconds ) ) { throw new EnumException <SendReliableMessageExcs>( SendReliableMessageExcs.SendBlocksTimeout ); } await Task.Delay(50, compositeTokenSource.Token).ConfigureAwait(false); } } } outMessageInfo.Status = OutMessageStatus.AllBlocksSent; // all blocks sent and confirmed _log.Trace( "{4}, send reliable message of {0} bytes " + "to {1} id={2},rid={3},kind={5}", data.Length, destination.Substring(0, 20), messageId, replyToMessageId, _reliableSamHelperGuid.ToString().Substring(0, 5), messageKind ); return(messageId); } finally { compositeTokenSource.Cancel(); } } } finally { TryRemoveOutMessageInfo(destinationInfo, messageId); } } catch (EnumException <SendReliableMessageExcs> ) { throw; } catch (OperationCanceledException) { throw; } catch (TimeoutException) { throw; } catch (Exception exc) { throw new EnumException <SendReliableMessageExcs>( SendReliableMessageExcs.UnexpectedError, innerException: exc ); } } }
public async Task<uint> SendReliableMessage( string destination, byte[] data, IProgress<OutMessageProgressInfo> progress = null, uint messageId = 0, uint replyToMessageId = 0, byte messageKind = 0, CancellationToken token = default(CancellationToken) ) { using (_stateHelper.GetFuncWrapper()) { try { Assert.True(SamHelper.IsDestinationStringValid(destination)); Assert.NotNull(data); Assert.InRange(data.Length, 1, _settings.MaxMesageLength); if (messageId == 0) messageId = await GetNextOutMessageId().ConfigureAwait(false); DestinationInfo destinationInfo; using (await _destinationDbLockSem.GetDisposable(token: token).ConfigureAwait(false)) { if (!_destinationDb.ContainsKey(destination)) _destinationDb.Add(destination, new DestinationInfo()); destinationInfo = _destinationDb[destination]; } OutMessageInfo outMessageInfo; using (await destinationInfo.OutMessagesDbLockSem.GetDisposable(token: token).ConfigureAwait(false)) { if ( destinationInfo.OutMessagesDb.ContainsKey(messageId) ) throw new EnumException<SendReliableMessageExcs>( SendReliableMessageExcs.ExistMessageId ); outMessageInfo = new OutMessageInfo( data, replyToMessageId, _settings.MaxBlockSize, progress ); destinationInfo.OutMessagesDb.Add(messageId, outMessageInfo); } try { uint blocksCount = outMessageInfo.BlockCount; /* * Handshake */ using ( var compositeTokenSource = CancellationTokenSource.CreateLinkedTokenSource( _cts.Token, token ) ) { try { var messageStatusTask = MessageStatusReceived .Where( x => x.Destination == destination && x.MessageId == messageId ) .FirstAsync() .ToTask(compositeTokenSource.Token); byte[] messageHash = GetMessageHash( messageId, replyToMessageId, messageKind, data ); for ( outMessageInfo.HandshakeAttemptNo = 0; outMessageInfo.HandshakeAttemptNo <= _settings.HandshakeAttemptCount; outMessageInfo.HandshakeAttemptNo++ ) { if ( outMessageInfo.HandshakeAttemptNo == _settings.HandshakeAttemptCount ) throw new EnumException<SendReliableMessageExcs>( SendReliableMessageExcs.HandshakeTimeout ); await SendHandshakeStart( destination, messageId, replyToMessageId, (uint)data.Length, outMessageInfo.BlockSize, messageHash, outMessageInfo.Data[0], messageKind ).ConfigureAwait(false); var waitTasks = await Task.WhenAny( messageStatusTask, Task.Delay( TimeSpan.FromSeconds( _settings.HandshakeTimeoutSeconds ), compositeTokenSource.Token ) ).ConfigureAwait(false); if (waitTasks == messageStatusTask) { var messageStatus = await messageStatusTask.ConfigureAwait(false); if ( !( messageStatus.StatusCode == MessageStatusArgs.MessageStatusCode.HandshakeOk || ( messageStatus.StatusCode == MessageStatusArgs.MessageStatusCode.AllBlocksReceived && outMessageInfo.BlockCount == 1 ) ) ) { throw new EnumException<SendReliableMessageExcs>( SendReliableMessageExcs.HandshakeError, messageStatus.WriteObjectToJson() ); } break; } } outMessageInfo.Status = OutMessageStatus.HandshakeOk; outMessageInfo.BlockConfirmed[0] = true; if (outMessageInfo.BlockCount > 1) { // Handshake established, start sending blocks DateTime lastBlockConfirmationTime = DateTime.UtcNow; var blocksInProgress = new SortedSet<uint>(); var blocksInProgressLockSem = new SemaphoreSlim(1); var blockSentTimes = new DateTime[blocksCount]; using ( BlockConfirmationReceived .Where( _ => _.Destination == destination && _.MessageId == messageId ) .ObserveOn(TaskPoolScheduler.Default) .Subscribe(async x => { if ( x.BlockId < blocksCount && !outMessageInfo.BlockConfirmed[x.BlockId] ) { await outMessageInfo.ConfirmBlock(x.BlockId).ConfigureAwait(false); lastBlockConfirmationTime = DateTime.UtcNow; using (await blocksInProgressLockSem.GetDisposable().ConfigureAwait(false)) { blocksInProgress.Remove(x.BlockId); } } } ) ) { while (true) { compositeTokenSource.Token.ThrowIfCancellationRequested(); IEnumerable<int> blocksToSend; using ( await blocksInProgressLockSem.GetDisposable(token: compositeTokenSource.Token) .ConfigureAwait(false)) { if ( blocksInProgress.Count == 0 && outMessageInfo.BlockConfirmed.All(x => x) ) break; blocksInProgress.RemoveWhere( i => DateTime.UtcNow > ( blockSentTimes[i] + TimeSpan.FromSeconds( _settings .ConfirmationOneBlockTimeoutSeconds ) ) ); blocksToSend = Enumerable.Range(0, (int)blocksCount) .Where( i => !blocksInProgress.Contains((uint)i) && !outMessageInfo.BlockConfirmed[i] ) .Take( Math.Max( 0, _settings.WindowSize - blocksInProgress.Count ) ); } foreach (int blockId in blocksToSend) { await SendBlock( destination, messageId, (uint)blockId ).ConfigureAwait(false); await outMessageInfo.SendBlock( (uint)blockId ).ConfigureAwait(false); using ( await blocksInProgressLockSem.GetDisposable(token: compositeTokenSource.Token) .ConfigureAwait(false)) { blocksInProgress.Add((uint)blockId); } blockSentTimes[blockId] = DateTime.UtcNow; } if ( DateTime.UtcNow > lastBlockConfirmationTime + TimeSpan.FromSeconds( _settings.ConfirmationTimeoutSeconds ) ) throw new EnumException<SendReliableMessageExcs>( SendReliableMessageExcs.SendBlocksTimeout ); await Task.Delay(50, compositeTokenSource.Token).ConfigureAwait(false); } } } outMessageInfo.Status = OutMessageStatus.AllBlocksSent; // all blocks sent and confirmed _log.Trace( "{4}, send reliable message of {0} bytes " + "to {1} id={2},rid={3},kind={5}", data.Length, destination.Substring(0, 20), messageId, replyToMessageId, _reliableSamHelperGuid.ToString().Substring(0, 5), messageKind ); return messageId; } finally { compositeTokenSource.Cancel(); } } } finally { TryRemoveOutMessageInfo(destinationInfo, messageId); } } catch (EnumException<SendReliableMessageExcs>) { throw; } catch (OperationCanceledException) { throw; } catch (TimeoutException) { throw; } catch (Exception exc) { throw new EnumException<SendReliableMessageExcs>( SendReliableMessageExcs.UnexpectedError, innerException: exc ); } } }