public T Receive() { // for reading, we give the impression to the client that we provide a single message at a time // which means we maintain a cursor and enumerator in the background and hide it from the caller if (_enumerator == null) { _enumerator = InitializeCursor(); } // there is no end when you need to sit and wait for messages to arrive while (true) { try { // do we have a message waiting? // this may block on the server for a few seconds but will return as soon as something is available if (_enumerator.MoveNext()) { // yes - record the current position and return it to the client _startedReading = true; _lastId = _enumerator.Current.Id; _position.Update(_positionQuery, Update.Set("last", _lastId), UpdateFlags.Upsert, SafeMode.False); var message = _enumerator.Current.Message; var delay = DateTime.UtcNow - _enumerator.Current.Enqueued; Log.Debug(m => m("Received {0} after {1}", _queueName, delay)); return(message); } if (!_startedReading) { // for an empty collection, we'll need to re-query to be notified of new records Log.Debug("Cursor Empty"); Thread.Sleep(100); _enumerator.Dispose(); _enumerator = InitializeCursor(); } else { // if the cursor is dead then we need to re-query, otherwise we just go back to iterating over it if (_enumerator.IsDead) { Log.Debug("Cursor Dead"); _enumerator.Dispose(); _enumerator = InitializeCursor(); } } } catch (Exception ex) { // cursor died or was killed _enumerator.Dispose(); _enumerator = InitializeCursor(); } } }
/// <summary> /// Receives an event (or action) from queue. Does not change the message state. Used for PubSub (1 to Many - all gets the same messages) use cases /// </summary> /// <returns></returns> private T ReceiveEvent() { if (_enumerator == null) { _enumerator = InitializeCursor(); } // running untill we have something to return while (true) { var t1 = DateTime.UtcNow; try { // do we have a message waiting? // this may block on the server for a few seconds but will return as soon as something is available if (_enumerator.MoveNext()) { // yes - record the current position and return it to the client Interlocked.Increment(ref _totalReceived); _lastId = _enumerator.Current.Id; var current = _enumerator.Current; var message = current.Message; var delay = DateTime.UtcNow - _enumerator.Current.Enqueued; Log.Debug(m => m("Received {0} after {1}", _queueName, delay)); return(message); } // if the cursor is dead then we need to re-query, otherwise we just go back to iterating over it if (_enumerator.IsDead) { Log.Debug("Cursor Dead"); _enumerator.Dispose(); _enumerator = InitializeCursor(); } // If we are here it means that the server returned without items, usually it sits in a 2 seconds block on the server, but if initial request is empty then return immedietly. //This is a safety mechanism to protect the mongo server from our DOS...(due to this cursor behavior, and from unknown bugs / 'behaviors') var iterationTime = DateTime.UtcNow - t1; if (iterationTime.TotalMilliseconds < SHORT_SLEEP_INTERVAL) // minimal polling interval : TODO - move to Config { Thread.Sleep(SHORT_SLEEP_INTERVAL); } } catch (Exception ex) { Log.Warn("[ReceiveEvents inside while true] Got exception. Will reinitialize and continue. Ex={0}", ex); // cursor died or was killed if (_enumerator != null) { _enumerator.Dispose(); } _enumerator = InitializeCursor(); //This is a safety mechanism to protect the mongo server from our DOS... var iterationTime = DateTime.UtcNow - t1; if (iterationTime.TotalMilliseconds < SHORT_SLEEP_INTERVAL) // minimal polling interval : TODO - move to Config { Thread.Sleep(SHORT_SLEEP_INTERVAL); } } } }