Note: This project was written while investigating the behaviour of multiple outputs. It's not really a good example of anything, so please don't base your own code on the code found here.
The is an Azure function app containing 4 functions. Each is written using Javascript, and have been tested using the Functions runtime version 2.
I wanted to see the order in which output bindings are fired. To try and determine this, I have 4 functions, each with two output bindings:
- one to a storage queue
- one to blob storage
I wanted to determine whether the order in which the function code calls the bindings was important, or whether it was the order in which the bindings are declared in function.json
that's important. To do this, I've created 4 functions that:
- try each order of code execution
- try each order of binding definition
It's difficult to see from logs which order the bindings fire, so in order to determine the order I deliberately break the blob storage binding by giving it a bad/invalid connection string. I then look at when this error is logged, and whether items are added to the queue or not.
If, for example, blog storage were to ALWAYs fire before the queue binding, I would never expect to see items in the queue as I'd expect the exception to cause the function/runtime to stop processing and return an error.
I then have 4 functions:
- executes the blob binding, in code, before the queue binding
- has the blob binding definition (in function.json) before the queue binding definition
- executes the blob binding, in code, before the queue binding
- has the queue binding definition (in function.json) before the blob binding definition
- executes the queue binding, in code, before the blob binding
- has the blob binding definition (in function.json) before the queue binding definition
- executes the queue binding, in code, before the blob binding
- has the queue binding definition (in function.json) before the blob binding definition
- Clone the function app code
- Using the Azure portal, create a storage account
- within that storage account, create a queue called
outputqueue
- within that storage account, create a queue called
- Set up your
local.settings.json
something like this...
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "",
"QueueStorageConnectionString": "[ADD CONNECTION STRING TO YOUR STORAGE ACCOUNT HERE]",
"ThisConnectionStringIsBroken": "broken"
}
}
When you deploy this to azure, remember to set up 2 Application Settings QueueStorageConnectionString
and ThisConnectionStringIsBroken
with the same values as those in your local.settings.json
.
Using the following tooling...
- Azure Functions Core Tools (2.0.1-beta.21)
- Function Runtime Version: 2.0.11370.0
...I see the following results:
- In all cases, the function outputs the START and END logging
- In all cases, the function returns an HTTP 500 response
- In all cases, a message was added to the queue
This suggests:
- neither the code order OR the binding order in being respected
- all bindings are being run AFTER
context.done()
- all binding are run, regardless of any one binding failing.
At the time of testing, this uses...
- Runtime version: 1.0.11388.0 (~1)
... and the results are:
- In all cases, the function outputs the START and END logging
- In all cases, the function returns an HTTP 500 response
- Only the following messages are seen in the queue:
- { "message": "blob-queue-queuebinding-blobbinding" }
- { "message": "queue-blob-queuebinding-blobbinding" }
This suggests:
- code order is not important
- binding order IS important, as we only see messages when the queue binding is first
- all bindings are being run AFTER
context.done()
At the time of testing, this uses...
- Runtime version: 2.0.11390.0 (beta)
... and the results are:
- In all cases, the function outputs the START and END logging
- In all cases, the function returns an HTTP 500 response
- In all cases, a message was added to the queue
This suggests:
- neither the code order OR the binding order in being respected
- all bindings are being run AFTER
context.done()
- all binding are run, regardless of any one binding failing
- Runtime 1 and 2 execute bindings differently. Runtime 1 respects the order of 'function.json' where as Runtime 2 appears to run all bindings, regardless of errors
- Anecdotally it's understood that "blob bindings run before other bindings" but for neither of these runtimes do I see this being upheld.
The main difference for C# (csx) is that the runtime validates that the storage account connection is valid before hosting the functions. So we must find a different way to make it fail. One way to do it is to create a blob in storage and acquire a lease on it. Then update the storage bindings to create that same file name. That's why the C# bindings all have the following:
{
"type": "blob",
"name": "outblob",
"path": "outcontainer/file.jpg",
"connection": "ThisConnectionStringIsBroken",
"direction": "out"
},
- Runtime 1 and 2 validate the storage connection string for storage, which is different to how they behave with Node.js functions.
- Runtime 1 and 2 return 500 error status codes for all four functions, and nothing is written to the queue, which again is different from the Node.js functions. Runtime 1 will show the error content in the 500 errors, while Runtime 2 doesn't (returns an empty body). It upholds the notion that "blob bindings run before other bindings" (as it fails in all four cases and nothing is writting to the queue).