Is IComponent::getState()/setState() thread-safe?

SDK for VST 3 audio plug-in and host development.
abique
Posts: 20
Joined: Tue Jun 21, 2016 12:43 pm

Is IComponent::getState()/setState() thread-safe?

Postby abique » Mon Feb 12, 2018 11:49 am

Hi,

In Bitwig Studio we use IComponent::getState() from the GUI thread.
But I cam across a crash where IComponent::getState() was concurrent to a program change executed on the Audio thread.

So please, could you guys clarify if getState() and setState() are thread-safe?

Also in general I think that the documentation could be made more clear regarding the threading model and which thread can call which method.

Many thanks,
Alex

ygrabit
Posts: 99
Joined: Fri Jun 17, 2016 7:52 am
Location: Hamburg

Re: Is IComponent::getState()/setState() thread-safe?

Postby ygrabit » Mon Feb 12, 2018 4:01 pm

Hi
there is in the SDK documention a workflow diagram "Audio Processor Call Sequence", setState and getState are called normally from the UI Thread when the plugin is used in a realtime context, in an offline context set/getState could be called in the same thread than the process call.

We will add some more explanation in the SDK doc.

Cheers
YVan Grabit

abique
Posts: 20
Joined: Tue Jun 21, 2016 12:43 pm

Re: Is IComponent::getState()/setState() thread-safe?

Postby abique » Tue Feb 13, 2018 1:27 pm

Thank you very much! :)

pongasoft
Posts: 43
Joined: Sun Mar 11, 2018 5:57 pm

Re: Is IComponent::getState()/setState() thread-safe?

Postby pongasoft » Mon Jun 11, 2018 2:59 pm

ygrabit wrote:Hi
there is in the SDK documention a workflow diagram "Audio Processor Call Sequence", setState and getState are called normally from the UI Thread when the plugin is used in a realtime context, in an offline context set/getState could be called in the same thread than the process call.

We will add some more explanation in the SDK doc.

Cheers


So if I understand what you are saying getState/setState can potentially being called at the same time as process is called... So should the process method use a lock to ensure that
a) the state is not being modified (via setState) in the middle of processing
b) getState gets a fully valid state

I am not sure how else to protect the integrity of the state if getState/setState are not guaranteed to be called outside of process...

Thanks
Yan

Arne Scheffler
Posts: 201
Joined: Mon Jun 20, 2016 7:53 am

Re: Is IComponent::getState()/setState() thread-safe?

Postby Arne Scheffler » Tue Jun 12, 2018 11:16 am

Hi,
please never ever use a lock/mutex or whatever it is called on the audio process thread.
To exchange data to the audio process thread use an atomic value.

Cheers,
Arne

pongasoft
Posts: 43
Joined: Sun Mar 11, 2018 5:57 pm

Re: Is IComponent::getState()/setState() thread-safe?

Postby pongasoft » Tue Jun 12, 2018 1:03 pm

Even with atomic values, if the state is comprised of more than one value, then I just don't see how you make it thread safe IF there is no guarantee of when getState/setState are being called...

Here is a very simple example in my VST plugin: 2 variables define the state of the plugin. Each variable on its own is probably atomic.

Code: Select all

 int fSwitchState; // 0 = A, B = 1
 bool fSoften;


In the process method:

Code: Select all

...
  int32 numParamsChanged = inputParameterChanges.getParameterCount();
  for(int i = 0; i < numParamsChanged; ++i)
  {
    IParamValueQueue *paramQueue = inputParameterChanges.getParameterData(i);
    if(paramQueue != nullptr)
    {
      ParamValue value;
      int32 sampleOffset;
      int32 numPoints = paramQueue->getPointCount();

      // we read the "last" point (ignoring multiple changes for now)
      if(paramQueue->getPoint(numPoints - 1, sampleOffset, value) == kResultOk)
      {
        switch(paramQueue->getParameterId())
        {
          case kAudioSwitch:
            fSwitchState = value;
            break;

          case kSoftenSwitch:
            fSoften = value == 1.0;
            break;
        }
      }
    }
  }



And the setState method

Code: Select all

  IBStreamer streamer(state, kLittleEndian);

  float savedParam1 = 0.f;
  streamer.readFloat(savedParam1);
  fSwitchState = savedParam1;

  bool savedParam2 = true;
  streamer.readBool(savedParam2);
  fSoften = savedParam2;


And the getState method

Code: Select all

  IBStreamer streamer(state, kLittleEndian);
  streamer.writeFloat(fSwitchState);
  streamer.writeBool(fSoften);


