XPCOM:nsIThreadManager: Difference between revisions

m
 
(14 intermediate revisions by 2 users not shown)
Line 6: Line 6:
  interface nsIThreadManager : nsISupports {
  interface nsIThreadManager : nsISupports {
   /**
   /**
     * Create a new named thread (a global, user PRThread).  If the name is
     * Create a new thread (a global, user PRThread).  Currently, flags is
     * non-empty, then the name of the thread must be unique.  Specifying an
     * an unused parameter, that must be 0.
    * empty name results in an anonymous thread that cannot be found later on
    * using the getThread method.
     */
     */
   nsIThread newThread(in ACString name);
   nsIThread newThread(in unsigned long flags);
   
  /**
    * Find a named thread.  If no thread exists by the given name, then null
    * is returned.
    */
  nsIThread getThread(in ACString name);
    
    
   /**
   /**
Line 73: Line 65:
  [scriptable, uuid(...)]
  [scriptable, uuid(...)]
  interface nsIThread : nsIEventTarget {
  interface nsIThread : nsIEventTarget {
  /**
    * Returns the name of the thread, which may be empty if this thread is
    * anonymous.
    */
  readonly attribute ACString name;
   
   /**
   /**
     * Returns the PRThread object corresponding to this nsIThread.
     * Returns the PRThread object corresponding to this nsIThread.
     */
     */
   [noscript] PRThread getPRThread();
   [noscript] readonly attribute PRThread PRThread;
    
    
   /**
   /**
Line 100: Line 86:
   /**
   /**
     * Process the next event.  If there are no pending events, then this
     * Process the next event.  If there are no pending events, then this
     * method will wait until an event is dispatched to this thread.  This
     * method may wait -- provided mayWait is true -- until an event is  
    * method is re-entrant but may only be called if this thread is the
    * dispatched to this thread.  This method is re-entrant but may only be
     * current thread.
    * called if this thread is the current thread.
     *
    * @return A boolean value that is "true" if an event was processed.
     */
     */
   void processNextEvent();
   boolean processNextEvent(in boolean mayWait);
  };
  };
=== nsIThreadInternal ===
=== nsIThreadInternal ===
  [scriptable, uuid(...)]
  [scriptable, uuid(...)]
Line 151: Line 140:
     *        Indicates whether or not the method is allowed to block the calling
     *        Indicates whether or not the method is allowed to block the calling
     *        thread.  For example, this parameter is false during thread shutdown.
     *        thread.  For example, this parameter is false during thread shutdown.
    * @param recursionDepth
    *        Indicates the number of calls to ProcessNextEvent on the call stack
    *        in addition to the current call.
     */
     */
   void onProcessNextEvent(in nsIThreadInternal thread,
   void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait,
                           in boolean mayWait);
                           in unsigned long recursionDepth);
  };
  };


Line 207: Line 199:
   * Create a new thread.
   * Create a new thread.
   *
   *
   * @param name
   * @param result
   *        The name of the thread (must be unique) or the empty string to
   *        The resulting nsIThread object.
  *        create an anonymous thread.
   * @param event
   * @param event
   *        The initial event to run on this thread.  This can be null.
   *        The initial event to run on this thread.  This can be null.
  * @param result
  *        The resulting nsIThread object.
   */
   */
  NS_METHOD NS_NewThread(const nsACString &name, nsIRunnable *event,
  NS_METHOD NS_NewThread(nsIThread **result, nsIRunnable *event);
                        nsIThread **result);
   
   
  /**
  /**
Line 233: Line 221:
   */
   */
  NS_METHOD NS_GetMainThread(nsIThread **result);
  NS_METHOD NS_GetMainThread(nsIThread **result);
 
/**
  * Get a reference to the thread with the given name.
  *
  * @param name
  *        The name of the requested thread.  Must be non-empty.
  * @param result
  *        The resulting nsIThread object.
  */
