1

I have a texture loading thread, which receives requests to load textures from a main thread via a concurrent queue.

The texture loader request is a simple struct with a raw pointer to the object that will receive the texture:

struct TextureLoaderRequest
{
    std::string mFilename;
    ContentViewer *mContentViewer;
};

The actual texture object contained within the ContentViewer is protected by a mutex and some atomic booleans (also contained within ContentViewer):

std::atomic<bool>               mIsLoaded;
std::atomic<bool>               mIsVisible;
std::mutex                      mImageMutex;

Then the texture access routines are as follows:

void ContentViewer::setTexture(ci::gl::TextureRef texture)
{
    std::lock_guard<std::mutex> guard(mImageMutex);
    mImage = texture;
}

ci::gl::TextureRef ContentViewer::getTexture()
{
    std::lock_guard<std::mutex> guard(mImageMutex);
    if (mIsVisible)
    {
        if (mImage != nullptr)
        {
            mIsLoaded = true;
            return mImage;
        }
        mIsLoaded = false;
    }
    return nullptr;
}

The texture loader may receive many texture load requests from the main thread at a single time, and then works through the queue loading and assigning textures to the content viewer pointed to in the texture load request message.

The problem I am having is that when the main thread "deletes" the content viewers, the texture loading thread may have an outstanding request in its queue, and by the time it gets to processing it, the content viewer has been deleted and the program crashes.

I'm not sure how to go about removing that outstanding texture load request sitting in the texture thread work queue. I cannot allow the main thread to wait for the relevant texture to be loaded for the content viewer, but then, what would the best practice strategy for achieving this be?

Thanks - Laythe

BDL
  • 21,052
  • 22
  • 49
  • 55
Laythe
  • 85
  • 1
  • 8
  • would using a std::shared_ptr be helpful? – xaxxon Feb 05 '17 at 13:14
  • I've retaged your question for opengl-es. (The opengl tag means desktop-gl). If you meant desktop-gl instead, feel free to tag it with that and remove the opengl-es tag. – BDL Feb 05 '17 at 13:28
  • can you share your texture work thread function? –  Feb 05 '17 at 13:43
  • @xaxxon How would shared_ptr be of use here? I guess the texture request could have a weak_ptr and then the thread check the weak_ptr in order to determine validity. I guess I would still need synchronization around the shared pointers. To me it seems cleaner to use raw pointers for this use case, but I am not expert in the use of shared pointers. – Laythe Feb 06 '17 at 01:18
  • @farhat latrach Please see below – Laythe Feb 06 '17 at 01:18

3 Answers3

0

Ok, so there are two options imho, either:

  1. You wait for all the requests for a specific object to be finished before deleting it.
  2. You check if the object is still there before you execute any operation that was scheduled.

I don't have sufficient insight on your application: how the queue is implemented and why and when requests are scheduled, so I cannot give feedback on that.

JHBonarius
  • 10,824
  • 3
  • 22
  • 41
  • Hi, I can't wait for the request to be honored. This code is to operate to perform a close operation, thereby the desired effect should be to cancel outstanding requests. The second option you mention is my first attempt and what I found was that if I check for validity, and then use the object, it may have disappeared in between the check and the usage, therefore crashing. I did find a solution which I will outline below. Thanks for your help! – Laythe Feb 06 '17 at 00:47
  • In a multithreaded system you _have_ to lock an object to prevent these situations, where for instance an object is deleted after the check for validity. Else it is impossible to prevent the situation you describe. so the deletion method for instance also has to lock mImageMutex. You're question is a common one in multithreading. Like is stated here [link]http://stackoverflow.com/questions/12455297/delete-an-object-securely-from-a-multi-threaded-program : You cannot delete an object that is in use. No amount of mutexes will fix that. – JHBonarius Feb 06 '17 at 12:22
0

I found that I needed to construct a cancellation list out of a std::mutex protected vector. When the main thread wants to quit it simply adds an entry into the vector and continues on. The texture loader thread has the additional burden of checking the list for every received texture request, but operation is not on the critical path.

I'd still be interested in alternatives/suggestions.

A little outline of the thread is below:

