XPCOM:nsIThreadManager

From MozillaWiki
Revision as of 05:11, 23 February 2006 by Darin (talk | contribs) (→‎Remarks)
Jump to navigation Jump to search

In a world without nested event queues, the concept of a thread and an event queue merge...

Interfaces

[scriptable, uuid(...)]
interface nsIThreadManager : nsISupports {
  /**
   * Create a new named thread (a global, user PRThread).  If the name is
   * non-empty, then the name of the thread must be unique.  Specifying an
   * empty name results in an anonymous thread that cannot be found later on
   * using the getThread method.
   */
  nsIThread newThread(in ACString name);
   
  /**
   * Find a named thread.
   */
  nsIThread getThread(in ACString name);
   
  /**
   * Get the main thread.
   */
  nsIThread getMainThread();
   
  /**
   * Get the current thread.
   */
  nsIThread getCurrentThread();
   
  /**
   * Set an external nsIThread instance (or null) as the nsIThread for the
   * current thread.  If a nsIThread is already associated with the calling
   * thread, then this function will replace it with the given nsIThread.  If
   * the given nsIThread is non-null, then its name attribute must be unique.
   * Its name may be equal to the name of the nsIThread being replaced.  This
   * method returns the nsIThread that was replaced by this method call or null
   * if there was no previous nsIThread associated with the current thread.
   */
  nsIThread setCurrentThread(in nsIThread thread);
   
  /**
   * This method returns true if the calling thread is the main thread of the
   * application process.
   */
  boolean isMainThread();
};
[scriptable, uuid(...)]
interface nsIDispatchTarget : nsISupports {
  /**
   * Dispatch a task to the target.  This function may be called from any
   * thread.  If flags specifies DISPATCH_SYNC, then the dispatch method
   * will not return until the task has been run.  NOTE: Calling dispatch
   * with DISPATCH_SYNC, may have the side-effect of running other tasks
   * on the current thread while waiting for the given task to run to
   * completion.  This function is re-entrant.
   */
  void dispatch(in nsIRunnable task, in unsigned long flags);
  const unsigned long DISPATCH_NORMAL = 0;
  const unsigned long DISPATCH_SYNC   = 1;
   
  /**
   * Returns true if tasks dispatched to this target will run on the
   * current thread (i.e., the thread calling this method).
   */
  boolean isOnCurrentThread();
};
[scriptable, uuid(...)]
interface nsIThread : nsIDispatchTarget {
  /**
   * Returns the name of the thread, which may be empty if this thread is
   * anonymous.
   */
  readonly attribute ACString name;
   
  /**
   * Shutdown the thread.  This method may not be executed from the thread
   * itself.  Instead, it is meant to be executed from another thread (usually
   * the thread that created this thread).  When this function returns, the
   * thread will be shutdown, and it will no longer be possible to dispatch
   * tasks to the thread.
   */
  void shutdown();
   
  /**
   * Run the next task assigned to this thread.  This function should block
   * execution of the current thread until a task is available and run.
   * This function is re-entrant but may only be called if this thread is the
   * current thread.
   * @throws NS_BASE_STREAM_WOULD_BLOCK if RUN_NO_WAIT is specified and there
   * were no tasks to run.
   */
  void runNextTask(in unsigned long flags);
  const unsigned long RUN_NORMAL  = 0;
  const unsigned long RUN_NO_WAIT = 1;
};

Remarks

  • Thread priority for native threads can be exposed via nsISupportsPriority
  • Custom threads like the socket transport thread and the primordial thread will use setCurrentThread. The primordial thread will need to interact with the "native" event system during calls to runNextTask, and the socket transport thread will need to poll sockets and perform i/o operations during calls to runNextTask.
  • Thread names must be unique.
  • The Necko I/O thread pool would be modified to implement nsIDispatchTarget instead of nsIEventTarget. We may even wish to define a nsIThreadPool interface and have it implement that as well.
  • We will need an alternative for nsIEventQueue::revokeEvents. 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 following interfaces/classes would go away or be modified heavily: nsIEventQueue, nsIEventQueueService, nsIEventTarget, nsIThread, PLEvent.
  • Issue: Perhaps we should support calling nsIThread::shutdown 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, shutdown also has the property that it does not return until the thread is gone. That obvious can't happen if shutdown is called on the thread itself.

How do synchronous tasks work?

When dispatch(..., DISPATCH_SYNC) is called on a nsIDispatchTarget, it first checks to see if the current thread is the same as the thread where the task would be run. If it is, then it just runs the task directly without queuing it up. However, if not, then it must queue up the task to be run the dispatch target processes its tasks. To avoid returning to the caller immediately, the method gets the current thread and calls its runNextTask method in a loop until it receives acknowledgement that the queued task has been run.

Development

Development is occuring on the THREADS_20060213_BRANCH. See also bug 326273.

Comments (biesi)

  • What happens if shutdown is called while a task is being processed? I assume that this task will be processed to its end, no further tasks are accepted, and then the thread is shut down. right?
    • What happens to tasks that were already dispatched to this thread, but not run?
  • Is it a good idea to allow other tasks to run on the current thread while in dispatch?
  • Who is expected to call runNextTask?

Responses (darin)

  • When asked to shutdown, a thread would finish dispatching all queued tasks, and refuse to accept any new tasks. Once all queued tasks run to completion, the thread would terminate. This is consistent with how event queues work today.
  • 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 a task to thread B, which cannot complete its job without dispatching a task back to thread A, then the system will dead-lock unless thread A continues to dispatch pending tasks.
  • runNextTask 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 runNextTask while waiting for the underlying HTTP transaction to complete. The key difference between runNextTask and nsIEventQueue::processPendingEvents is that runNextTask, 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 tasks.