Of functions and thread safety

I’ve been trying to chase down this crash bug I’m getting, every time I think I have a lead, it turns out I don’t. I have most of the functionality in my program switched off whilst I’m trying to find it. it happens when I render audio, after I have started a thread to load a wav file.

A lead I have at the moment is the crash will occur in the draw thread, but in one of my poco threads, execution can sometimes currently be on ofGetElapsedtimeMicros which I’m quite fond of using and use it in almost every thread. I’m on mac osx so the eventual function call is to clock_get_time(cs, &now); in clock.h

Would it be a problem to call ofGetElapsedtimeMicros from different threads? is clock_get_time simply part of the macosx sdk and can I rely on that being thread safe?

It’s possible.

Could you post your code?

My bet is that there’s some other resource that the threads are sharing in an unsafe way.

ok, it’s a bit long winded. the short version is I keep a multimap of different sounds in a singleton soundplayer class, a seperate thread soundloader will load data, send back to the soundplayer class, and rtaudio will render whatever data in the multimap from the soundplayer class I allow it.

the longer version with code.

soundplayer has a thread and a looping threaded function and a lock. The main thread will pick up a GUI event and call load sounds

void SoundPlayer::LoadSound(std::multimap<string, SoundLoadListener*> loadRequests)
{
    GetLockBlock("SoundPlayer::LoadSound");
    
    std::multimap<string, SoundLoadListener*>::iterator it = loadRequests.begin();
    while(it != loadRequests.end())
    {
        _loadRequests.insert(*it);
        it++;
    }
    
    //take out from requests what we don't need loader thread to handle
    //and create soundobjects,
    //either match up data already loaded and set state to loaded,
    //or leave for loader thread and set loadstate to loading
    std::multimap<string, SoundLoadListener*>::iterator it1 = _loadRequests.begin();
    while(it1 != _loadRequests.end())
    {
        StreamObject* newStreamObject = new StreamObject;
        _streamObjects.insert(std::pair<std::string, StreamObject*>(it1->first, newStreamObject));
        
        newStreamObject->SetLoadState(LoadingState::eLoading);
        newStreamObject->SetLoadListener(it1->second);

        std::map<string, SoundStore*>::iterator it2 = _dataTable.begin();
        if(_dataTable.find(it1->first) != _dataTable.end())
        {
            //data found, set data, set load state, remove from requests
            newStreamObject->GetData().m_fWholeTune = it2->second->_dataf;
            newStreamObject->SetLoadState(LoadingState::eLoading); //tricking threadedfunction to pick this up
            
            //newStreamObject->GetLoadListener()->OnLoaded(newStreamObject);
            it1 = _loadRequests.erase(it1);
            continue;
        }
        
        it1++;
    }
        
    ReleaseLock("SoundPlayer::LoadSound");
}

the threaded function will pick up these requests and pass on the requests to soundloader, another thread.

_soundLoader.LoadSounds(_loadRequests);

loadsounds simply attains it’s own lock, takes a copy of the requsts, stores them, and starts the thread

void SoundLoader::LoadSounds(std::multimap<string, SoundLoadListener*> loadRequests)
{
    _loadRequests = loadRequests;
    //SetPriority(Poco::Thread::Priority::PRIO_HIGHEST);
    //SetOSPriority(GetMaxOSPriority());
    startThread();
}

void SoundLoader::threadedFunction()
{
    //unsigned long long timeFuncStart = ofGetElapsedTimeMillis();
    GetLockBlock("SoundLoader::threadedFunction");

    //for each entry in _loadRequests, load and call soundplayer->dataloaded
    std::multimap<string, SoundLoadListener*>::iterator it = _loadRequests.begin();
    while(it != _loadRequests.end())
    {
        SF_INFO info;
        void* datav;
        float* dataf;
        
        std::string filename = it->first;
        size_t len = filename.find_last_of(".");
        size_t filenameLen =  filename.length();
        
        char buffer[32];
        filename.copy(buffer, filenameLen - len,len);
        buffer[filenameLen - len] = '\0';

        //for the test, its definitely a .wav
        {
            SNDFILE* sndfile;
            
            sndfile = sf_open(filename.c_str(), SFM_READ, &info);
            if(sndfile == NULL)
            {
                //failed to open, return false;
                return false;
            }
            
            sf_count_t numSamples = info.channels * info.frames;
            dataf = new float[numSamples];
            
            sf_seek(sndfile, 0, SEEK_SET);
            //be clever here
            //read in a loop, a few frames at a time? 1024? 2048? test it
            //then yield
            sf_count_t samplesReadPre = numSamples;
            sf_count_t samplesRead = 0;
            
            while(samplesReadPre > 0)
            {
                //printf("soundload \n");
                unsigned int samplestoRead = 0;
                if(samplesReadPre - 2048 > 0)
                {
                    samplestoRead = 2048;
                }
                else
                {
                    samplestoRead = samplesReadPre;
                }
                samplesReadPre -= samplestoRead;
                samplesRead += sf_read_float(sndfile, dataf, samplestoRead);
                dataf += samplestoRead;
                
                yield();
            }
            
            assert(samplesRead == numSamples);
            
            dataf -= samplesRead;//reset pointer
            
            sf_close(sndfile);

        }
        
        SoundPlayer::GetInstance()->DataLoaded(filename, info, dataf);
        it = _loadRequests.erase(it);
    }
	ReleaseLock("SoundLoader::threadedFunction");
}