void textureLoaderThreadFn()
{
    log("texture loader thread started");

    while (!mShouldQuit)
    {
        // Wait for texture loader request
        TextureLoaderRequest *textureLoaderRequest = nullptr;
        mTextureRequests->popBack(&textureLoaderRequest);

        // it is possible popBack didnt modify textureLoaderRequest (eg. when cancelled on exit)
        if (textureLoaderRequest != nullptr)
        {
            std::lock_guard<std::mutex> lk(mCancellationListMutex);

            if (std::find(mCancellationList.begin(), mCancellationList.end(), textureLoaderRequest->mFilename) != mCancellationList.end())
            {
                // Cancelled

                // we must reset the isLoading that was set by the main thread,
                // so that the request to load the texture can get put back if need be
                textureLoaderRequest->mContentViewer->mIsLoading = false;

                // remove from cancellation list
                mCancellationList.erase(std::remove(mCancellationList.begin(), mCancellationList.end(), textureLoaderRequest->mFilename), mCancellationList.end());
            }
            else
            {
                // Not cancelled
                <SERVICE TEXTURE REQUEST>
            }

            // dont need this anymore
            delete textureLoaderRequest;
        }
    }
    log("texture loader thread stopped");

    // Empty the queue    
    int count = 0;
    TextureLoaderRequest *textureLoaderRequest = nullptr;
    while (mTextureRequests->tryPopBack(&textureLoaderRequest))
    {
        if (textureLoaderRequest != nullptr)
            delete textureLoaderRequest;
        count++;
    }
    log("texture loader thread purged " + std::to_string(count) + " outstanding texture load requests");
}
Laythe
  • 85
  • 1
  • 8
0

I would suggest you add a status flag to the the content viewer to distinguish 3 statuses; scheduled for coloration, being colored and not scheduled for coloration. The main thread should delete a content viewer only when its status is scheduled for coloration or not scheduled for coloration.

The texture worker thread changes the status to being colored and once it is colored it put it to colored.

The change of the statuses and the check of the status if it can be deleted should always be scoped by the same mutex; you might put the flag as private in content viewer and use two public methods 1)void change_status(status) and 2)bool can_delete().

Both functions should start with acquiring same mutex. the 1) to be used in different transitions in main thread and in texture worker thread and the 2) is used by the main thread before deleting the content viewer to return true only if status is not being colored.

In the texture work thread, before quitting, you might delete the last colored content in the case it was not deleted by main thread (as it might had been on status being colored).

  • the main thread wont be able to delete it as its status is "being colored"; the can_delete woud return false. Then this last one you delete while quitting the texture thread... –  Feb 06 '17 at 13:16
  • Hi, if the main thread wants to close the content viewer before a scheduled request has been performed, the only scheme I have came up with so far is to make a note of the fact that we have deleted the content viewer (the mutex protected cancellation list I mention below), and then have the texture loader cancel the texture request based on the request destination address being in the cancellation list. This is the option I outline below and seems to work but has not been stress tested. I am not sure why we would need to protect an atomic boolean flag with a std:mutex? – Laythe Feb 06 '17 at 13:28
  • Do you mean delete the content viewer from within the texture loader thread itself? – Laythe Feb 06 '17 at 13:29
  • Also, "The main thread should delete a content viewer only when its status is scheduled for coloration or not scheduled for coloration" - What would the main thread so in this case, is it encounters a content viewer that being coloured (loaded)? Thanks – Laythe Feb 06 '17 at 13:29
  • skip and does not delete it as it is being used. Always it should be only one in this status. I assume that the main thread when decided to delete content viewers it will also shut down the texture thread so the texture thread worker, while quitting, it deletes the last content viewer it had used if its not yet deleted; say like you always stores the pointer of the last one and before quitting checks it. –  Feb 06 '17 at 13:35
  • Sorry for being a bit vague. The texture request thread needs to hang around, as it is a general loader thread, possibly servicing others, so there would be no possibility of the texture thread deleting the content viewers. – Laythe Feb 06 '17 at 18:58
  • If, within the main loop of the texture thread, it knew somehow when it is safe to delete the content viewer, then I could use the same logic to perform the "ignore texture load", but I don't see how this is possible without some sort of cancellation list that the texture thread has to work through every scan of the thread. – Laythe Feb 06 '17 at 18:58