public HeartbeatTimer(PushMessageManager pushMm) { timer = new Timer(s => { long sinceLast = DateTimeOffset.Now.ToUnixTimeMilliseconds() - pushMm.LastMsgReceived; if (sinceLast > pushMm.AlarmPeriodSetting) { pushMm.conn.Opts.HeartbeatAlarmEventHandlerOrDefault.Invoke(this, new HeartbeatAlarmEventArgs(pushMm.conn, pushMm.Sub, pushMm.LastStreamSeq, pushMm.LastConsumerSeq)); } }, null, pushMm.AlarmPeriodSetting, pushMm.AlarmPeriodSetting); }
Subscription CreateSubscription(string subject, string queueName, EventHandler <MsgHandlerEventArgs> userHandler, bool autoAck, PushSubscribeOptions pushSubscribeOptions, PullSubscribeOptions pullSubscribeOptions) { // 1. Prepare for all the validation bool isPullMode = pullSubscribeOptions != null; SubscribeOptions so; string stream; string qgroup; ConsumerConfiguration userCC; if (isPullMode) { so = pullSubscribeOptions; // options must have already been checked to be non null stream = pullSubscribeOptions.Stream; userCC = so.ConsumerConfiguration; qgroup = null; // just to make compiler happy both paths set variable ValidateNotSupplied(userCC.DeliverGroup, JsSubPullCantHaveDeliverGroup); ValidateNotSupplied(userCC.DeliverSubject, JsSubPullCantHaveDeliverSubject); } else { so = pushSubscribeOptions ?? DefaultPushOpts; stream = so.Stream; // might be null, that's ok (see directBind) userCC = so.ConsumerConfiguration; if (!userCC.MaxPullWaiting.Equals(ConsumerConfiguration.IntUnset)) { throw JsSubPushCantHaveMaxPullWaiting.Instance(); } if (!userCC.MaxBatch.Equals(ConsumerConfiguration.IntUnset)) { throw JsSubPushCantHaveMaxBatch.Instance(); } if (!userCC.MaxBytes.Equals(ConsumerConfiguration.IntUnset)) { throw JsSubPushCantHaveMaxBytes.Instance(); } // figure out the queue name qgroup = ValidateMustMatchIfBothSupplied(userCC.DeliverGroup, queueName, JsSubQueueDeliverGroupMismatch); if (so.Ordered && qgroup != null) { throw JsSubOrderedNotAllowOnQueues.Instance(); } } // 2A. Flow Control / heartbeat not always valid if (userCC.FlowControl || userCC.IdleHeartbeat != null && userCC.IdleHeartbeat.Millis > 0) { if (isPullMode) { throw JsSubFcHbNotValidPull.Instance(); } if (qgroup != null) { throw JsSubFcHbHbNotValidQueue.Instance(); } } // 2B. Did they tell me what stream? No? look it up. if (string.IsNullOrWhiteSpace(stream)) { stream = LookupStreamBySubject(subject); if (stream == null) { throw JsSubNoMatchingStreamForSubject.Instance(); } } ConsumerConfiguration serverCC = null; string consumerName = userCC.Durable; string inboxDeliver = userCC.DeliverSubject; // 3. Does this consumer already exist? if (consumerName != null) { ConsumerInfo serverInfo = LookupConsumerInfo(stream, consumerName); if (serverInfo != null) // the consumer for that durable already exists { serverCC = serverInfo.ConsumerConfiguration; // check to see if the user sent a different version than the server has // modifications are not allowed IList <string> changes = userCC.GetChanges(serverCC); if (changes.Count > 0) { throw JsSubExistingConsumerCannotBeModified.Instance($"[{string.Join(",", changes)}]"); } if (isPullMode) { if (!string.IsNullOrWhiteSpace(serverCC.DeliverSubject)) { throw JsSubConsumerAlreadyConfiguredAsPush.Instance(); } } else if (string.IsNullOrWhiteSpace(serverCC.DeliverSubject)) { throw JsSubConsumerAlreadyConfiguredAsPull.Instance(); } if (string.IsNullOrWhiteSpace(serverCC.DeliverGroup)) { // lookedUp was null/empty, means existing consumer is not a queue consumer if (qgroup == null) { // ok fine, no queue requested and the existing consumer is also not a queue consumer // we must check if the consumer is in use though if (serverInfo.PushBound) { throw JsSubConsumerAlreadyBound.Instance(); } } else // else they requested a queue but this durable was not configured as queue { throw JsSubExistingConsumerNotQueue.Instance(); } } else if (qgroup == null) { throw JsSubExistingConsumerIsQueue.Instance(); } else if (!serverCC.DeliverGroup.Equals(qgroup)) { throw JsSubExistingQueueDoesNotMatchRequestedQueue.Instance(); } // durable already exists, make sure the filter subject matches if (string.IsNullOrWhiteSpace(subject)) { subject = userCC.FilterSubject; } else if (!IsFilterMatch(subject, serverCC.FilterSubject, stream)) { throw JsSubSubjectDoesNotMatchFilter.Instance(); } inboxDeliver = serverCC.DeliverSubject; // use the deliver subject as the inbox. It may be null, that's ok, we'll fix that later } else if (so.Bind) { throw JsSubConsumerNotFoundRequiredInBind.Instance(); } } // 4. If no deliver subject (inbox) provided or found, make an inbox. if (string.IsNullOrWhiteSpace(inboxDeliver)) { inboxDeliver = Conn.NewInbox(); } // 5. If consumer does not exist, create and settle on the config. Name will have to wait // If the consumer exists, I know what the settled info is if (serverCC == null) { ConsumerConfiguration.ConsumerConfigurationBuilder ccBuilder = ConsumerConfiguration.Builder(userCC); // Pull mode doesn't maintain a deliver subject. It's actually an error if we send it. if (!isPullMode) { ccBuilder.WithDeliverSubject(inboxDeliver); } if (string.IsNullOrWhiteSpace(userCC.FilterSubject)) { ccBuilder.WithFilterSubject(subject); } if (string.IsNullOrWhiteSpace(userCC.DeliverGroup) && !string.IsNullOrWhiteSpace(qgroup)) { ccBuilder.WithDeliverGroup(qgroup); } // createOrUpdateConsumer can fail for security reasons, maybe other reasons? serverCC = ccBuilder.Build(); consumerName = null; } // 6. create the subscription Subscription sub; if (isPullMode) { MessageManager[] managers = { new PullMessageManager() }; SyncSubscription CreateSubDelegate(Connection lConn, string lSubject, string lQueueNa) { return(new JetStreamPullSubscription(lConn, lSubject, this, stream, consumerName, inboxDeliver, managers)); } sub = ((Connection)Conn).subscribeSync(inboxDeliver, queueName, CreateSubDelegate); } else { bool syncMode = userHandler == null; PushMessageManager pushMessageManager = PushMessageManagerFactoryImpl((Connection)Conn, so, serverCC, qgroup != null, syncMode); MessageManager[] managers; if (so.Ordered) { managers = new MessageManager[] { new SidCheckManager(), pushMessageManager, new OrderedMessageManager(this, stream, serverCC, syncMode) }; } else { managers = new MessageManager[] { pushMessageManager }; } if (syncMode) { SyncSubscription CreateSubDelegate(Connection lConn, string lSubject, string lQueue) { return(new JetStreamPushSyncSubscription(lConn, lSubject, lQueue, this, stream, consumerName, inboxDeliver, managers)); } sub = ((Connection)Conn).subscribeSync(inboxDeliver, queueName, CreateSubDelegate); } else { EventHandler <MsgHandlerEventArgs> autoAckHandler; if (autoAck && serverCC.AckPolicy != AckPolicy.None) { autoAckHandler = (sender, args) => args.Message.Ack(); } else { autoAckHandler = (sender, args) => {}; } EventHandler <MsgHandlerEventArgs> handler = (sender, args) => { foreach (MessageManager mm in managers) { if (mm.Manage(args.Message)) { return; // manager handled the message } } userHandler.Invoke(sender, args); autoAckHandler.Invoke(sender, args); }; AsyncSubscription CreateAsyncSubDelegate(Connection lConn, string lSubject, string lQueue) { return(new JetStreamPushAsyncSubscription(lConn, lSubject, lQueue, this, stream, consumerName, inboxDeliver, managers)); } sub = ((Connection)Conn).subscribeAsync(inboxDeliver, queueName, handler, CreateAsyncSubDelegate); } } // 7. The consumer might need to be created, do it here if (consumerName == null) { try { ConsumerInfo ci = AddOrUpdateConsumerInternal(stream, serverCC); if (sub is JetStreamAbstractSyncSubscription syncSub) { syncSub.Consumer = ci.Name; } else if (sub is JetStreamPushAsyncSubscription asyncSub) { asyncSub.Consumer = ci.Name; } } catch { // create consumer can fail, unsubscribe and then throw the exception to the user sub.Unsubscribe(); throw; } } return(sub); }