If getState is called absolutely any time, then there might be a case when the 2 values are being read/assigned in the process method while they are being written to the streamer... so you could end up with fSwitchState having the new value while fSoften has the old one... and there you go, the state is written invalid. The fact that the 2 values are atomic is not enough.

How do you go about this if you do not use any locks? Or is there some form of guarantee in when and how getState/setState are being called?

Yan

Arne Scheffler
Posts: 201
Joined: Mon Jun 20, 2016 7:53 am

Re: Is IComponent::getState()/setState() thread-safe?

Postby Arne Scheffler » Tue Jun 12, 2018 1:48 pm

Hi,

Code: Select all

struct MyParams {
   double v1;
   bool v2;
};

class Processor : public AudioEffect
{
//....
   std::atomic<MyParams*> setStateParams;
   std::atomic<MyParams*> toDeleteParams;
   MyParams processParams;
};

tresult process (ProcessData& data)
{
   auto stateParams = setStateParams.exchange (nullptr);
   if (stateParams)
   {
      processParams = *stateParams;
      auto test = toDeleteParams.exchange (stateParams);
      assert (test == nullptr);
   }
//....
}

void cleanupMemory()
{
   auto toDelete = toDeleteParams.exchange (nullptr); // cleanup memory
   if (toDelete)
      delete toDelete;
   toDelete = setStateParams.exchange (nullptr); // cleanup memory
   if (toDelete)
      delete toDelete;
}

tresult setState (IBStream* stream)
{
//....
   cleanupMemory ();
   // allocate new state
   auto stateParams = new MyParams;
   // read state from stream into stateParams !
   setStateParams.exchange (stateParams);
}
tresult setActive (bool state)
{
   if(!state)
      cleanupMemory ();
}

Coded out of my head, so typos may very well happened.
I hope this makes it clear.

Cheers,
Arne.

pongasoft
Posts: 43
Joined: Sun Mar 11, 2018 5:57 pm

Re: Is IComponent::getState()/setState() thread-safe?

Postby pongasoft » Tue Jun 12, 2018 2:30 pm

I think I understand the gist. Probably need to do something similar with getState and reading parameter (inputParameterChanges) though

That being said, why is allocating/freeing memory better than using a very short live lock? (playing the devil's advocate here... but you can implement very efficient locks... and in this case the lock is being held ONLY during the copy of the memory)

Ex:

Code: Select all

tresult process (ProcessData& data)
{
   MyParams stateParams;
   withLock {
     stateParams = processParams;
   }
}

tresult setState (IBStream* stream)
{
   MyParams stateParams;
   // read state from stream into stateParams !
   withLock {
     processParams = stateParams;
   }
}


Thanks

Yan

Arne Scheffler
Posts: 201
Joined: Mon Jun 20, 2016 7:53 am

Re: Is IComponent::getState()/setState() thread-safe?

Postby Arne Scheffler » Tue Jun 12, 2018 2:51 pm

Please watch the good and funny video from Pete Goodliffe about the "The Golden Rules of Audio Programming" @ ADC. Then you hopefully know why you never ever use a lock in a realtime audio thread.

https://www.youtube.com/watch?v=SJXGSJ6Zoro

Cheers,
Arne

pongasoft
Posts: 43
Joined: Sun Mar 11, 2018 5:57 pm

Re: Is IComponent::getState()/setState() thread-safe?

Postby pongasoft » Tue Jun 12, 2018 8:11 pm

Based on your code example, I built a couple of abstractions that are thread safe/lock free...

You can check them out there
SingleElementQueue https://github.com/pongasoft/vst-ab-switch/blob/v1.1.1/src/cpp/ABSwitchProcessor.h#L40
AtomicValue https://github.com/pongasoft/vst-ab-switch/blob/v1.1.1/src/cpp/ABSwitchProcessor.h#L82

The SingleElementQueue is being used to communicate the new state to the processor (UI thread calls setState which calls SingleElementQueue::push to push the new state in the queue, and processor calls SingleElementQueue::pop to read it) (https://github.com/pongasoft/vst-ab-switch/blob/v1.1.1/src/cpp/ABSwitchProcessor.cpp#L314)

The AtomicValue is being used to save the new state (processor calls AtomicValue::set / UI thread calls getState which calls AtomicValue::get) (https://github.com/pongasoft/vst-ab-switch/blob/v1.1.1/src/cpp/ABSwitchProcessor.cpp#L331). This can't use a queue because the value needs to be present whenever getState is called...

Yan


Return to “VST 3 SDK”

Who is online

Users browsing this forum: No registered users and 0 guests