public async Task <FunctionResult> ExecuteAsync(IStorageQueueMessage value, CancellationToken cancellationToken) { BlobTriggerMessage message = JsonConvert.DeserializeObject <BlobTriggerMessage>(value.AsString, JsonSerialization.Settings); if (message == null) { throw new InvalidOperationException("Invalid blob trigger message."); } string functionId = message.FunctionId; if (functionId == null) { throw new InvalidOperationException("Invalid function ID."); } // Ensure that the function ID is still valid. Otherwise, ignore this message. FunctionResult successResult = new FunctionResult(true); BlobQueueRegistration registration; if (!_registrations.TryGetValue(functionId, out registration)) { return(successResult); } IStorageBlobContainer container = registration.BlobClient.GetContainerReference(message.ContainerName); string blobName = message.BlobName; IStorageBlob blob; switch (message.BlobType) { case StorageBlobType.PageBlob: blob = container.GetPageBlobReference(blobName); break; case StorageBlobType.BlockBlob: default: blob = container.GetBlockBlobReference(blobName); break; } // Ensure the blob still exists with the same ETag. string possibleETag = await _eTagReader.GetETagAsync(blob, cancellationToken); if (possibleETag == null) { // If the blob no longer exists, just ignore this message. return(successResult); } // If the blob still exists but the ETag is different, delete the message but do a fast path notification. if (!String.Equals(message.ETag, possibleETag, StringComparison.Ordinal)) { _blobWrittenWatcher.Notify(blob); return(successResult); } //// If the blob still exists and its ETag is still valid, execute. //// Note: it's possible the blob could change/be deleted between now and when the function executes. Guid?parentId = await _causalityReader.GetWriterAsync(blob, cancellationToken); TriggeredFunctionData input = new TriggeredFunctionData { ParentId = parentId, TriggerValue = blob }; return(await registration.Executor.TryExecuteAsync(input, cancellationToken)); }
public async Task <FunctionResult> ExecuteAsync(IStorageBlob value, CancellationToken cancellationToken) { // Avoid unnecessary network calls for non-matches. First, check to see if the blob matches this trigger. IReadOnlyDictionary <string, object> bindingData = _input.CreateBindingData(value.ToBlobPath()); if (bindingData == null) { // Blob is not a match for this trigger. return(new FunctionResult(true)); } // Next, check to see if the blob currently exists (and, if so, what the current ETag is). string possibleETag = await _eTagReader.GetETagAsync(value, cancellationToken); if (possibleETag == null) { // If the blob doesn't exist and have an ETag, don't trigger on it. return(new FunctionResult(true)); } IStorageBlockBlob receiptBlob = _receiptManager.CreateReference(_hostId, _functionId, value.Container.Name, value.Name, possibleETag); // Check for the completed receipt. If it's already there, noop. BlobReceipt unleasedReceipt = await _receiptManager.TryReadAsync(receiptBlob, cancellationToken); if (unleasedReceipt != null && unleasedReceipt.IsCompleted) { return(new FunctionResult(true)); } else if (unleasedReceipt == null) { // Try to create (if not exists) an incomplete receipt. if (!await _receiptManager.TryCreateAsync(receiptBlob, cancellationToken)) { // Someone else just created the receipt; wait to try to trigger until later. // Alternatively, we could just ignore the return result and see who wins the race to acquire the // lease. return(new FunctionResult(false)); } } string leaseId = await _receiptManager.TryAcquireLeaseAsync(receiptBlob, cancellationToken); if (leaseId == null) { // If someone else owns the lease and just took over this receipt or deleted it; // wait to try to trigger until later. return(new FunctionResult(false)); } ExceptionDispatchInfo exceptionInfo; try { // Check again for the completed receipt. If it's already there, noop. BlobReceipt receipt = await _receiptManager.TryReadAsync(receiptBlob, cancellationToken); Debug.Assert(receipt != null); // We have a (30 second) lease on the blob; it should never disappear on us. if (receipt.IsCompleted) { await _receiptManager.ReleaseLeaseAsync(receiptBlob, leaseId, cancellationToken); return(new FunctionResult(true)); } // We've successfully acquired a lease to enqueue the message for this blob trigger. Enqueue the message, // complete the receipt and release the lease. // Enqueue a message: function ID + blob path + ETag BlobTriggerMessage message = new BlobTriggerMessage { FunctionId = _functionId, BlobType = value.BlobType, ContainerName = value.Container.Name, BlobName = value.Name, ETag = possibleETag }; await _queueWriter.EnqueueAsync(message, cancellationToken); // Complete the receipt & release the lease await _receiptManager.MarkCompletedAsync(receiptBlob, leaseId, cancellationToken); await _receiptManager.ReleaseLeaseAsync(receiptBlob, leaseId, cancellationToken); return(new FunctionResult(true)); } catch (Exception exception) { exceptionInfo = ExceptionDispatchInfo.Capture(exception); } Debug.Assert(exceptionInfo != null); await _receiptManager.ReleaseLeaseAsync(receiptBlob, leaseId, cancellationToken); exceptionInfo.Throw(); FunctionResult result = new FunctionResult(exceptionInfo.SourceException); return(result); // Keep the compiler happy; we'll never get here. }