Concurrency Verified

āœ… - A checkmark means "this has been tested, and will run concurrently". ā” - The questionmark means "it hasn't been tested, safety unknown" āŒ - The red "X" means "this has been tested, and it will not work concurrently".

Concurrency in this context is about gathering zone data, as seen during the preview stage when fetching current data for all zones.

Here's how to perform a test:

  1. Build dnscontrol with the -race flag: go build -race (this makes a special binary)

  2. Run this special binary: dnscontrol preview with a configuration that lists at least 4 domains using the provider in question. More domains is better.

  3. The special binary runs much slower (1/10th the speed) because it is doing a lot of checking.

If it reports problems, the error message will indicate where the problem is. It might not be 100% accurate, but it will be in the right area.

Development guidance

Loosely, each Provider has a top-level struct named {providername}Provider; if this state is just some simple data-types initialized once before processing starts, and then only read, then the provider is likely to be concurrency-safe.

If this contains state which is updated (eg, caches) then the provider is probably not concurrency-safe unless every routine accessing it is protected by appropriate primitives.

Eg, URLs and auth credentials are fine. An account ID determined on the first query? That probably needs to be protected, even if every fetch returns the same data.

The -race build is a helpful hint but is not a guarantee.

Multiple Providers

Most simple use-cases likely use just one copy of a given provider, managing zones in an account. But there can be multiple distinct copies, each for different accounts. Someone might use this while migrating accounts, for instance. You might have two fields in creds.json both with a TYPE of your provider.

The uses of the provider objects should never create copies; each is created by a constructor, but thereafter is a singleton per constructed provider. Thus it is safe to have synchronization objects inside the provider struct.

See, for example, the dnsimple provider, where there is a sync.Once per object, not at a global level, so that the .accountID can be fetched just once per configured provider. Because sync.Once contains a reference to sync.noCopy, the go vet command will catch attempts to copy that object, and so will catch attempts to copy the containing dnsimpleProvider object.

Last updated