private async Task ReleaseMessageAsync(IStorageQueueMessage message, CancellationToken cancellationToken) { try { // We couldn't process the message. Let someone else try. await _queue.UpdateMessageAsync(message, TimeSpan.Zero, MessageUpdateFields.Visibility, cancellationToken); } catch (StorageException exception) { if (exception.IsBadRequestPopReceiptMismatch()) { // Someone else already took over the message; no need to do anything. return; } else if (exception.IsNotFoundMessageOrQueueNotFound() || exception.IsConflictQueueBeingDeletedOrDisabled()) { // The message or queue is gone, or the queue is down; no need to release the message. return; } else { throw; } } }
public void QueueTrigger_ProvidesPocoComplexPropertyBindingData() { // Arrange Poco expectedChild = new Poco { Value = "abc", Int32Value = 123 }; IStorageAccount account = CreateFakeStorageAccount(); IStorageQueue queue = CreateQueue(account, QueueName); Poco value = new Poco { Child = expectedChild }; string content = JsonConvert.SerializeObject(value, typeof(Poco), settings: null); IStorageQueueMessage message = queue.CreateMessage(content); queue.AddMessage(message); // Act Poco result = RunTrigger <Poco>(account, typeof(BindToPocoComplexPropertyBindingDataProgram), (s) => BindToPocoComplexPropertyBindingDataProgram.TaskSource = s); // Assert AssertEqual(expectedChild, result); }
/// <inheritdoc /> public Task UpdateMessageAsync(IStorageQueueMessage message, TimeSpan visibilityTimeout, MessageUpdateFields updateFields, CancellationToken cancellationToken) { CloudQueueMessage sdkMessage = ((StorageQueueMessage)message).SdkObject; return(_sdk.UpdateMessageAsync(sdkMessage, visibilityTimeout, updateFields, options: null, operationContext: null, cancellationToken: cancellationToken)); }
private async Task DeleteMessageAsync(IStorageQueueMessage message, CancellationToken cancellationToken) { try { await _queue.DeleteMessageAsync(message, cancellationToken); } catch (StorageException exception) { // For consistency, the exceptions handled here should match UpdateQueueMessageVisibilityCommand. if (exception.IsBadRequestPopReceiptMismatch()) { // If someone else took over the message; let them delete it. return; } else if (exception.IsNotFoundMessageOrQueueNotFound() || exception.IsConflictQueueBeingDeletedOrDisabled()) { // The message or queue is gone, or the queue is down; no need to delete the message. return; } else { throw; } } }
public void ExecuteAsync_IfMessageIsFunctionIdIsRegistered_GetsETag(BlobType expectedBlobType) { // Arrange string expectedContainerName = "container"; string expectedBlobName = TestBlobName; string functionId = "FunctionId"; Mock <IBlobETagReader> mock = new Mock <IBlobETagReader>(MockBehavior.Strict); mock.Setup(r => r.GetETagAsync(It.Is <IStorageBlob>(b => b.BlobType == (StorageBlobType)expectedBlobType && b.Name == expectedBlobName && b.Container.Name == expectedContainerName), It.IsAny <CancellationToken>())) .Returns(Task.FromResult("ETag")) .Verifiable(); IBlobETagReader eTagReader = mock.Object; BlobQueueTriggerExecutor product = CreateProductUnderTest(eTagReader); product.Register(functionId, CreateDummyTriggeredFunctionExecutor()); BlobTriggerMessage triggerMessage = new BlobTriggerMessage { FunctionId = functionId, BlobType = (StorageBlobType)expectedBlobType, ContainerName = expectedContainerName, BlobName = expectedBlobName, ETag = "OriginalETag" }; IStorageQueueMessage message = CreateMessage(triggerMessage); // Act Task task = product.ExecuteAsync(message, CancellationToken.None); // Assert task.WaitUntilCompleted(); mock.Verify(); }
public static Guid? GetOwner(IStorageQueueMessage msg) { string text = msg.TryGetAsString(); if (text == null) { return null; } IDictionary<string, JToken> json; try { json = JsonSerialization.ParseJObject(text); } catch (Exception) { return null; } if (!json.ContainsKey(ParentGuidFieldName) || json[ParentGuidFieldName].Type != JTokenType.String) { return null; } string val = (string)json[ParentGuidFieldName]; Guid guid; if (Guid.TryParse(val, out guid)) { return guid; } return null; }
public static async Task AddMessageAndCreateIfNotExistsAsync(this IStorageQueue queue, IStorageQueueMessage message, CancellationToken cancellationToken) { if (queue == null) { throw new ArgumentNullException("queue"); } bool isQueueNotFoundException = false; try { await queue.AddMessageAsync(message, cancellationToken); return; } catch (StorageException exception) { if (!exception.IsNotFoundQueueNotFound()) { throw; } isQueueNotFoundException = true; } Debug.Assert(isQueueNotFoundException); await queue.CreateIfNotExistsAsync(cancellationToken); await queue.AddMessageAsync(message, cancellationToken); }
public void QueueTrigger_IfBoundToPocoAndMessageIsNotJson_DoesNotBind() { // Arrange const string content = "not json"; // Not a valid JSON byte sequence. IStorageAccount account = CreateFakeStorageAccount(); IStorageQueue queue = CreateQueue(account, QueueName); IStorageQueueMessage message = queue.CreateMessage(content); queue.AddMessage(message); // Act Exception exception = RunTriggerFailure <Poco>(account, typeof(BindToPocoProgram), (s) => BindToPocoProgram.TaskSource = s); // Assert Assert.IsType <InvalidOperationException>(exception); Assert.Equal("Exception binding parameter 'message'", exception.Message); Exception innerException = exception.InnerException; Assert.IsType <InvalidOperationException>(innerException); const string expectedInnerMessage = "Binding parameters to complex objects (such as 'Poco') uses " + "Json.NET serialization. \r\n1. Bind the parameter type as 'string' instead of 'Poco' to get the raw " + "values and avoid JSON deserialization, or\r\n2. Change the queue payload to be valid json. The JSON " + "parser failed: Unexpected character encountered while parsing value: n. Path '', line 0, position " + "0.\r\n"; Assert.Equal(expectedInnerMessage, innerException.Message); }
public static string TryGetAsString(this IStorageQueueMessage message) { if (message == null) { throw new ArgumentNullException("message"); } string value; try { value = message.AsString; } catch (Exception ex) { if (ex is DecoderFallbackException || ex is FormatException) { value = null; } else { throw; } } return(value); }
public void ExecuteAsync_IfBlobIsUnchanged_CallsInnerExecutor() { // Arrange string functionId = "FunctionId"; string matchingETag = "ETag"; Guid expectedParentId = Guid.NewGuid(); IBlobETagReader eTagReader = CreateStubETagReader(matchingETag); IBlobCausalityReader causalityReader = CreateStubCausalityReader(expectedParentId); Mock <IFunctionExecutor> mock = new Mock <IFunctionExecutor>(MockBehavior.Strict); mock.Setup(e => e.TryExecuteAsync(It.Is <IFunctionInstance>(f => f.ParentId == expectedParentId), It.IsAny <CancellationToken>())) .Returns(Task.FromResult <IDelayedException>(null)) .Verifiable(); IFunctionExecutor innerExecutor = mock.Object; BlobQueueTriggerExecutor product = CreateProductUnderTest(eTagReader, causalityReader, innerExecutor); product.Register(functionId, CreateFakeInstanceFactory()); IStorageQueueMessage message = CreateMessage(functionId, matchingETag); // Act Task <bool> task = product.ExecuteAsync(message, CancellationToken.None); // Assert task.WaitUntilCompleted(); mock.Verify(); }
public void QueueTrigger_IfBoundToPocoAndMessageIsIncompatibleJson_DoesNotBind() { // Arrange const string content = "123"; // A JSON int rather than a JSON object. IStorageAccount account = CreateFakeStorageAccount(); IStorageQueue queue = CreateQueue(account, QueueName); IStorageQueueMessage message = queue.CreateMessage(content); queue.AddMessage(message); // Act Exception exception = RunTriggerFailure <Poco>(account, typeof(BindToPocoProgram), (s) => BindToPocoProgram.TaskSource = s); // Assert Assert.IsType <InvalidOperationException>(exception); Assert.Equal("Exception binding parameter 'message'", exception.Message); Exception innerException = exception.InnerException; Assert.IsType <InvalidOperationException>(innerException); string expectedInnerMessage = "Binding parameters to complex objects (such as 'Poco') uses Json.NET " + "serialization. \r\n1. Bind the parameter type as 'string' instead of 'Poco' to get the raw values " + "and avoid JSON deserialization, or\r\n2. Change the queue payload to be valid json. The JSON parser " + "failed: Error converting value 123 to type '" + typeof(Poco).FullName + "'. Path '', line 1, " + "position 3.\r\n"; Assert.Equal(expectedInnerMessage, innerException.Message); }
public void ExecuteAsync_IfBlobHasChanged_NotifiesWatcherAndReturnsSuccessResult() { // Arrange string functionId = "FunctionId"; IBlobETagReader eTagReader = CreateStubETagReader("NewETag"); Mock <IBlobWrittenWatcher> mock = new Mock <IBlobWrittenWatcher>(MockBehavior.Strict); mock.Setup(w => w.Notify(It.IsAny <IStorageBlob>())) .Verifiable(); IBlobWrittenWatcher blobWrittenWatcher = mock.Object; BlobQueueTriggerExecutor product = CreateProductUnderTest(eTagReader, blobWrittenWatcher); BlobQueueRegistration registration = new BlobQueueRegistration { BlobClient = CreateClient(), Executor = CreateDummyTriggeredFunctionExecutor() }; product.Register(functionId, registration); IStorageQueueMessage message = CreateMessage(functionId, "OriginalETag"); // Act Task <FunctionResult> task = product.ExecuteAsync(message, CancellationToken.None); // Assert task.WaitUntilCompleted(); mock.Verify(); Assert.True(task.Result.Succeeded); }
public static Guid?GetOwner(IStorageQueueMessage msg) { string text = msg.TryGetAsString(); if (text == null) { return(null); } IDictionary <string, JToken> json; try { json = JsonSerialization.ParseJObject(text); } catch (Exception) { return(null); } if (!json.ContainsKey(ParentGuidFieldName) || json[ParentGuidFieldName].Type != JTokenType.String) { return(null); } string val = (string)json[ParentGuidFieldName]; Guid guid; if (Guid.TryParse(val, out guid)) { return(guid); } return(null); }
public void ExecuteAsync_IfInnerExecutorFails_ReturnsFailureResult() { // Arrange string functionId = "FunctionId"; string matchingETag = "ETag"; IBlobETagReader eTagReader = CreateStubETagReader(matchingETag); IBlobCausalityReader causalityReader = CreateStubCausalityReader(); FunctionResult expectedResult = new FunctionResult(false); Mock <ITriggeredFunctionExecutor> mock = new Mock <ITriggeredFunctionExecutor>(MockBehavior.Strict); mock.Setup(e => e.TryExecuteAsync( It.IsAny <TriggeredFunctionData>(), It.IsAny <CancellationToken>())) .ReturnsAsync(expectedResult) .Verifiable(); BlobQueueTriggerExecutor product = CreateProductUnderTest(eTagReader, causalityReader); ITriggeredFunctionExecutor innerExecutor = mock.Object; product.Register(functionId, innerExecutor); IStorageQueueMessage message = CreateMessage(functionId, matchingETag); // Act Task <FunctionResult> task = product.ExecuteAsync(message, CancellationToken.None); // Assert Assert.False(task.Result.Succeeded); }
internal async Task ProcessMessageAsync(IStorageQueueMessage message, TimeSpan visibilityTimeout, CancellationToken cancellationToken) { try { if (!await _queueProcessor.BeginProcessingMessageAsync(message.SdkObject, cancellationToken)) { return; } FunctionResult result = null; using (ITaskSeriesTimer timer = CreateUpdateMessageVisibilityTimer(_queue, message, visibilityTimeout, _backgroundExceptionDispatcher)) { timer.Start(); result = await _triggerExecutor.ExecuteAsync(message, cancellationToken); await timer.StopAsync(cancellationToken); } await _queueProcessor.CompleteProcessingMessageAsync(message.SdkObject, result, cancellationToken); } catch (OperationCanceledException) { // Don't fail the top-level task when an inner task cancels. } catch (Exception exception) { // Immediately report any unhandled exception from this background task. // (Don't capture the exception as a fault of this Task; that would delay any exception reporting until // Stop is called, which might never happen.) _backgroundExceptionDispatcher.Throw(ExceptionDispatchInfo.Capture(exception)); } }
private static IReadOnlyDictionary <string, object> CreateBindingData(IStorageQueueMessage value, IReadOnlyDictionary <string, object> bindingDataFromValueType) { Dictionary <string, object> bindingData = new Dictionary <string, object>(StringComparer.OrdinalIgnoreCase); string queueMessageString = value.TryGetAsString(); // Don't provide the QueueTrigger binding data when the queue message is not a valid string. if (queueMessageString != null) { bindingData.Add("QueueTrigger", queueMessageString); } bindingData.Add("DequeueCount", value.DequeueCount); bindingData.Add("ExpirationTime", value.ExpirationTime.GetValueOrDefault(DateTimeOffset.MaxValue)); bindingData.Add("Id", value.Id); bindingData.Add("InsertionTime", value.InsertionTime.GetValueOrDefault(DateTimeOffset.UtcNow)); bindingData.Add("NextVisibleTime", value.NextVisibleTime.GetValueOrDefault(DateTimeOffset.MaxValue)); bindingData.Add("PopReceipt", value.PopReceipt); if (bindingDataFromValueType != null) { foreach (KeyValuePair <string, object> item in bindingDataFromValueType) { // In case of conflict, binding data from the value type overrides the built-in binding data above. bindingData[item.Key] = item.Value; } } return(bindingData); }
private static void TestOwnerIsNull(IStorageQueueMessage message) { // Act Guid?owner = QueueCausalityManager.GetOwner(message); // Assert Assert.Null(owner); }
public static void AddMessage(this IStorageQueue queue, IStorageQueueMessage message) { if (queue == null) { throw new ArgumentNullException("queue"); } queue.AddMessageAsync(message, CancellationToken.None).GetAwaiter().GetResult(); }
public void GetOwner_IfMessageIsNotValidString_ReturnsNull() { Mock <IStorageQueueMessage> mock = new Mock <IStorageQueueMessage>(MockBehavior.Strict); mock.Setup(m => m.AsString).Throws <DecoderFallbackException>(); IStorageQueueMessage message = mock.Object; TestOwnerIsNull(message); }
private async Task CopyToPoisonQueueAsync(IStorageQueueMessage message, CancellationToken cancellationToken) { await _poisonQueue.AddMessageAndCreateIfNotExistsAsync(message, cancellationToken); if (_sharedWatcher != null) { _sharedWatcher.Notify(_poisonQueue.Name); } }
private async Task ProcessMessageAsync(IStorageQueueMessage message, TimeSpan visibilityTimeout, CancellationToken cancellationToken) { try { bool succeeded; using (ITaskSeriesTimer timer = CreateUpdateMessageVisibilityTimer(_queue, message, visibilityTimeout, _backgroundExceptionDispatcher)) { timer.Start(); succeeded = await _triggerExecutor.ExecuteAsync(message, cancellationToken); await timer.StopAsync(cancellationToken); } // Need to call Delete message only if function succeeded. if (succeeded) { await DeleteMessageAsync(message, cancellationToken); } else if (_poisonQueue != null) { if (message.DequeueCount >= _maxDequeueCount) { _log.WriteLine("Message has reached MaxDequeueCount of {0}. Moving message to queue '{1}'.", _maxDequeueCount, _poisonQueue.Name); await CopyToPoisonQueueAsync(message, cancellationToken); await DeleteMessageAsync(message, cancellationToken); } else { await ReleaseMessageAsync(message, cancellationToken); } } else { // For queues without a corresponding poison queue, leave the message invisible when processing // fails to prevent a fast infinite loop. // Specifically, don't call ReleaseMessage(message) } } catch (OperationCanceledException) { // Don't fail the top-level task when an inner task cancels. } catch (Exception exception) { // Immediately report any unhandled exception from this background task. // (Don't capture the exception as a fault of this Task; that would delay any exception reporting until // Stop is called, which might never happen.) _backgroundExceptionDispatcher.Throw(ExceptionDispatchInfo.Capture(exception)); } }
public QueueMessageValueProvider(IStorageQueueMessage message, object value, Type valueType) { if (value != null && !valueType.IsAssignableFrom(value.GetType())) { throw new InvalidOperationException("value is not of the correct type."); } _message = message; _value = value; _valueType = valueType; }
public void Add(T item) { IStorageQueueMessage message = _converter.Convert(item); if (message == null) { throw new InvalidOperationException("Cannot enqueue a null queue message instance."); } _queue.AddMessageAndCreateIfNotExistsAsync(message, CancellationToken.None).GetAwaiter().GetResult(); }
public Task AddAsync(T item, CancellationToken cancellationToken) { IStorageQueueMessage message = _converter.Convert(item); if (message == null) { throw new InvalidOperationException("Cannot enqueue a null queue message instance."); } return(_queue.AddMessageAndCreateIfNotExistsAsync(message, cancellationToken)); }
public void ExecuteAsync_IfFunctionIdIsNull_Throws() { // Arrange BlobQueueTriggerExecutor product = CreateProductUnderTest(); IStorageQueueMessage message = CreateMessage("{}"); // Act Task task = product.ExecuteAsync(message, CancellationToken.None); // Assert ExceptionAssert.ThrowsInvalidOperation(() => task.GetAwaiter().GetResult(), "Invalid function ID."); }
public async Task SetValueAsync(object value, CancellationToken cancellationToken) { IStorageQueueMessage message = _converter.Convert((TInput)value); Debug.Assert(message != null); await _queue.AddMessageAndCreateIfNotExistsAsync(message, cancellationToken); if (_messageEnqueuedWatcher != null) { _messageEnqueuedWatcher.Notify(_queue.Name); } }
private ITaskSeriesTimer CreateUpdateMessageVisibilityTimer(IStorageQueue queue, IStorageQueueMessage message, TimeSpan visibilityTimeout, IWebJobsExceptionHandler exceptionHandler) { // Update a message's visibility when it is halfway to expiring. TimeSpan normalUpdateInterval = new TimeSpan(visibilityTimeout.Ticks / 2); IDelayStrategy speedupStrategy = new LinearSpeedupStrategy(normalUpdateInterval, MinimumVisibilityRenewalInterval); ITaskSeriesCommand command = new UpdateQueueMessageVisibilityCommand(queue, message, visibilityTimeout, speedupStrategy); return(new TaskSeriesTimer(command, exceptionHandler, Task.Delay(normalUpdateInterval))); }
private static ITaskSeriesTimer CreateUpdateMessageVisibilityTimer(IStorageQueue queue, IStorageQueueMessage message, TimeSpan visibilityTimeout, IBackgroundExceptionDispatcher backgroundExceptionDispatcher) { // Update a message's visibility when it is halfway to expiring. TimeSpan normalUpdateInterval = new TimeSpan(visibilityTimeout.Ticks / 2); IDelayStrategy speedupStrategy = new LinearSpeedupStrategy(normalUpdateInterval, TimeSpan.FromMinutes(1)); ITaskSeriesCommand command = new UpdateQueueMessageVisibilityCommand(queue, message, visibilityTimeout, speedupStrategy); return(new TaskSeriesTimer(command, backgroundExceptionDispatcher, Task.Delay(normalUpdateInterval))); }
public void ExecuteAsync_IfMessageIsNotJson_Throws() { // Arrange BlobQueueTriggerExecutor product = CreateProductUnderTest(); IStorageQueueMessage message = CreateMessage("ThisIsNotValidJson"); // Act Task task = product.ExecuteAsync(message, CancellationToken.None); // Assert Assert.Throws <JsonReaderException>(() => task.GetAwaiter().GetResult()); }
public bool TryConvert(object input, out IStorageQueueMessage output) { TInput typedInput = input as TInput; if (typedInput == null) { output = null; return(false); } output = _innerConverter.Convert(typedInput); return(true); }
public void ExecuteAsync_IfMessageIsFunctionIdIsNotRegistered_ReturnsSuccessResult() { // Arrange BlobQueueTriggerExecutor product = CreateProductUnderTest(); IStorageQueueMessage message = CreateMessage(new BlobTriggerMessage { FunctionId = "Missing" }); // Act Task <FunctionResult> task = product.ExecuteAsync(message, CancellationToken.None); // Assert Assert.True(task.Result.Succeeded); }
public Task AddMessageAsync(IStorageQueueMessage message, CancellationToken cancellationToken) { if (message == null) { throw new ArgumentNullException("message"); } MutableStorageQueueMessage storeMessage = message as MutableStorageQueueMessage; if (storeMessage == null) { storeMessage = new FakeStorageQueueMessage(message.SdkObject); } _store.AddMessage(_queueName, storeMessage); return Task.FromResult(0); }
public UpdateQueueMessageVisibilityCommand(IStorageQueue queue, IStorageQueueMessage message, TimeSpan visibilityTimeout, IDelayStrategy speedupStrategy) { if (queue == null) { throw new ArgumentNullException("queue"); } if (message == null) { throw new ArgumentNullException("message"); } if (speedupStrategy == null) { throw new ArgumentNullException("speedupStrategy"); } _queue = queue; _message = message; _visibilityTimeout = visibilityTimeout; _speedupStrategy = speedupStrategy; }
private static void TestOwnerIsNull(IStorageQueueMessage message) { // Act Guid? owner = QueueCausalityManager.GetOwner(message); // Assert Assert.Null(owner); }
/// <inheritdoc /> public Task DeleteMessageAsync(IStorageQueueMessage message, CancellationToken cancellationToken) { CloudQueueMessage sdkMessage = ((StorageQueueMessage)message).SdkObject; return _sdk.DeleteMessageAsync(sdkMessage, cancellationToken); }
/// <inheritdoc /> public Task UpdateMessageAsync(IStorageQueueMessage message, TimeSpan visibilityTimeout, MessageUpdateFields updateFields, CancellationToken cancellationToken) { CloudQueueMessage sdkMessage = ((StorageQueueMessage)message).SdkObject; return _sdk.UpdateMessageAsync(sdkMessage, visibilityTimeout, updateFields, cancellationToken); }
private static IReadOnlyDictionary<string, object> CreateBindingData(IStorageQueueMessage value, IReadOnlyDictionary<string, object> bindingDataFromValueType) { Dictionary<string, object> bindingData = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); string queueMessageString = value.TryGetAsString(); // Don't provide the QueueTrigger binding data when the queue message is not a valid string. if (queueMessageString != null) { bindingData.Add("QueueTrigger", queueMessageString); } bindingData.Add("DequeueCount", value.DequeueCount); bindingData.Add("ExpirationTime", value.ExpirationTime.GetValueOrDefault(DateTimeOffset.MaxValue)); bindingData.Add("Id", value.Id); bindingData.Add("InsertionTime", value.InsertionTime.GetValueOrDefault(DateTimeOffset.UtcNow)); bindingData.Add("NextVisibleTime", value.NextVisibleTime.GetValueOrDefault(DateTimeOffset.MaxValue)); bindingData.Add("PopReceipt", value.PopReceipt); if (bindingDataFromValueType != null) { foreach (KeyValuePair<string, object> item in bindingDataFromValueType) { // In case of conflict, binding data from the value type overrides the built-in binding data above. bindingData[item.Key] = item.Value; } } return bindingData; }
private static ITaskSeriesTimer CreateUpdateMessageVisibilityTimer(IStorageQueue queue, IStorageQueueMessage message, TimeSpan visibilityTimeout, IBackgroundExceptionDispatcher backgroundExceptionDispatcher) { // Update a message's visibility when it is halfway to expiring. TimeSpan normalUpdateInterval = new TimeSpan(visibilityTimeout.Ticks / 2); IDelayStrategy speedupStrategy = new LinearSpeedupStrategy(normalUpdateInterval, TimeSpan.FromMinutes(1)); ITaskSeriesCommand command = new UpdateQueueMessageVisibilityCommand(queue, message, visibilityTimeout, speedupStrategy); return new TaskSeriesTimer(command, backgroundExceptionDispatcher, Task.Delay(normalUpdateInterval)); }
public Task UpdateMessageAsync(IStorageQueueMessage message, TimeSpan visibilityTimeout, MessageUpdateFields updateFields, CancellationToken cancellationToken) { _store.UpdateMessage(_queueName, (MutableStorageQueueMessage)message, visibilityTimeout, updateFields); return Task.FromResult(0); }
public Task DeleteMessageAsync(IStorageQueueMessage message, CancellationToken cancellationToken) { _store.DeleteMessage(_queueName, (MutableStorageQueueMessage)message); return Task.FromResult(0); }