NS_METHOD NS_GetThread(const nsACString &name, nsIThread **result);
   
   
  /**
  /**
Line 296: Line 274:
   
   
  already_AddRefed<nsIThread> do_GetMainThread();
  already_AddRefed<nsIThread> do_GetMainThread();
&nbsp;
already_AddRefed<nsIThread> do_GetThread(const nsACString &name);


=== nsRunnable ===
=== nsRunnable ===
Line 316: Line 292:
Dispatching <code>MyEvent</code> is then as simple as:
Dispatching <code>MyEvent</code> is then as simple as:


nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
NS_ENSURE_STATE(thread);
&nbsp;
  nsCOMPtr<nsIRunnable> event = new MyEvent();
  nsCOMPtr<nsIRunnable> event = new MyEvent();
  NS_ENSURE_STATE(event);
  rv = NS_DispatchToCurrentThread(event);
  &nbsp;
  NS_ENSURE_SUCCESS(rv, rv);
thread->Dispatch(event, NS_DISPATCH_NORMAL);


== From JS ==
== From JS ==
Line 357: Line 329:
* The Necko I/O thread pool will be replaced by a generic thread pool implementation provided by XPCOM.  The thread pool will implement <code>nsIEventTarget</code>, allowing events to be dispatched to any thread in the thread pool.  We may even wish to define a <code>nsIThreadPool</code> interface and have it implement that as well.  That interface would define methods to adjust the limits on the number of threads in the thread pool and so on.
* The Necko I/O thread pool will be replaced by a generic thread pool implementation provided by XPCOM.  The thread pool will implement <code>nsIEventTarget</code>, allowing events to be dispatched to any thread in the thread pool.  We may even wish to define a <code>nsIThreadPool</code> interface and have it implement that as well.  That interface would define methods to adjust the limits on the number of threads in the thread pool and so on.
* We will need an alternative for <code>nsIEventQueue::RevokeEvents</code>.  There are several possibilities: (1) make consumers hold references to their nsIRunnable's so they can "disconnect" them manually, or (2) expose an enumeration/visitor API that allows consumers to walk the list of pending tasks and remove arbitrary tasks.  I'm not sure what is best yet.  The first option or something similar based on weak references is probably best as it avoids the costly O(n) <code>RevokeEvents</code> call.
* We will need an alternative for <code>nsIEventQueue::RevokeEvents</code>.  There are several possibilities: (1) make consumers hold references to their nsIRunnable's so they can "disconnect" them manually, or (2) expose an enumeration/visitor API that allows consumers to walk the list of pending tasks and remove arbitrary tasks.  I'm not sure what is best yet.  The first option or something similar based on weak references is probably best as it avoids the costly O(n) <code>RevokeEvents</code> call.
* The following interfaces/classes would go away or be modified heavily: nsIEventQueue, nsIEventQueueService, nsIEventTarget, nsIThread, PLEvent.
* The following interfaces/classes would go away or be modified heavily: <code>nsIEventQueue</code>, <code>nsIEventQueueService</code>, <code>nsIEventTarget</code>, <code>nsIThread</code>, <code>PLEvent</code>.
* Issue: Perhaps we should support calling <code>nsIThread::Shutdown</code> from the thread itself.  That would probably have to be implemented by posting an event to the main thread, and having that thread call shutdown.  However, <code>Shutdown</code> also has the property that it does not return until the thread is gone.  That obvious can't happen if <code>Shutdown</code> is called on the thread itself.
* Issue: Perhaps we should support calling <code>nsIThread::Shutdown</code> from the thread itself.  That would probably have to be implemented by posting an event to the main thread, and having that thread call shutdown.  However, <code>Shutdown</code> also has the property that it does not return until the thread is gone.  That obvious can't happen if <code>Shutdown</code> is called on the thread itself.


Line 369: Line 341:
== Development ==
== Development ==


Development is occuring on the THREADS_20060213_BRANCH.  See also {{bug|326273}}.
Development is occuring on the THREADS_20060307_BRANCH.  See also {{bug|326273}}.


== Comments ==
== Comments ==
Line 383: Line 355:
* If you are sync dispatching, then it is necessary to pump events on the calling thread or else you could end up in a dead-lock situation.  For example, if thread A sync dispatches an event to thread B, which cannot complete its job without dispatching an event back to thread A, then the system will dead-lock unless thread A continues to dispatch pending events.
* If you are sync dispatching, then it is necessary to pump events on the calling thread or else you could end up in a dead-lock situation.  For example, if thread A sync dispatches an event to thread B, which cannot complete its job without dispatching an event back to thread A, then the system will dead-lock unless thread A continues to dispatch pending events.
* <code>ProcessNextEvent</code> may be called by anybody that wishes to implement the concept of a "modal event loop."  For example, XMLHttpRequest (in sync configuration) would sit in a loop calling <code>ProcessNextEvent</code> while waiting for the underlying HTTP transaction to complete.  The key difference between <code>ProcessNextEvent</code> and <code>nsIEventQueue::ProcessPendingEvents</code> is that <code>ProcessNextEvent</code>, when called on the UI thread, will process UI events as well.  And, it will also wait (block the thread of execution) if there are no UI events or pending <code>nsIRunnable</code> events.
* <code>ProcessNextEvent</code> may be called by anybody that wishes to implement the concept of a "modal event loop."  For example, XMLHttpRequest (in sync configuration) would sit in a loop calling <code>ProcessNextEvent</code> while waiting for the underlying HTTP transaction to complete.  The key difference between <code>ProcessNextEvent</code> and <code>nsIEventQueue::ProcessPendingEvents</code> is that <code>ProcessNextEvent</code>, when called on the UI thread, will process UI events as well.  And, it will also wait (block the thread of execution) if there are no UI events or pending <code>nsIRunnable</code> events.
=== dbaron: ===
So mrbkap was telling me at lunch about what's essentially the third bullet point of your comment to biesi above -- that you're making the concept of "modal event loop" even more powerful.  This actually scares me a good bit, because these modal event loops happen within another event -- an event that may have state on the stack (e.g., |this| parameters) that could be destroyed by the processing of further events, or may have posted events that rely on the event completing.  I really don't like the mix of programming models here that makes it very hard to write robust code, and I wish we could avoid this whole concept altogether rather than making it even more powerful, and thus more dangerous.  We already have crashes where frame trees get destroyed while processing an event that are related to the Windows API's way of doing the same thing, though (for, e.g., a DestroyWindow call or whatever it's called).
==== darin: ====
Yup, I'm aware of that issue.  In fact, the problem already exists today.  There are some places in the code where WaitPLEvent + HandleEvent are called in a loop waiting for an event to process.  That bypasses PL_ProcessPendingEvents recursion restriction.  PopThreadEventQueue does not behave as you would expect either.  It can cause ProcessPendingEvents to run for all but the PLEventQueue that is the subject of the current call to PL_ProcessPendingEvents on the stack.  That defeats the purpose of the nested event queue in the first place.
The problem you describe with layout events also occurs for nsIStreamListener implementations.  It would be really wierd if OnDataAvailable were called recursively, or if OnDataAvailable were called while the consumer is calling nsIChannel::AsyncOpen.  These specific problems are dealt with in nsBaseChannel.cpp by temporarily suspending the channel during callbacks.  That suppresses recusive stream listener calls.
Do you have a suggestion that would allow us to prevent this problem?  Is there a better way to make XMLHttpRequest.send, document.load, window.alert, etc. appear modal to the calling Javascript?
For C++ consumers, we'd have to basically eliminate modal dialogs altogether to avoid this problem.  Today, we have bugs where downloads stall and get disconnected all because the browser popped up a modal dialog and the dialog was not closed promptly enough by the user.  That's just unacceptable.  And, nsIPromptService is frozen.
I think the answer is to be careful about making callbacks across XPCOM boundaries where event queues may be pumped.  Maybe the layout code needs to unwind the stack more before allowing such callbacks to happen so that it can be sure that event processing won't be the end of the world.
dbaron: Also, with the new push/popEventQueue API defined on nsIThreadInternal, it is possible to suppress event dispatch for a particular scope.  That is needed to implement synchronous XPCOM proxy calls.  I'm assuming that any proper use of synchronous XPCOM proxy calls will be one that completes relatively quickly so as to negate the effects of locking up the browser.  We should probably be very careful about calling PushEventQueue on the main thread, where it would potentially disrupt the browser UI.  Native events would still be dispatched as always, but without application level events, the UI isn't very useful.
272

edits