//data loaded in soundplayer will take the new float data and put it into it’s own soundstore class

void SoundPlayer::DataLoaded(std::string filename, SF_INFO info, float* dataf)
{
    //unsigned long long timeDataLoadedStart = ofGetElapsedTimeMicros();
    GetLockBlock("SoundPlayer::DataLoaded", true);
    
    SoundStore* newStore = new SoundStore;
    
    newStore->_filename = filename;
    newStore->_info = info;
    newStore->_dataf = dataf;
    
    _dataTable.insert(std::pair<std::string, SoundStore*>(filename, newStore));
    
    ReleaseLock("SoundPlayer::DataLoaded", true);
}

the threaded function of soundplayer, it will keep checking of the data has been loaded, then alert the listeners that allow the GUI to play them

if(_streamObjects.size() > 0)
        {
            //printf("_streamObjects.size() > 0 \n");
            std::multimap<std::string, StreamObject*>::iterator streamObjIt = _streamObjects.begin();
            while(streamObjIt != _streamObjects.end())
            {
                StreamObject& streamObject = *(streamObjIt->second);
                
                if(streamObject.GetLoadState() == LoadingState::eLoading)
                {
                    //check data table for load
                    std::map<string, SoundStore*>::iterator it2 = _dataTable.find(streamObjIt->first);
                    if(it2 != _dataTable.end())
                    {
                        //data found, set data, set load state, remove from requests
                        streamObject.SetData(it2->second->_dataf);//force copy
                        streamObject.SetInfo(it2->second->_info);
                        streamObject.SetName(it2->first);
                        streamObject.SetLoadState(LoadingState::eLoaded);
                        streamObject.GetLoadListener()->OnLoaded(&streamObject);
                    }
                }
                else if(streamObject.GetLoadState() == LoadingState::eLoaded)
                {
                    //todo what do we do here eh?
                }
                
                streamObjIt++;
            }
        }

then when the gui wants to play them, the rtaudio renderer will know this and allow it to be rendered to the audio device

void SoundPlayer::RealCallback(float *outputBuffer, int outChannels, unsigned int frameCount, AudioData* audioData)
{
    
    unsigned int inChannels = audioData->GetNumChannels();
    unsigned int startChan = audioData->m_startChannel;
    
    audioData->m_phase = resample_linear_int16xN_reference(audioData->m_phase, audioData->m_phaseStep, audioData->m_fWholeTune,
                                                           audioData->m_tempBuffer, frameCount, inChannels, audioData->m_bForward);
    audioData->m_framePos = audioData->m_phase >> 32;

    for( unsigned int i = 0; i < (frameCount * outChannels); i += inChannels)
    {
        int k = i * inChannels;
        int l = k  + startChan;
        
        for(int j = 0; j < inChannels; j++)
        {
            outputBuffer[l + j] += audioData->m_tempBuffer[k + j];
        }
    }
}

the crash will happen when I press play, or when I take focus away from the screen. it will do so intermittently.

is the crash happening on ofGetElapsedTimeMicros? you should get info from the debugger of in which exact function call is the crash happening

the crash isn’t happening in ofGetElapsedTimeMicros(), I’ve just seen this being the function currently being run in other threads at the time of crash more than a few times. the crash itself is usually in the draw thread and in ofTessellator on deallocating memory, usually when I’m drawing a rounded rect or my own shape using ofPath. I think thats just because of the length of time it takes to complete vs all the other drawing and a bit of a red herring. Sometimes its in pollevents(), sometimes i get the method cache corrupted message.

When I thought I had solved this problem a while ago, it turns out the draw thread was just being held up as it was trying to lock a mutex and blocking for too long, but on checking it recently it doesn’t seem to be blocking for very long.

most recent crash is this one, on loading and playing a wav then taking focus away from the screen

void ofMainLoop::pollEvents(){
	if(windowPollEvents){
		windowPollEvents();
	}
}

objc[2616]: Method cache corrupted. This may be a message to an invalid object, or a memory error somewhere else.
objc[2616]: receiver 0x101505e60, SEL 0x7fff9368f04d, isa 0x7fff7620ebd8, cache 0x7fff7620ebe8, buckets 0x1020ed000, mask 0x3f, occupied 0x27
objc[2616]: receiver 944 bytes, buckets 1536 bytes
objc[2616]: selector 'edgesOnly'
objc[2616]: isa '_CUIInternalLinkRendition'
objc[2616]: Method cache corrupted.

also if you are using xcode try cleaning the project completely and rebuilding everything including the OF lib. sometimes xocde mixes old object files or something and generates binaries that crash for no apparent reason

This is a fairly old issue, I’ve since reconstructed the project on a brand new macbook since it happened. I’m fairly certain it will be my error, there will be some unsafe thread memory operation I’m doing.

I appear to have solved it, the problem was with the tempbuffer I used to handle the resampled data, it was a member of my audio objects, for some reason it wasn’t liking that so I just swapped it out for a single static float array all the audio objects share.