I've created some very simple Azure functions. They read and write data from Couchbase (which is running in Azure on a VM).
I'm concerned about the connection(s) that I make to Couchbase in an Azure Function. I create a Cluster
object each time. This is an expensive operation, and I would typically only do it once in a normal web app. But in the Azure Function, I'm new
ing it up every time.
There are a lot of expensive to instantiate objects like this beyond just Couchbase. Is there way to create a singleton, or some sort of shared object that Azure Functions can reuse between calls?
When dealing with singletons on Azure Functions, there are several considerations. One is that global state is shared among AF invocations. So, if a function is invoked once and then again later (soon enough that the host hasn't unloaded your code), then the initialization only happens once. Another consideration is that AF is perfectly free to start multiple AF invocations simultaneously - so any singletons need to be threadsafe (including their initialization).
This means you'll want to use Lazy<T>
/ AsyncLazy<T>
. However, bear in mind that AF (with these types) will preserve the singleton state (post-initialization) for your next invocation even if it fails. This can be a problem particularly with cloud computing because if there's a network (or configuration) error when your AF is starting up, you want the initialization to be retried on the next AF invocation.
In conclusion, you want to use Lazy<T>
/ AsyncLazy<T>
in such a way that is threadsafe and does not preserve failures.
With Lazy<T>
, this means you have to use the LazyThreadSafetyMode.PublicationOnly
flag and pass a function to the constructor (not just implicitly use the default constructor for T
). Note that this means you need to ensure your initialization function itself is threadsafe because it can be executed by multiple threads simultaneously.
With AsyncLazy<T>
, you have to use the AsyncLazyFlags.RetryOnFailure
flag. Since AsyncLazy<T>
is essentially a Lazy<Task<T>>
, the async initialization task is "shared" among all simultaneous callers, and then atomically replaced with a new Lazy<Task<T>>
instance if it fails. So the async initialization function does not need to be threadsafe.
Since getting this all right (especially for multiple singletons) is rather copy-and-pasteish, I abstracted this out for the AF project I'm working on:
AsyncLazy<T>
for both sync and async singletons so that a sync initialization function will "share" its work, so it's only run once even if multiple threads simultaneously request an instance.It took a while to get to this point, but I'm pretty pleased at how it's turned out. Been meaning to blog about this, too...
Static properties for your expensive connection objects will work fine, but I recommend wrapping them in Lazy<>
so that you get guaranteed thread safety out of the box.
Based on the sample blog post that you linked to an example of making the bucket reusable across all your function calls in a guaranteed thread safe way might look something like this:
public class FunctionClass { private static Lazy<IBucket> LazyBucket = new Lazy<IBucket>(() => { var uri = ConfigurationManager.AppSettings["couchbaseUri"]; var cluster = new Cluster(new ClientConfiguration { Servers = new List<Uri> { new Uri(uri) } }); var bucketName = ConfigurationManager.AppSettings["couchbaseBucketName"]; var bucketPassword = ConfigurationManager.AppSettings["couchbaseBucketPassword"]; return cluster.OpenBucket(bucketName, bucketPassword); }); // Your actual function implementation public static async Task Run() { // Here you are guaranteed to get back a shared connection object to your bucket that has been // initalized only once in a thread safe way var initalizedOnceBucket = LazyBucket.Value; // do something with the bucket } }
If the construction of your expensive object that should be shared relies on some async calls (I suspect the Couchbase C# client might have async versions of it's methods). You can use the AsyncLazy<>
from the awesome Nito.AsyncEx Nuget package written by Stephen Cleary. Regular Lazy<>
is built into .NET so don't require any external dependencies.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With