Multithreading - Creating a Module

Started by
7 comments, last by WitchLord 9 years, 4 months ago

I know that building multiple modules in separate threads that belong to the same engine is "thread safe" because the engine will only allow building one module at a time.

It looks like if multiple threads are calling "asIScriptEngine::GetModule" and "asIScriptModule::Discard" at the same time they calling code should acquire a lock first, since these functions modify an array belonging to the engine. I'm not suggesting that the functions themselves should block. I am just confirming that the burden is on the application to synchronize those module-related activities.


// Create a new script module
asAcquireExclusiveLock();
asIScriptModule *mod = engine->GetModule("module", asGM_ALWAYS_CREATE);
asReleaseExclusiveLock();

// Load and add the script sections to the module
string script;
LoadScriptFile("script.as", script);
mod->AddScriptSection("script.as", script.c_str());

// Build the module
int r = mod->Build();

// ... Do something with the module

asAcquireExclusiveLock();
mod->Discard();
asReleaseExclusiveLock();
Advertisement

What a coincidence. :)

Yesterday I checked in revision 2041 with an improvement to make GetModule and Discard threadsafe.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

I think I found a gap in your solution.


asCModule *asCScriptEngine::GetModule(const char *_name, bool create)
{
    // Accept null as well as zero-length string
    const char *name = "";
    if( _name != 0 ) name = _name;

    if( lastModule && lastModule->name == name )
        return lastModule;

    lastModule = 0;
    ACQUIRESHARED(engineRWLock);
    // TODO: optimize: Improve linear search
    for( asUINT n = 0; n < scriptModules.GetLength(); ++n )
        if( scriptModules[n] && scriptModules[n]->name == name )
        {
            lastModule = scriptModules[n];
            break;
        }
    RELEASESHARED(engineRWLock);
    if( lastModule ) 
        return lastModule;

    if( create )
    {
        asCModule *module = asNEW(asCModule)(name, this);
        if( module == 0 )
        {
            // Out of memory
            return 0;
        }

        ACQUIREEXCLUSIVE(engineRWLock);
        scriptModules.PushLast(module);
        RELEASEEXCLUSIVE(engineRWLock);

        lastModule = module;

        return lastModule;
    }

    return 0;
}

The member variable "lastModule" should be guarded under an exclusive lock when writing. I had one user experience a crash from a module being destroyed twice. The problem was two threads were attempting to create new modules simultaneously. One of the threads searched through the "scriptModules" array an found no matching module, while the other thread created a new module and saved it to "lastModule". Then the first thread tested "lastModule" (immediately after the search), and it was not null (even though the search was not successful). So both function calls returns the same "asIModule" pointer, setting the stage for the crash.

I have attached a patch with my solution.

I think you forgot to attach the patch. :)

But I see what you mean, and I'll have it fixed asap.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Sorry. I thought I had uploaded it.

Thanks. I've checked in the fix in revision 2088.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Thanks for the quick response, but I think there may still be a slight issue. It seems like if two threads enter this block:


ACQUIRESHARED(engineRWLock);
if( lastModule && lastModule->name == name )
    retModule = lastModule;
else
{
    // TODO: optimize: Improve linear search
    for( asUINT n = 0; n < scriptModules.GetLength(); ++n )
        if( scriptModules[n] && scriptModules[n]->name == name )
        {
            lastModule = retModule = scriptModules[n];
            break;
        }
}
RELEASESHARED(engineRWLock);

Then there is the opportunity for:

Thread1: "lastModule->name == name" true

Thread2: "scriptModules[n]->name == name" true

Thread2: "lastModule = retModule = scriptModules[n]"

Thread1: "retModule = lastModule"

If that were to happen, then Thread1 would return the incorrect module. It seems like "lastModule" should always be within an exclusive lock when being written to.

Of course, you're right. I totally missed this yesterday.

I'll have it corrected tonight.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Corrected in revision 2089.

It was a stupid mistake from my side. In trying to keep the code cleaner I forgot to think through the scenarios for concurrencies.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement