Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XPCOM C++ multithread and javascript callback

Background

In XULRunner version belowe 12.0 it's works, but when i'm trying port it to version 12.0 or higher it crash application. Main reason is that in sdk v12 or newer developers remove proxy objects to xpcom components and recommend replace it by wrapping objects with nsRunnable/nsIRunnable and route invocation to main thread by function NS_DispatchToMainThread (click here)

What i'm developing?

I created db connector which is async and comunicate with main thread by callbacks. Using: XULRunner v6, porting to XULRunner v17 or above



    //nsIDBCallback.idl
    [scriptable, function, uuid(XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX)]
    interface nsIDBCallback : nsISupports {
       void onInfo(in long phase, in long status, in string info);
    }



    //nsDBService.h, it is XPCOM component 
    class nsDBService : public nsIDBService, nsIRunnable
    {
    public:
       NS_DECL_ISUPPORTS
       NS_DECL_NSIRUNNABLE
       NS_DECL_NSIDBSERVICE

    private:
       std::vector<nsIThread*>     threads;
       std::vector<nsIDBCallback*> callbacks;
       std::vector<const char*>    sqls;

       nsIThread* makeNewThread();
       void       runOperationIfNotBussy();

    public:
       NS_IMETHODIMP Query(const char *sql, nsIDBCallback *callback);
    }



    //nsDBService.cpp
    // adding query and other data to buffers, 
    // it's thread safe, there are used mutex's
    NS_IMETHODIMP nsDBService::Query(const char *sql, nsIDBCallback *callback)
    {
       callbacks.push_back(callback);
       sqls     .push_back(sql);
       threads  .push_back( makeNewThread() );

       //run added operation if db driver is free, 
       //if driver is bussy then invocation is in buffer and need to wait
       runOperationIfNotBussy();

       return NS_OK;
    }

    void nsDBService::runOperationIfNotBussy() 
    {
       //some conditions, test's etc.

       //run first operation on list
       // RUNNING A THREAD, still ok
       if(...) threads.front()->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL); 
    }

    //if this method is used by another thread+db query, 
    //then other operations can't run and need to wait
    //operations are stored and supported like fifo
    NS_IMETHODIMP nsDBService::Run(void)
    {
       //some other operations
       //real db operations in background

       int32_t phase = 3; //endphase
       int32_t code  = 0; //ok
       const char *msg = "OK";

       nsIDBCallback *callback = callbacks.pop();
       //wrapping callback function with runnable interface
       nsIRunnable   *runCallback = new nsResultCallback(callback, 
                                                         phase, 
                                                         code, 
                                                         msg);
       //routing event to main thread
       NS_DispatchToMainThread(runCallback, NS_DISPATCH_NORMAL); 

       runOperationIfNotBussy();
    }



    //nsResultCallback.h
    class nsResultCallback: public nsRunnable
    { 
    public:
       NS_DECL_ISUPPORTS
    public:
       NS_DECL_NSIRUNNABLE

    private:
       nsIDBCallback* callback;
       int32_t        resPhase;
       int32_t        resStatus;
       const char*    resMessage;

    public:
       nsResultCallback(nsIDBCallback* callback, 
                        int32_t phase, 
                        int32_t status, 
                        const std::string &message)   
          : callback(callback), 
            resPhase(phase), 
            resStatus(status), 
            resMessage(c_str_clone(message.c_str())) {};
       ~nsResultCallback();

    };



    //nsResultCallback.cpp
    NS_IMETHODIMP nsResultCallback::Run(void)
    {
       nsresult rv = NS_ERROR_FAILURE;
       try 
       {
          // APP HANDS AND CRUSH ! 
          if(this->callback) this->callback->OnInfo(resPhase, resStatus, resMessage);
       }
       catch(...) 
       {
          rv = NS_ERROR_UNEXPECTED;
          ERRF("nsBackpack::Run call method OnInfo from callback failed");
       }
       return rv;
    }

INVOCATION



    // *.js
    nsDBService.query("SELECT * FROM t", function(phase, code, mes) {
       //some UI actions or others db queries
    });

Problem:

Application freeze and crash when code execution look like this:



    nsDBService::Query //main thread ok
    nsDBService::runOperationIfNotBussy //main thread
    nsDBService::threads.front()->Dispatch //run bg thread
    nsDBService:Run //bg thread
    NS_DispatchToMainThread //main thread
    nsResultCallback::Run //main thread
    nsIDBCallback::OnInfo //main thread, crash

If code execution look like this, everything is ok:

                                                                                                       

nsDBService::Query //main thread ok
NS_DispatchToMainThread //main thread
nsResultCallback::Run //main thread
nsIDBCallback::OnInfo //main thread ok

Question:

When nsIDBCallback is invoked from NS_DispatchToMainThread and NS_DispatchToMainThread is invoked from other thread then main app thread, then execution fails, what i'm missing, don't understand? Or what is another approach for background tasks?

like image 557
LBezimienny Avatar asked Jan 16 '13 16:01

LBezimienny


1 Answers

Cannot reproduce, as you didn't provide a self-contained, complete example, so some remarks instead:

The first thing I noticed is the cross-thread access of std::vector. You wrote something about mutexes in the comments, so this might be OK.

What is certainly wrong, is storing raw pointers to nsIDBCallback. XPCOM objects are ref-counted. So as soon your Query method returns, the underlying object might be deleted if there are no other references to it, leaving behind a dangling pointer in your vector. I think this is what is happening here! You need to keep the object alive until the thread is done with it, preferably by putting it into a nsCOMPtr<nsIDBCallback> somewhere, e.g. in an nsCOMPArray<nsIDBCallback>.

PS: Turns out this is a somewhat old question, which I missed... So sorry for the delay answering it :p

like image 175
nmaier Avatar answered Oct 22 '22 13:10

nmaier