Static local variables unintentionally shared across plugin instances?

Came across something that seems weird to me. I have a plugin (VST3) that has some static local variables declared inside of my processBlock(), for counting things - like number of times processBlock is called, accumulated number of samples processed etc. They keep running totals for various calculations. I didn’t make them member variables because some of them are just for debugging, and anyway, it seems a static local would work just as well and be just right there in the only function that uses it.

For example:

void PluginProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi)
{
    static long count = 0;

    // do something

    count++;
}

I just noticed today that if I add a second instance of the plugin to the AudioPluginHost, the static locals get updated twice as fast, or twice per call, or they are somehow being shared between the two running instances. I’m not even sure how that’s possible!

static storage duration. The storage for the object is allocated when the program begins and deallocated when the program ends. Only one instance of the object exists. (…) Variables declared at block scope with the specifier static or thread_local have static or thread storage duration but are initialized the first time control passes through their declaration

If two instances of a plugin run in the same process, they share their statics.

1 Like

Wow, thanks. I had no idea that would happen! So I guess if you don’t want that to happen, everything needs to be a member variable?

1 Like

Yap. Except, of course, for things that you do want to share between instances. But you shouldn’t count on that either, because a host can choose to run them on different processes. I use a SharedResourcePointer (a ref-counted pointer to a static object) for some big things that are only read after they’re created, so they can be shared, but it’s ok if they’re not. I also use thread local static maps to “fake” thread local members, but that’s a whole another thing.

Small OT: are you aware the JUCE has a class for this too, right?
https://docs.juce.com/master/classThreadLocalValue.html

I saw it, but if I get it right it works pretty much like thread_local, i.e. an instance per thread, and I needed an instance per thread, per object -that’s what I meant with thread local members. So I did

bool& whatever(const MyAudioProcessor* p) {
    thread_local static std::unordered_map<const MyAudioProcessor*, bool> v;
    return v[p];
}

It’s for flags that I raise when some parameters are changed or a preset/state is loaded, and this could happen on two threads at the same time, so if I have a single atomic flag, it could be reset on one thread while the other hasn’t reached there yet.

Ah sorry, I interpreted your use of the word member to mean: “they are to threads what member variables are to objects”. Thanks for explaining your use case

1 Like