internal void manualAck(StanMsg m) { if (m == null) { return; } rwLock.EnterReadLock(); string localAckSubject = ackInbox; bool localManualAck = options.manualAcks; Connection sc = this.sc; rwLock.ExitReadLock(); if (localManualAck == false) { throw new StanManualAckException(); } if (sc == null) { throw new StanBadSubscriptionException(); } byte[] b = ProtocolSerializer.createAck(m.proto); sc.NATSConnection.Publish(localAckSubject, b); }
internal void processMsg(object sender, MsgHandlerEventArgs args) { bool isClosed = false; AsyncSubscription sub = null; Msg raw = null; MsgProto mp = new MsgProto(); ProtocolSerializer.unmarshal(args.Message.Data, mp); raw = args.Message; lock (mu) { isClosed = (nc == null); subMap.TryGetValue(raw.Subject, out sub); } if (isClosed || sub == null) { return; } sub.processMsg(mp); }
internal void unsubscribe(string subject, string ackInbox) { IConnection lnc; lock (mu) { lnc = this.nc; subMap.Remove(ackInbox); } UnsubscribeRequest usr = new UnsubscribeRequest(); usr.ClientID = clientID; usr.Subject = subject; usr.Inbox = ackInbox; byte[] b = ProtocolSerializer.marshal(usr); var r = lnc.Request(unsubRequests, b, 2000); SubscriptionResponse sr = new SubscriptionResponse(); ProtocolSerializer.unmarshal(r.Data, sr); if (!string.IsNullOrEmpty(sr.Error)) { throw new StanException(sr.Error); } }
private void processPingResponse(object sender, MsgHandlerEventArgs e) { // No data means OK (no need to unmarshall) var data = e.Message.Data; if (data?.Length > 0) { var pingResp = new PingResponse(); try { ProtocolSerializer.unmarshal(data, pingResp); } catch { // Ignore this as an invalid protocol message. return; } string err = pingResp.Error; if (err?.Length > 0) { closeDueToPing(new StanException(err)); } } lock (pingLock) { pingOut = 0; } }
public void Close() { Msg reply = null; lock (mu) { cleanupOnClose(null); if (nc == null || nc.IsClosed()) { nc = null; return; } CloseRequest req = new CloseRequest { ClientID = clientID }; try { if (closeRequests != null) { reply = nc.Request(closeRequests, ProtocolSerializer.marshal(req)); } } catch (StanBadSubscriptionException) { // it's possible we never actually connected. return; } if (reply != null) { CloseResponse resp = new CloseResponse(); try { ProtocolSerializer.unmarshal(reply.Data, resp); } catch (Exception e) { throw new StanCloseRequestException(e); } if (!string.IsNullOrEmpty(resp.Error)) { throw new StanCloseRequestException(resp.Error); } } if (ncOwned) { nc.Close(); } nc = null; } }
internal PublishAck publish(string subject, byte[] data, EventHandler <StanAckHandlerArgs> handler) { string localAckSubject = null; long localAckTimeout = 0; string subj = this.pubPrefix + "." + subject; string guidValue = newGUID(); byte[] b = ProtocolSerializer.createPubMsg(clientID, guidValue, subject, data); PublishAck a = new PublishAck(this, guidValue, handler, opts.PubAckWait); lock (mu) { if (nc == null) { throw new StanConnectionClosedException(); } if (pubAckMap.isAtCapacity()) { var bd = pubAckMap; Monitor.Exit(mu); // Wait for space outside of the lock so // acks can be removed. bd.waitForSpace(); Monitor.Enter(mu); if (nc == null) { throw new StanConnectionClosedException(); } } pubAckMap.Add(guidValue, a); localAckSubject = ackSubject; localAckTimeout = opts.ackTimeout; } try { nc.Publish(subj, localAckSubject, b); } catch (Exception e) { removeAck(guidValue); throw e; } return(a); }
internal void unsubscribe(string subject, string inbox, string ackInbox, bool close) { IConnection lnc; lock (mu) { lnc = nc; if (lnc == null) { throw new StanConnectionClosedException(); } subMap.Remove(inbox); } string requestSubject = unsubRequests; if (close) { requestSubject = subCloseRequests; if (string.IsNullOrEmpty(requestSubject)) { throw new StanNoServerSupport(); } } UnsubscribeRequest usr = new UnsubscribeRequest { ClientID = clientID, Subject = subject, Inbox = ackInbox }; byte[] b = ProtocolSerializer.marshal(usr); var r = lnc.Request(requestSubject, b, 2000); SubscriptionResponse sr = new SubscriptionResponse(); ProtocolSerializer.unmarshal(r.Data, sr); if (!string.IsNullOrEmpty(sr.Error)) { throw new StanException(sr.Error); } }
private void processAck(object sender, MsgHandlerEventArgs args) { PubAck pa = new PubAck(); try { ProtocolSerializer.unmarshal(args.Message.Data, pa); } catch (Exception) { // TODO: (cls) handle this... return; } PublishAck a = removeAck(pa.Guid); if (a != null) { a.InvokeHandler(pa.Guid, pa.Error); } }
internal void processMsg(MsgProto mp) { rwLock.EnterReadLock(); EventHandler <StanMsgHandlerArgs> cb = handler; bool isManualAck = options.manualAcks; string localAckSubject = ackInbox; IStanConnection subsSc = sc; IConnection localNc = null; if (subsSc != null) { localNc = sc.NATSConnection; } rwLock.ExitReadLock(); if (cb != null && subsSc != null) { StanMsgHandlerArgs args = new StanMsgHandlerArgs(new StanMsg(mp, this)); cb(this, args); } if (!isManualAck && localNc != null) { byte[] b = ProtocolSerializer.createAck(mp); try { localNc.Publish(localAckSubject, b); } catch (Exception) { /* * Ignore - subscriber could have closed the connection * or there's been a connection error. The server will * resend the unacknowledged messages. */ } } }
private void processPingResponse(object sender, MsgHandlerEventArgs args) { if (IsNoResponseMsg(args.Message)) { return; } // No data can be a valid message var data = args.Message.Data; if (data?.Length > 0) { var pingResp = new PingResponse(); try { ProtocolSerializer.unmarshal(data, pingResp); } catch { // Ignore this as an invalid protocol message. return; } string err = pingResp.Error; if (err?.Length > 0) { closeDueToPing(new StanException(err)); return; } } lock (pingLock) { pingOut = 0; } }
internal PublishAck publish(string subject, byte[] data, EventHandler <StanAckHandlerArgs> handler) { string localAckSubject = null; long localAckTimeout = 0; string subj = this.pubPrefix + "." + subject; string guidValue = newGUID(); byte[] b = ProtocolSerializer.createPubMsg(clientID, guidValue, subject, data, connID); PublishAck a = null; lock (mu) { if (nc == null) { throw new StanConnectionClosedException(); } if (nc.IsReconnecting()) { throw new StanConnectionException("The NATS connection is reconnecting"); } a = new PublishAck(this, guidValue, handler, opts.PubAckWait); while (!pubAckMap.TryAdd(guidValue, a)) { var bd = pubAckMap; Monitor.Exit(mu); // Wait for space outside of the lock so // acks can be removed. bd.waitForSpace(); Monitor.Enter(mu); if (nc == null) { throw new StanConnectionClosedException(); } } localAckSubject = ackSubject; localAckTimeout = opts.ackTimeout; } try { nc.Publish(subj, localAckSubject, b); // Flush to reduce latency. // // TODO: Add a soft (non-ping) flush to NATS.net // for this type of situation. Only flush in // blocking publish calls. if (handler == null) { nc.Flush(); } } catch { removeAck(guidValue); throw; } return(a); }
public void Close() { Msg reply = null; lock (mu) { cleanupOnClose(null); if (nc == null || nc.IsClosed()) { nc = null; return; } CloseRequest req = new CloseRequest { ClientID = clientID }; try { if (closeRequests != null) { reply = nc.Request(closeRequests, ProtocolSerializer.marshal(req)); } } catch (StanBadSubscriptionException) { // it's possible we never actually connected. return; } catch (NATSReconnectBufferException) { // In order to maintain backward compatibility, we need to avoid throwing // this exception. The reply will be null, so we'll fall through // and gracefully close the streaming connection. The streaming server // will cleanup this client on its own. } if (reply != null) { CloseResponse resp = new CloseResponse(); try { ProtocolSerializer.unmarshal(reply.Data, resp); } catch (Exception e) { throw new StanCloseRequestException(e); } if (!string.IsNullOrEmpty(resp.Error)) { throw new StanCloseRequestException(resp.Error); } } if (ncOwned) { nc.Close(); } nc = null; } }
internal Connection(string stanClusterID, string clientID, StanOptions options) { this.clientID = clientID; if (options != null) { opts = new StanOptions(options); } else { opts = new StanOptions(); } if (opts.natsConn == null) { ncOwned = true; try { nc = new ConnectionFactory().CreateConnection(opts.NatsURL); } catch (Exception ex) { throw new StanConnectionException(ex); } } else { nc = opts.natsConn; ncOwned = false; } // create a heartbeat inbox string hbInbox = newInbox(); hbSubscription = nc.SubscribeAsync(hbInbox, processHeartBeat); string discoverSubject = opts.discoverPrefix + "." + stanClusterID; ConnectRequest req = new ConnectRequest(); req.ClientID = this.clientID; req.HeartbeatInbox = hbInbox; Msg cr; try { cr = nc.Request(discoverSubject, ProtocolSerializer.marshal(req), opts.ConnectTimeout); } catch (NATSTimeoutException) { throw new StanConnectRequestTimeoutException(); } ConnectResponse response = new ConnectResponse(); try { ProtocolSerializer.unmarshal(cr.Data, response); } catch (Exception e) { throw new StanConnectRequestException(e); } if (!string.IsNullOrEmpty(response.Error)) { throw new StanConnectRequestException(response.Error); } // capture cluster configuration endpoints to publish and subscribe/unsubscribe pubPrefix = response.PubPrefix; subRequests = response.SubRequests; unsubRequests = response.UnsubRequests; subCloseRequests = response.SubCloseRequests; closeRequests = response.CloseRequests; // setup the Ack subscription ackSubject = StanConsts.DefaultACKPrefix + "." + newGUID(); ackSubscription = nc.SubscribeAsync(ackSubject, processAck); // TODO: hardcode or options? ackSubscription.SetPendingLimits(1024 * 1024, 32 * 1024 * 1024); pubAckMap = new BlockingDictionary <string, PublishAck>(opts.maxPubAcksInflight); }
public void Close() { Msg reply = null; lock (mu) { IConnection lnc = nc; nc = null; if (lnc == null) { return; } if (lnc.IsClosed()) { return; } if (ackSubscription != null) { ackSubscription.Unsubscribe(); ackSubscription = null; } if (hbSubscription != null) { hbSubscription.Unsubscribe(); hbSubscription = null; } CloseRequest req = new CloseRequest(); req.ClientID = this.clientID; try { if (this.closeRequests != null) { reply = lnc.Request(closeRequests, ProtocolSerializer.marshal(req)); } } catch (StanBadSubscriptionException) { // it's possible we never actually connected. return; } if (reply != null) { CloseResponse resp = new CloseResponse(); try { ProtocolSerializer.unmarshal(reply.Data, resp); } catch (Exception e) { throw new StanCloseRequestException(e); } if (!string.IsNullOrEmpty(resp.Error)) { throw new StanCloseRequestException(resp.Error); } } if (ncOwned && lnc != null) { lnc.Close(); } } }
internal PublishAck publish(string subject, byte[] data, EventHandler <StanAckHandlerArgs> handler) { string localAckSubject = null; string subj = this.pubPrefix + "." + subject; string guidValue = newGUID(); byte[] b = ProtocolSerializer.createPubMsg(clientID, guidValue, subject, data, connID); PublishAck a = null; lock (mu) { if (nc == null || nc.IsClosed()) { throw new StanConnectionClosedException(); } if (nc.IsReconnecting()) { throw new StanConnectionException("The NATS connection is reconnecting"); } a = new PublishAck(this, guidValue, handler, opts.PubAckWait); int pingInterval = opts.PingInterval; while (!pubAckMap.TryAdd(guidValue, a)) { Monitor.Exit(mu); // Wait for space outside of the lock so // acks can be removed and other executive // functions can continue pubAckMap.TryWaitForSpace(pingInterval); Monitor.Enter(mu); if (nc == null || nc.IsClosed()) { throw new StanConnectionClosedException(); } if (nc.IsReconnecting()) { throw new StanConnectionException("The NATS connection is reconnecting"); } } localAckSubject = ackSubject; } try { nc.Publish(subj, localAckSubject, b); // Flush to reduce latency. if (handler == null) { nc.FlushBuffer(); } } catch { removeAck(guidValue); throw; } return(a); }
internal Connection(string stanClusterID, string clientID, StanOptions options) { this.clientID = clientID; connID = Google.Protobuf.ByteString.CopyFrom(System.Text.Encoding.UTF8.GetBytes(pubNUID.Next)); opts = (options != null) ? new StanOptions(options) : new StanOptions(); if (opts.natsConn == null) { ncOwned = true; try { nc = new ConnectionFactory().CreateConnection(opts.NatsURL); nc.Opts.MaxReconnect = Options.ReconnectForever; // TODO: disable buffering. } catch (Exception ex) { throw new StanConnectionException(ex); } } else { nc = opts.natsConn; ncOwned = false; } // Prepare a subscription on ping responses, even if we are not // going to need it, so that if that fails, it fails before initiating // a connection. pingSubscription = nc.SubscribeAsync(newInbox(), processPingResponse); // create a heartbeat inbox string hbInbox = newInbox(); hbSubscription = nc.SubscribeAsync(hbInbox, processHeartBeat); string discoverSubject = opts.discoverPrefix + "." + stanClusterID; // The streaming server expects seconds, but can handle millis // millis are denoted by negative numbers. int pi; if (opts.PingInterval < 1000) { pi = opts.pingInterval * -1; } else { pi = opts.pingInterval / 1000; } ConnectRequest req = new ConnectRequest { ClientID = clientID, HeartbeatInbox = hbInbox, ConnID = (Google.Protobuf.ByteString)connID, Protocol = StanConsts.protocolOne, PingMaxOut = opts.PingMaxOutstanding, PingInterval = pi }; Msg cr; try { cr = nc.Request(discoverSubject, ProtocolSerializer.marshal(req), opts.ConnectTimeout); } catch (NATSTimeoutException) { protoUnsubscribe(); throw new StanConnectRequestTimeoutException( string.Format("No response from a streaming server with a cluster ID of '{0}'", stanClusterID)); } ConnectResponse response = new ConnectResponse(); try { ProtocolSerializer.unmarshal(cr.Data, response); } catch (Exception e) { protoUnsubscribe(); throw new StanConnectRequestException(e); } if (!string.IsNullOrEmpty(response.Error)) { protoUnsubscribe(); throw new StanConnectRequestException(response.Error); } // capture cluster configuration endpoints to publish and subscribe/unsubscribe pubPrefix = response.PubPrefix; subRequests = response.SubRequests; unsubRequests = response.UnsubRequests; subCloseRequests = response.SubCloseRequests; closeRequests = response.CloseRequests; // setup the Ack subscription ackSubject = StanConsts.DefaultACKPrefix + "." + newGUID(); ackSubscription = nc.SubscribeAsync(ackSubject, processAck); // TODO: hardcode or options? ackSubscription.SetPendingLimits(1024 * 1024, 32 * 1024 * 1024); pubAckMap = new BlockingDictionary <string, PublishAck>(opts.maxPubAcksInflight); // TODO - check out sub map and chans bool unsubPing = true; // Do this with servers which are at least at protcolOne. if (response.Protocol >= StanConsts.protocolOne) { // Note that in the future server may override client ping // interval value sent in ConnectRequest, so use the // value in ConnectResponse to decide if we send PINGs // and at what interval. // In tests, the interval could be negative to indicate // milliseconds. if (response.PingInterval != 0) { unsubPing = false; // These will be immutable pingRequests = response.PingRequests; pingInbox = pingSubscription.Subject; // negative values returned from the server are ms if (response.PingInterval < 0) { pingInterval = response.PingInterval * -1; } else { // if positive, the result is in seconds, but // in the .NET clients we always use millis. pingInterval = response.PingInterval * 1000; } pingMaxOut = response.PingMaxOut; pingBytes = ProtocolSerializer.createPing(connID); pingTimer = new Timer(pingServer, null, pingInterval, Timeout.Infinite); } } if (unsubPing) { pingSubscription.Unsubscribe(); pingSubscription = null; } }
// in STAN, much of this code is in the connection module. internal void subscribe(string subRequestSubject, string subject, string qgroup, EventHandler <StanMsgHandlerArgs> handler) { rwLock.EnterWriteLock(); try { this.handler += handler; this.subject = subject; if (sc == null) { throw new StanConnectionClosedException(); } // Listen for actual messages. inboxSub = sc.NATSConnection.SubscribeAsync(inbox, sc.processMsg); SubscriptionRequest sr = new SubscriptionRequest(); sr.ClientID = sc.ClientID; sr.Subject = subject; sr.QGroup = (qgroup == null ? "" : qgroup); sr.Inbox = inbox; sr.MaxInFlight = options.MaxInflight; sr.AckWaitInSecs = options.AckWait / 1000; sr.StartPosition = options.startAt; sr.DurableName = (options.DurableName == null ? "" : options.DurableName); // Conditionals switch (sr.StartPosition) { case StartPosition.TimeDeltaStart: sr.StartTimeDelta = convertTimeSpan( options.useStartTimeDelta ? options.startTimeDelta : (DateTime.UtcNow - options.startTime)); break; case StartPosition.SequenceStart: sr.StartSequence = options.startSequence; break; } byte[] b = ProtocolSerializer.marshal(sr); Msg m = sc.NATSConnection.Request(subRequestSubject, b, sc.opts.ConnectTimeout); SubscriptionResponse r = new SubscriptionResponse(); ProtocolSerializer.unmarshal(m.Data, r); if (string.IsNullOrWhiteSpace(r.Error) == false) { throw new StanException(r.Error); } ackInbox = r.AckInbox; } catch { if (inboxSub != null) { try { inboxSub.Unsubscribe(); } catch (NATSTimeoutException) { // NOOP - this is unrecoverable. } } throw; } finally { rwLock.ExitWriteLock(); } }