private static Memory <char> ToChars(Stream workspace) { if (workspace.Position == 0) { return(Memory <char> .Empty); } var sourceSpan = new Span <byte>(new byte[workspace.Position]); workspace.Position = 0; if (workspace.Read(sourceSpan) == -1) { throw NatsException.OpParserError("Error while trying to read from workspace stream."); } var result = new Memory <char>(new char[sourceSpan.Length]); var resultSpan = result.Span; for (var i = 0; i < sourceSpan.Length; i++) { resultSpan[i] = (char)sourceSpan[i]; } return(result); }
private void ThrowIfNotConnected() { if (!IsConnected) { throw NatsException.NotConnected(); } }
private static ReadOnlyMemory <byte> ReadPayload(Stream source, int payloadSize) { if (payloadSize == 0) { return(ReadOnlyMemory <byte> .Empty); } var payload = new Memory <byte>(new byte[payloadSize]); var consumed = 0; while (true) { var read = source.Read(payload.Slice(consumed).Span); if (read < 1) { throw NatsException.OpParserOpParsingError(MsgOp.Name, "Could not read payload from stream."); } consumed += read; if (consumed == payloadSize) { break; } if (consumed > payloadSize) { throw NatsException.OpParserOpParsingError(MsgOp.Name, "Read to many bytes for the payload."); } } return(payload); }
private static ErrOp ParseErrorOp(Stream source, Stream workspace) { while (true) { var b = (byte)source.ReadByte(); if (b == Cr) { b = (byte)source.ReadByte(); if (b != Lf) { throw NatsException.OpParserOpParsingError(ErrOp.Name, Lf, b); } break; } workspace.WriteByte(b); } var buff = new Memory <byte>(new byte[workspace.Position]); workspace.Position = 0; workspace.Read(buff.Span); return(new ErrOp(string.Create(buff.Length, buff, (t, s) => { var x = s.Span; for (var i = 0; i < t.Length; i++) { t[i] = (char)x[i]; } }))); }
private static PingOp ParsePingOp(Stream source) { var c = (byte)source.ReadByte(); if (c != Lf) { throw NatsException.OpParserOpParsingError(PingOp.Name, Lf, c); } return(PingOp.Instance); }
public Task <MsgOp> RequestAsync(string subject, ReadOnlyMemory <byte> body, CancellationToken cancellationToken = default) { ThrowIfDisposed(); if (body.Length > _connection.ServerInfo.MaxPayload) { throw NatsException.ExceededMaxPayload(_connection.ServerInfo.MaxPayload, body.Length); } return(_connectionInfo.UseInboxRequests ? DoRequestUsingInboxAsync(subject, body, cancellationToken) : DoRequestAsync(subject, body, cancellationToken)); }
private async Task <MsgOp> DoRequestAsync(string subject, byte[] body, int?timeoutMs) { var requestReplyAddress = $"{Guid.NewGuid():N}"; var pubCmd = PubCmd.Generate(subject, body, requestReplyAddress); var taskComp = new TaskCompletionSource <MsgOp>(); var requestSubscription = MsgOpStream.Where(msg => msg.Subject == requestReplyAddress).Subscribe( msg => taskComp.SetResult(msg), ex => taskComp.SetException(ex)); var subscriptionInfo = new SubscriptionInfo(requestReplyAddress, maxMessages: 1); var subCmd = SubCmd.Generate(subscriptionInfo.Subject, subscriptionInfo.Id); var unsubCmd = UnsubCmd.Generate(subscriptionInfo.Id, subscriptionInfo.MaxMessages); await _connection.WithWriteLockAsync(async writer => { await writer.WriteAsync(subCmd).ConfigureAwait(false); await writer.WriteAsync(unsubCmd).ConfigureAwait(false); await writer.FlushAsync().ConfigureAwait(false); await writer.WriteAsync(pubCmd).ConfigureAwait(false); await writer.FlushAsync().ConfigureAwait(false); }).ConfigureAwait(false); Task.WaitAny(new[] { Task.Delay(timeoutMs ?? _connectionInfo.RequestTimeoutMs), taskComp.Task }, _cancellation.Token); if (!taskComp.Task.IsCompleted) { taskComp.SetException(NatsException.RequestTimedOut()); } return(await taskComp.Task .ContinueWith(t => { requestSubscription?.Dispose(); if (!t.IsFaulted) { return t.Result; } var ex = t.Exception?.GetBaseException() ?? t.Exception; if (ex == null) { return t.Result; } _logger.Error("Exception while performing request.", ex); throw ex; }) .ConfigureAwait(false)); }
private Subscription CreateSubscription(SubscriptionInfo subscriptionInfo, Func <INatsObservable <MsgOp>, IDisposable> subscriptionFactory) { var subscription = Subscription.Create( subscriptionInfo, subscriptionFactory(MsgOpStream.Where(msg => msg.SubscriptionId == subscriptionInfo.Id)), DisposeSubscription); if (!_subscriptions.TryAdd(subscription.SubscriptionInfo.Id, subscription)) { throw NatsException.CouldNotCreateSubscription(subscription.SubscriptionInfo); } return(subscription); }
private Subscription CreateMsgOpSubscription(SubscriptionInfo subscriptionInfo, Func <INatsObservable <MsgOp>, IDisposable> subscriptionFactory) { var subscription = Subscription.Create( subscriptionInfo, subscriptionFactory(MsgOpStream.Where(msg => subscriptionInfo.Matches(msg.Subject))), info => Swallow.Everything(() => Unsub(info))); if (!_subscriptions.TryAdd(subscription.SubscriptionInfo.Id, subscription)) { throw NatsException.CouldNotCreateSubscription(subscription.SubscriptionInfo); } return(subscription); }
private async Task <MsgOp> DoRequestUsingInboxAsync(string subject, ReadOnlyMemory <byte> body, CancellationToken cancellationToken = default) { var requestId = UniqueId.Generate(); var replyToSubject = $"{_inboxAddress}.{requestId}"; var taskComp = new TaskCompletionSource <MsgOp>(); if (!_outstandingRequests.TryAdd(requestId, taskComp)) { throw NatsException.InitRequestError("Unable to initiate request."); } using var cts = cancellationToken == default ? new CancellationTokenSource(_connectionInfo.RequestTimeoutMs) : CancellationTokenSource.CreateLinkedTokenSource(_cancellation.Token, cancellationToken); await using var _ = cts.Token.Register(() => { if (_outstandingRequests.TryRemove(requestId, out var ts)) { ts.TrySetCanceled(); } }).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); await _connection.WithWriteLockAsync(async (writer, arg) => { var(subjectIn, bodyIn, replyToIn) = arg; if (_inboxSubscription == null) { SetupInboxSubscription(); await SubCmd.WriteAsync( writer, _inboxSubscription.SubscriptionInfo.Subject.AsMemory(), _inboxSubscription.SubscriptionInfo.Id.AsMemory(), _inboxSubscription.SubscriptionInfo.QueueGroup.AsMemory()).ConfigureAwait(false); } await PubCmd.WriteAsync(writer, subjectIn, replyToIn, bodyIn).ConfigureAwait(false); await writer.FlushAsync().ConfigureAwait(false); }, Tuple.Create(subject.AsMemory(), body, replyToSubject.AsMemory())).ConfigureAwait(false); return(await taskComp.Task.ConfigureAwait(false)); }
public async Task PubAsync(string subject, ReadOnlyMemory <byte> body, string replyTo = null) { ThrowIfDisposed(); if (body.Length > _connection.ServerInfo.MaxPayload) { throw NatsException.ExceededMaxPayload(_connection.ServerInfo.MaxPayload, body.Length); } await _connection.WithWriteLockAsync(async (writer, arg) => { var(subjectIn, bodyIn, replyToIn) = arg; await PubCmd.WriteAsync(writer, subjectIn, replyToIn, bodyIn).ConfigureAwait(false); if (ShouldAutoFlush) { await writer.FlushAsync().ConfigureAwait(false); } }, Tuple.Create(subject.AsMemory(), body, replyTo.AsMemory())).ConfigureAwait(false); }
public void Pub(string subject, ReadOnlyMemory <byte> body, string replyTo = null) { ThrowIfDisposed(); if (body.Length > _connection.ServerInfo.MaxPayload) { throw NatsException.ExceededMaxPayload(_connection.ServerInfo.MaxPayload, body.Length); } _connection.WithWriteLock((writer, arg) => { var(subjectIn, bodyIn, replyToIn) = arg; PubCmd.Write(writer, subjectIn.Span, replyToIn.Span, bodyIn); if (ShouldAutoFlush) { writer.Flush(); } }, Tuple.Create(subject.AsMemory(), body, replyTo.AsMemory())); }
private static InfoOp ParseInfoOp(Stream source, Stream workspace) { while (true) { var b = (byte)source.ReadByte(); if (b == Cr) { b = (byte)source.ReadByte(); if (b != Lf) { throw NatsException.OpParserOpParsingError(InfoOp.Name, Lf, b); } break; } workspace.WriteByte(b); } var m = ToChars(workspace); return(new InfoOp(m)); }
private void Worker() { bool ShouldDoWork() => !_isDisposed && IsConnected && _cancellation?.IsCancellationRequested == false; var lastOpReceivedAt = DateTime.UtcNow; var ping = false; while (ShouldDoWork()) { try { if (ping) { ping = false; _logger.Debug("Pinging due to silent server."); Ping(); } foreach (var op in _connection.ReadOp()) { lastOpReceivedAt = DateTime.UtcNow; _opMediator.Emit(op); switch (op) { case PingOp _ when _connectionInfo.AutoRespondToPing && ShouldDoWork(): Pong(); break; case ErrOp errOp: throw NatsException.ClientReceivedErrOp(errOp); } } } catch (NatsException nex) when(nex.ExceptionCode == NatsExceptionCodes.OpParserError) { throw; } catch (Exception ex) { if (!ShouldDoWork()) { break; } _logger.Error("Worker got Exception.", ex); if (ex.InnerException is SocketException socketEx) { // ReSharper disable once HeapView.BoxingAllocation _logger.Error($"Worker task got SocketException with SocketErrorCode='{socketEx.SocketErrorCode}'"); if (socketEx.SocketErrorCode == SocketError.Interrupted) { break; } if (socketEx.SocketErrorCode != SocketError.TimedOut) { throw; } } var silenceDeltaMs = DateTime.UtcNow.Subtract(lastOpReceivedAt).TotalMilliseconds; if (silenceDeltaMs >= ConsumerMaxMsSilenceFromServer) { throw NatsException.ConnectionFoundIdling(_connection.ServerInfo.Host, _connection.ServerInfo.Port); } if (silenceDeltaMs >= ConsumerPingAfterMsSilenceFromServer) { ping = true; } } } }
private static MsgOp ParseMsgOp(Stream source, Stream workspace) { var sub = ReadOnlySpan <char> .Empty; var sid = ReadOnlySpan <char> .Empty; var replyTo = ReadOnlySpan <char> .Empty; int payloadSize; byte b; while (true) { b = (byte)source.ReadByte(); if (b == Cr) { payloadSize = int.Parse(ToChars(workspace).Span); b = (byte)source.ReadByte(); if (b != Lf) { throw NatsException.OpParserOpParsingError(MsgOp.Name, Lf, b); } break; } if (!IsDelimMarker(b)) { workspace.WriteByte(b); } else { if (sub.IsEmpty) { sub = ToChars(workspace).Span; workspace.Position = 0; continue; } if (sid.IsEmpty) { sid = ToChars(workspace).Span; workspace.Position = 0; continue; } if (replyTo.IsEmpty) { replyTo = ToChars(workspace).Span; workspace.Position = 0; continue; } throw NatsException.OpParserOpParsingError(MsgOp.Name, "Message does not conform to expected protocol format."); } } var payload = ReadPayload(source, payloadSize); b = (byte)source.ReadByte(); if (b != Cr) { throw NatsException.OpParserOpParsingError(MsgOp.Name, Cr, b); } b = (byte)source.ReadByte(); if (b != Lf) { throw NatsException.OpParserOpParsingError(MsgOp.Name, Lf, b); } return(new MsgOp( sub, sid, replyTo, payload)); }
public IOp ReadOneOp() { IOp op = null; using var workspace = new MemoryStream(); var opMarkerChars = new byte[4]; var i = -1; while (true) { var curr = _stream.ReadByte(); if (curr == -1) { return(null); } var c = (byte)curr; if (!IsDelimMarker(c) && c != Cr && c != Lf) { opMarkerChars[++i] = c; continue; } if (i == -1) { continue; } if (opMarkerChars[0] == M) { op = ParseMsgOp(_stream, workspace); } else if (opMarkerChars[0] == P) { if (opMarkerChars[1] == I) { op = ParsePingOp(_stream); } else if (opMarkerChars[1] == O) { op = ParsePongOp(_stream); } } else if (opMarkerChars[0] == I) { op = ParseInfoOp(_stream, workspace); } else if (opMarkerChars[0] == Plus) { op = ParseOkOp(_stream); } else if (opMarkerChars[0] == Minus) { op = ParseErrorOp(_stream, workspace); } if (op == null) { var opMarker = string.Create(i + 1, opMarkerChars, (t, v) => { if (t.Length == 4) { t[3] = (char)v[3]; } t[2] = (char)v[2]; t[1] = (char)v[1]; t[0] = (char)v[0]; }); throw NatsException.OpParserUnsupportedOp(opMarker); } i = -1; opMarkerChars[0] = EmptyOpMarker; opMarkerChars[1] = EmptyOpMarker; opMarkerChars[2] = EmptyOpMarker; opMarkerChars[3] = EmptyOpMarker; workspace.Position = 0; return(op); } }
private void Worker() { bool ShouldDoWork() => !_isDisposed && IsConnected && _cancellation?.IsCancellationRequested == false; while (ShouldDoWork()) { var lastOpReceivedAt = DateTime.UtcNow; try { foreach (var op in _connection.ReadOp()) { if (!ShouldDoWork()) { break; } lastOpReceivedAt = DateTime.UtcNow; _opMediator.Emit(op); if (op is PingOp && _connectionInfo.AutoRespondToPing) { Pong(); } if (op is ErrOp errOp) { throw NatsException.ClientReceivedErrOp(errOp); } } } catch (Exception ex) { if (!ShouldDoWork()) { break; } _logger.Error("Worker got Exception.", ex); if (ex.InnerException is SocketException socketEx) { _logger.Error($"Worker task got SocketException with SocketErrorCode='{socketEx.SocketErrorCode}'"); if (socketEx.SocketErrorCode == SocketError.Interrupted) { break; } if (socketEx.SocketErrorCode != SocketError.TimedOut) { throw; } } var silenceDeltaMs = DateTime.UtcNow.Subtract(lastOpReceivedAt).TotalMilliseconds; if (silenceDeltaMs >= ConsumerMaxMsSilenceFromServer) { throw NatsException.ConnectionFoundIdling(_connection.ServerInfo.Host, _connection.ServerInfo.Port); } if (silenceDeltaMs >= ConsumerPingAfterMsSilenceFromServer) { Ping(); } } } }