/// <summary> /// Construct Message /// </summary> /// <param name="handle">handle</param> /// <param name="payload">payload</param> /// <param name="streams">streams</param> internal Message(Handle handle, BsonDocument payload, IDictionary<string, Stream> streams) { this.Handle = handle; this.Payload = payload; this.Streams = streams; }
/// <summary> /// Ack handle and send payload to queue, atomically, with no gridfs streams /// </summary> /// <param name="handle">handle to ack received from Get()</param> /// <param name="payload">payload to send</param> /// <param name="earliestGet">earliest instant that a call to Get() can return message</param> /// <param name="priority">priority for order out of Get(). 0 is higher priority than 1</param> /// <param name="newTimestamp">true to give the payload a new timestamp or false to use given message timestamp</param> /// <exception cref="ArgumentNullException">handle or payload is null</exception> /// <exception cref="ArgumentException">priority was NaN</exception> public void AckSend(Handle handle, BsonDocument payload, DateTime earliestGet, double priority, bool newTimestamp) { AckSend(handle, payload, earliestGet, priority, newTimestamp, new KeyValuePair<string, Stream>[0]); }
/// <summary> /// Ack handle and send payload to queue, atomically. /// </summary> /// <param name="handle">handle to ack received from Get()</param> /// <param name="payload">payload to send</param> /// <param name="earliestGet">earliest instant that a call to Get() can return message</param> /// <param name="priority">priority for order out of Get(). 0 is higher priority than 1</param> /// <param name="newTimestamp">true to give the payload a new timestamp or false to use given message timestamp</param> /// <param name="streams">streams to upload into gridfs or null to forward handle's streams</param> /// <exception cref="ArgumentNullException">handle or payload is null</exception> /// <exception cref="ArgumentException">priority was NaN</exception> public void AckSend(Handle handle, BsonDocument payload, DateTime earliestGet, double priority, bool newTimestamp, IEnumerable<KeyValuePair<string, Stream>> streams) { if (handle == null) throw new ArgumentNullException("handle"); if (payload == null) throw new ArgumentNullException("payload"); if (Double.IsNaN(priority)) throw new ArgumentException("priority was NaN", "priority"); var toSet = new BsonDocument { {"payload", payload}, {"running", false}, {"resetTimestamp", DateTime.MaxValue}, {"earliestGet", earliestGet}, {"priority", priority}, }; if (newTimestamp) toSet["created"] = DateTime.UtcNow; if (streams != null) { var streamIds = new BsonArray(); foreach (var stream in streams) streamIds.Add(gridfs.Upload(stream.Value, stream.Key).Id); toSet["streams"] = streamIds; } //using upsert because if no documents found then the doc was removed (SHOULD ONLY HAPPEN BY SOMEONE MANUALLY) so we can just send collection.Update(new QueryDocument("_id", handle.Id), new UpdateDocument("$set", toSet), UpdateFlags.Upsert); foreach (var existingStream in handle.Streams) existingStream.Value.Dispose(); if (streams != null) { foreach (var existingStream in handle.Streams) gridfs.DeleteById(existingStream.Key); } }
/// <summary> /// Ack handle and send payload to queue, atomically, with new timestamp and no gridfs streams /// </summary> /// <param name="handle">handle to ack received from Get()</param> /// <param name="payload">payload to send</param> /// <param name="earliestGet">earliest instant that a call to Get() can return message</param> /// <param name="priority">priority for order out of Get(). 0 is higher priority than 1</param> /// <exception cref="ArgumentNullException">handle or payload is null</exception> /// <exception cref="ArgumentException">priority was NaN</exception> public void AckSend(Handle handle, BsonDocument payload, DateTime earliestGet, double priority) { AckSend(handle, payload, earliestGet, priority, true); }
/// <summary> /// Ack handle and send payload to queue, atomically, with 0.0 priority, new timestamp and no gridfs streams /// </summary> /// <param name="handle">handle to ack received from Get()</param> /// <param name="payload">payload to send</param> /// <param name="earliestGet">earliest instant that a call to Get() can return message</param> /// <exception cref="ArgumentNullException">handle or payload is null</exception> public void AckSend(Handle handle, BsonDocument payload, DateTime earliestGet) { AckSend(handle, payload, earliestGet, 0.0); }
/// <summary> /// Ack handle and send payload to queue, atomically, with earliestGet as Now, 0.0 priority, new timestamp and no gridfs streams /// </summary> /// <param name="handle">handle to ack received from Get()</param> /// <param name="payload">payload to send</param> /// <exception cref="ArgumentNullException">handle or payload is null</exception> public void AckSend(Handle handle, BsonDocument payload) { AckSend(handle, payload, DateTime.UtcNow); }
/// <summary> /// Acknowledge a handle was processed and remove from queue. /// </summary> /// <param name="handle">handle received from Get()</param> /// <exception cref="ArgumentNullException">handle is null</exception> public void Ack(Handle handle) { if (handle == null) throw new ArgumentNullException("handle"); collection.Remove(new QueryDocument("_id", handle.Id)); foreach (var stream in handle.Streams) { stream.Value.Dispose(); gridfs.DeleteById(stream.Key); } }
/// <summary> /// Get a non running message from queue /// </summary> /// <param name="query">query where top level fields do not contain operators. Lower level fields can however. eg: valid {a: {$gt: 1}, "b.c": 3}, invalid {$and: [{...}, {...}]}</param> /// <param name="resetRunning">duration before this message is considered abandoned and will be given with another call to Get()</param> /// <param name="wait">duration to keep polling before returning null</param> /// <param name="poll">duration between poll attempts</param> /// <param name="approximateWait">whether to fluctuate the wait time randomly by +-10 percent. This ensures Get() calls seperate in time when multiple Queues are used in loops started at the same time</param> /// <returns>message or null</returns> /// <exception cref="ArgumentNullException">query is null</exception> public Message Get(QueryDocument query, TimeSpan resetRunning, TimeSpan wait, TimeSpan poll, bool approximateWait) { if (query == null) throw new ArgumentNullException ("query"); //reset stuck messages collection.Update( new QueryDocument { { "running", true }, { "resetTimestamp", new BsonDocument("$lte", DateTime.UtcNow) } }, new UpdateDocument("$set", new BsonDocument("running", false)), UpdateFlags.Multi ); var builtQuery = new QueryDocument("running", false); foreach (var field in query) builtQuery.Add("payload." + field.Name, field.Value); builtQuery.Add("earliestGet", new BsonDocument("$lte", DateTime.UtcNow)); var resetTimestamp = DateTime.UtcNow; try { resetTimestamp += resetRunning; } catch (ArgumentOutOfRangeException) { resetTimestamp = resetRunning > TimeSpan.Zero ? DateTime.MaxValue : DateTime.MinValue; } var sort = new SortByDocument { { "priority", 1 }, { "created", 1 } }; var update = new UpdateDocument("$set", new BsonDocument { { "running", true }, { "resetTimestamp", resetTimestamp } }); var fields = new FieldsDocument { { "payload", 1 }, { "streams", 1 } }; var end = DateTime.UtcNow; try { if (approximateWait) //fluctuate randomly by 10 percent wait += TimeSpan.FromMilliseconds(wait.TotalMilliseconds * GetRandomDouble(-0.1, 0.1)); end += wait; } catch (Exception e) { if (!(e is OverflowException) && !(e is ArgumentOutOfRangeException)) throw e;//cant cover end = wait > TimeSpan.Zero ? DateTime.MaxValue : DateTime.MinValue; } while (true) { var findModifyArgs = new FindAndModifyArgs { Query = builtQuery, SortBy = sort, Update = update, Fields = fields, Upsert = false }; var message = collection.FindAndModify(findModifyArgs).ModifiedDocument; if (message != null) { var handleStreams = new List<KeyValuePair<BsonValue, Stream>>(); var messageStreams = new Dictionary<string, Stream>(); foreach (var streamId in message["streams"].AsBsonArray) { var fileInfo = gridfs.FindOneById(streamId); var stream = fileInfo.OpenRead(); handleStreams.Add(new KeyValuePair<BsonValue, Stream>(streamId, stream)); messageStreams.Add(fileInfo.Name, stream); } var handle = new Handle(message["_id"].AsObjectId, handleStreams); return new Message(handle, message["payload"].AsBsonDocument, messageStreams); } if (DateTime.UtcNow >= end) return null; try { Thread.Sleep(poll); } catch (ArgumentOutOfRangeException) { if (poll < TimeSpan.Zero) poll = TimeSpan.Zero; else poll = TimeSpan.FromMilliseconds(int.MaxValue); Thread.Sleep(poll); } if (DateTime.UtcNow >= end) return null; } }