Fixing Threading
----------------

1) Observations 

 The following observations are made when considering the current (17/11/2004)
 problems in gstreamer.

 - Bin state changes.
   Currently the state of a bin is determined by the highest state of the 
   children, This is in particular a problem for GstThread because a thread
   should start/stop spinning at any time depending on the state of a child.
   
    ex 1:

    +-------------------------------------+
    | GstThread                           |
    | +--------+   +---------+   +------+ |
    | | src    |   | decoder |   | sink | |
    | |       src-sink      src-sink    | |
    | +--------+   +---------+   +------+ |
    +-------------------------------------+

   When performing the state change on the GstThread to PLAYING, one of the
   children (at random) will go to PLAYING first, this will trigger a method
   in GstThread that will start spinning the thread. Some elements are not yet
   in the PLAYING state when the scheduler starts iterating elements. This
   is not a clean way to start the data passing.

   State changes also trigger negotiation and scheduling (in the other thread)
   can do too. This creates races in negotiation.

 - ERROR and EOS conditions triggering a state change

   A typical problem is also that since scheduling starts while the state change
   happens, it is possible that the elements go to EOS or ERROR before the
   state change completes. Currently this makes the elements go to PAUSED again,
   creating races with the state change in progress. This also gives the 
   impression to the core that the state change failed. 

 - no locking whatsoever

   When an element does a state change, it is possible for another thread to 
   perform a conflicting state change. 

 - negotiation is not designed to work over multithread boundaries.

   negotiation over a queue is not possible. There is no method or policy of
   discovering a media type and then commiting it. It is also not possible to
   tie the negotiated media to the relevant buffer. 

   ex1:
     it Should be possible to queue the old and the new formats in a queue.
     The element connected to the sinkpad of the queue should be able to
     find out that the new format will be accepted by the element connected
     on the srcpad of the queue, even if that element is streaming the old
     format.

      +------------------------------+
      | GstQueue                     |
      |   +++++++++++++++++++++++++  |
    -sink |B|B|B|B|B|B|A|A|A|A|A|A| src-
      |   +++++++++++++++++++++++++  |
      +------------------------------+
          +----------+ +----------+
           buffers in   buffers in 
           new format   old format
                 
  - element properties are not threadsafe

    When setting an element property while streaming, the element does no
    locking whatsoever to guarantee its internal consistency.

  - No control over streaming.

    When some GstThread is iterating and you want to reconnect a pad, there
    is no way to block the pad, perform the actions and then unblock it
    again. This leads to thread problems where a pad is negotiation at the
    same time that it is passing data.

    This is currently solved by PAUSING the pipeline or performing the actions
    in the same threadcontext as the iterate loop.

  - race conditions in synchronizing the clocks and spinning up the pipeline.
    Currently the clock is started as soon as the pipeline is set to playing.
    Because some time elaspes before the elements are negotiated, autoplugged
    and streaming, the first frame/sample almost always arrives late at the
    sinks. Hacks exist to adjust the element base time to compensate for the
    delay but this clearly is not clean.

  - race conditions when performing seeks in the pipeline. Since the elements
    have no control over the streaming threads, they cannot block them or
    resync them to the new seek position. It is also hard to synchronize them
    with the clock.

  - race conditions when sending tags and error messages up the pipeline 
    hierarchy. These races are either caused by glib refcounting problems and
    by not properly locking.
  
  - more as changes are implemented and testcases are written

2) possible solutions

  - not allowing threading at all

    Run the complete pipeline in a single thread. Complications to solve include
    handling of blocking operations like source elements blocking in kernel
    space, sink elements blocking on the clock or kernel space, etc.. In practice,
    all operations should be made non-blocking because a blocking element can
    cause the rest of the pipeline to block as well and cause it to miss a deadline.
    A non-blocking model needs cooperation from the kernel (with callbacks) or
    requires the use of a polling mechanism, both of which are either impractical
    or too CPU intensive and in general not achievable for a general purpose
    Multimedia framework. For this reason we will not go further with this
    solution.

  - Allow threading.

    To make this work, We propose the following changes:

    - Remove GstThread, it does not add anything useful in a sense that you cannot
      arbitrarily place the thread element, it needs decoupled elements around the
      borders.

    - Simplify the state changes of bins elements. A bin or element never changes
      state automatically on EOS and ERROR.

    - Introduce the concept of the application and the streaming thread. All data
      passing is done in the streaming thread. This also means that all operations
      either are performed in the application thread or streaming thread and that
      they should be protected against competing operations in other threads.
      This would define a policy for adding appropriate locking.

    - Move the creation of threads into source and loop-based elements. This will
      make it possible for the elements in control of the threads to perform the
      locking when needed. One particular instance is for example the state changes,
      by creating the threads in the element, it is possible to sync the streaming
      and the application thread (which does the state change).

    - Remove negotiation from state changes. This will remove the conflict between
      streaming and negotiating elements.

    - add locks around pad operations like negotiation, streaming, linking, etc. This
      will remove races between these conflicting operations. This will also make it
      possible to un/block dataflow.

    - add locks around bin operations like add/removing elements. 

    - add locks around element operations like state changes and property changes.

    - add a 2-phase directed negotiation process. The source pad queries and figures
      out what the sinkpad can take in the first phase. In the second phase it sends
      the new format change as an event to the peer element. This event can be 
      interleaved with the buffers and can travel over queues inbetween the buffers.
      Need to rethink this wrt bufferpools (see DShow and old bufferpool implementation)
    
    - add a preroll phase that will be used to spin up the pipeline and align frames/samples
      in the sinks. This phase will happen in the PAUSED state. This also means that
      dataflow will happen in the PAUSED state. Sinks will not sink samples in the PAUSED
      state but will complete their state change asynchronously. This will allow 
      us to have perfect synchronisation with the clock.

    - a two phase seek policy. First the event travels upstream, putting all elements in
      the seeking phase and making them synchronize to the new position. In the 
      second phase the DISCONT event signals the end of the seek and all filters can
      continue with the new position.

    - Error messages, EOS, tags and other events in the pipeline should be sent to a 
      mainloop. The app then has an in-thread mechanism for getting information about
      the pipeline. It should also be possible to get the messages directly from the
      elements itself, like signals. The application programmer has to know that 
      working these events come from another thread and should handle them accordingly.

    - Add return values to push/pull so that errors upstream or downstream can be noted
      by other elements so that they can disable themselves or propagate the error.


3) detailed explanation

 a) Pipeline construction

   Pipeline construction includes:

     - adding/removing elements to the bin
     - finding elements in a bin by name and interface
     - setting the clock on a pipeline.
     - setting properties on objects
     - linking/unlinking pads

     These operations should take the object lock to make sure it can be
     executed from different threads.

   When connecting pads to other pads from elements inside another bin, 
   we require that the bin has a ghostpad for the pad. This is needed so
   that the bin looks like a self-contained element.

    not allowed:
                       +---------------------+
                       | GstBin              |
       +---------+     |   +--------+        |
       | element |     |   | src    |        |
      sink      src------sink     src- ...   |
       +---------+     |   +--------+        |
                       +---------------------+

    allowed:
                       +-----------------------+
                       | GstBin                |
                       |     +--------+        |
       +---------+     |     | src    |        |
       | element |     |   sink     src- ...   |
      sink      src---sink/  +--------+        |
       +---------+     +-----------------------+

   This requirement is important when we need to sort the elements in the
   bin to perfrom the state change.
     

   testcases:

    - create a bin, add/remove elements from it
    - add/remove from different threads and check the bin integrity.

  b) state changes

     An element can be in one of the four states NULL, READY, PAUSED, PLAYING.

      NULL: starting state of the element
      READY: element is ready to start running.
      PAUSED: element is streaming data, has opened devices etc.
      PLAYING: element is streaming data and clock is running

     Note that data starts streaming even in the PAUSED state. The only difference 
     between the PAUSED and PLAYING state is that the clock is running in the 
     PLAYING state. This mostly has an effect on the renderers which will block on 
     the first sample they receive when in PAUSED mode. The transition from 
     READY->PAUSED is called the preroll state. During that transition, media is
     queued in the pipeline and autoplugging is done. 

     Elements are put in a new state using the _set_state function. This function
     can return the following return values:

       typedef enum {
         GST_STATE_FAILURE             = 0,
         GST_STATE_PARTIAL             = 1,
         GST_STATE_SUCCESS             = 2,
         GST_STATE_ASYNC               = 3
       } GstElementStateReturn;
 
     GST_STATE_FAILURE is returned when the element failed to go to the 
     required state. When dealing with a bin, this is returned when one
     of the elements failed to go to the required state. The other elements 
     in the bin might have changed their states succesfully. This return
     value means that the element did _not_ change state, for bins this
     means that not all children have changed their state.

     GST_STATE_PARTIAL is returned when some elements in a bin where in the 
     locked state and therefore did not change their state. Note that the 
     state of the bin will be changed regardless of this PARTIAL return value.

     GST_STATE_SUCCES is returned when all the elements successfully changed their
     states.

     GST_STATE_ASYNC is returned when an element is going to report the success
     or failure of a state change later.

     The state of a bin is not related to the state of its children but only to
     the last state change directly performed on the bin or on a parent bin. This
     means that changing the state of an element inside the bin does not affect
     the state of the bin.

     Setting the state on a bin that is already in the correct state will
     perform the requested state change on the children.

     Elements are not allowed to change their own state. For bins, it is allowed
     to change the state of its children. This means that the application
     can only know about the states of the elements it has explicitly set.

     There is a difference in the way a pipeline and a bin handles the state
     change of its children:

      - a bin returns GST_STATE_ASYNC when one of its children returns an
        ASYNC reply.

      - a pipeline never returns GST_STATE_ASYNC but returns from the state
        change function after all ASYNC elements completed the state change.
        This is done by polling the ASYNC elements until they return their
        final state.

     The state change function must be fast an cannot block. If a blocking behaviour
     is unavoidable, the state change function must perform an async state change.
     Sink elements therefore always use async state changes since they need to
     wait before the first buffer arrives at the sink.

     A bin has to change the state of its children elements from the sink to the
     source elements.  This makes sure that a sink element is always ready to 
     receive data from a source element in the case of a READY->PAUSED state change.
     In the case of a PAUSED->READY state, the sink element will be set to READY 
     first so that the source element will receive an error when it tries to push 
     data to this element so that it will shut down as well.

     For loop based elements we have to be careful since they can pull a buffer 
     from the peer element before it has been put in the right state. 
     The state of a loop based element is therefore only changed after the source
     element has been put in the new state.

  c) Element state change functions

     The core will call the change_state function of an element with the element
     lock held. The element is responsible for starting any streaming tasks/threads
     and making sure that it synchronizes them to the state change function if 
     needed.

     This means that no other thread is allowed to change the state of the element
     at that time and for bins, it is not possible to add/remove elements.

     When an element is busy doing the ASYNC state change, it is possible that another
     state change happens. The elements should be prepared for this.
     
     An element can receive a state change for the same state it is in. This
     is not a problem, some elements (like bins) use this to resynchronize their
     children. Other elements should ignore this state change and return SUCCESS.
     
     When performing a state change on an element that returns ASYNC on one of
     the state changes, ASYNC is returned and you can only proceed to the next
     state change change when this ASYNC state change completed. Use the
     gst_element_get_state function to know when the state change completed.
     An example of this behaviour is setting a videosink to PLAYING, it will
     return ASYNC in the state change from READY->PAUSED. You can only set
     it to PLAYING when this state change completes.
     
     Bins will perform the state change code listed in d).
     
     For performing the state change, two variables are used: the current state
     of the element and the pending state. When the element is not performing a
     state change, the pending state == None. The state change variables are
     protected by the element lock. The pending state != None as long as the
     state change is performed or when an ASYNC state change is running.

     The core provides the following function for applications and bins to
     get the current state of an element:

       bool gst_element_get_state(&state, &pending, timeout);

     This function will block while the state change function is running inside
     the element because it grabs the element lock. 
     When the element did not perform an async state change, this function returns 
     TRUE immediatly with the state updated to reflect the current state of the 
     element and pending set to None. 
     When the element performed an async state change, this function will block 
     for the value of timeout and will return TRUE if the element completed the
     async state change within that timeout, otherwise it returns FALSE, with
     the current and pending state filled in.
     
     The algorithm is like this:

       bool gst_element_get_state(elem, &state, &pending, timeout)
       {
         g_mutex_lock (ELEMENT_LOCK);
         if (elem->pending != none) {
	   if (!g_mutex_cond_wait(STATE, ELEMENT_LOCK, timeout) {
	     /* timeout triggered */
	     *state = elem->state;
	     *pending = elem->pending;
	     ret = FALSE;
	   }
         }
         if (elem->pending == none) {
           *state = elem->state;
	   *pending = none;
           ret = TRUE;
         }
         g_mutex_unlock (ELEMENT_LOCK);

         return ret;
       }
     
     For plugins the following function is provided to commit the pending state, 
     the ELEMENT_LOCK should be held when calling this function:

       gst_element_commit_state(element)
       {
         if (pending != none) {
           state = pending;
           pending = none;
         }
         g_cond_broadcast (STATE);
       }

     For bins the gst_element_get_state() works slightly different. It will run
     the function on all of its children, as soon as one of the children returns
     FALSE, the method returns FALSE with the state set to the current bin state
     and the pending set to pending state. 

     For bins with elements that did an ASYNC state change, the _commit_state() 
     is only executed when actively calling _get_state(). The reason for this is 
     that when a child of the bin commits its state, this is not automatically 
     reported to the bin. This is not a problem since the _get_state() function 
     is the only way to get the current and pending state of the bin and is always 
     consistent.

  d) bin state change algorithm

     In order to perform the sink to source state change a bin must be able to sort 
     the elements. To make this easier we require that elements are connected to
     bins using ghostpads on the bin.

     The algoritm goes like this:

       d = [ ]                            # list of delayed elements
       p = [ ]                            # list of pending async elements
       q = [ elements without srcpads ]   # sinks
       while q not empty do
         e = dequeue q
         s = [ all elements connected to e on the sinkpads ]
         q = q append s
         if e is entry point
           d = d append e
         else
           r = state change e
           if r is ASYNC
             p = p append e
       done
       while d not empty do
         e = dequeue d
         r = state change e
         if r is ASYNC
           p = p append e
       done
       # a bin would return ASYNC here if p is not empty

       # this last part is only performed by a pipeline
       while p not empty do
         e = peek p
         if state completed e
           dequeue e from p
       done

     The algorithm first tries to find the sink elements, ie. ones without
     sinkpads. Then it changes the state of each sink elements and queues
     the elements connected to the sinkpads.

     The entry points (loopbased and getbased elements) are delayed as we 
     first need to change the state of the other elements before we can activate 
     the entry points in the pipeline.

     The pipeline will poll the async children before returning.
     
  e) The GstTask infrastructure

     A new component: GstTask is added to the core. A task is created by
     an instance of the abstract GstScheduler class.

     Each schedulable element (when added to a pipeline) is handed a
     reference to a GstScheduler. It can use this object to create
     a GstTask, which is basically a managed wrapper around a threading
     library like GThread. It should be possible to write a GstScheduler
     instance that uses other means of scheduling, like one that does not
     use threads but implements task switching based on mutex locking.
     
     When source and loopbased elements want to create the streaming thread
     they create an instance of a GstTask, which they pass a pointer to
     a loop-function. This function will be called as soon as the element
     performs GstTask.start(). The element can stop and uses mutexes to
     pause the GstTask from, for example, the state change function or the
     event functions.

     The GstTasks implement the streaming threads.

  f) the preroll phase

     Element start the streaming threads in the READY->PAUSED state. Since
     the elements that start the threads are put in the PAUSED state last,
     after their connected elements, they will be able to deliver data to
     their peers without problems.
  
     Sink elements like audio and videosinks will return an async state change
     reply and will only commit the state change after receiving the first
     buffer. This will implement the preroll phase.

     The following pseudo code shows an algorithm for commiting the state
     change in the streaming method.

	  GST_OBJECT_LOCK (element);
	  /* if we are going to PAUSED, we can commit the state change */
	  if (GST_STATE_TRANSITION (element) == GST_STATE_READY_TO_PAUSED) {
	    gst_element_commit_state (element);
	  }
	  /* if we are paused we need to wait for playing to continue */
	  if (GST_STATE (element) == GST_STATE_PAUSED) {

            /* here we wait for the next state change */
	    do {
	      g_cond_wait (element->state_cond, GST_OBJECT_GET_LOCK (element));
	    } while (GST_STATE (element) == GST_STATE_PAUSED);

            /* check if we got playing */
            if (GST_STATE (element) != GST_STATE_PLAYING) {
	      /* not playing, we can't accept the buffer */
	      GST_OBJECT_UNLOCK (element);
	      gst_buffer_unref (buf);
	      return GST_FLOW_WRONG_STATE;
	    }
	  }
	  GST_OBJECT_UNLOCK (element);

     

  g) return values for push/pull

     To recover from pipeline errors in a more elegant manner than just 
     shutting down the pipeline, we need more finegrained error messages
     in the data transport. The plugins should be able to know what goes
     wrong when interacting with their outside environment. This means
     that gst_pad_push/gst_pad_pull and gst_event_send should return a
     result code.

     Possible return values include:

      - GST_OK
      - GST_ERROR
      - GST_NOT_CONNECTED
      - GST_NOT_NEGOTIATED
      - GST_WRONG_STATE
      - GST_UNEXPECTED
      - GST_NOT_SUPPORTED

      GST_OK
        Data transport was successful

      GST_ERROR
        An error occured during transport, such as a fatal decoding error,
        the pad should not be used again.

      GST_NOT_CONNECTED
        The pad was not connected

      GST_NOT_NEGOTIATED
        The peer does not know what datatype is going over the pipeline.

      GST_WRONG_STATE
        The peer pad is not in the correct state.

      GST_UNEXPECTED
        The peer pad did not expect the data because it was flushing or
        received an eos.

      GST_NOT_SUPPORTED
        The operation is not supported.

     The signatures of the functions will become:

       GstFlowReturn gst_pad_push (GstPad *pad, GstBuffer *buffer);
       GstFlowReturn gst_pad_pull (GstPad *pad, GstBuffer **buffer);

       GstResult gst_pad_push_event (GstPad *pad, GstEvent *event);
        
	 - push_event will send the event to the connected pad.

     For sending events from the application:

       GstResult gst_pad_send_event (GstPad *pad, GstEvent *event);

  h) Negotiation

     Implement a simple two phase negotiation. First the source queries the
     sink if it accepts a certain format, then it sends the new format
     as an event. Sink pads can also trigger a state change by requesting
     a renegotiation.

  i) Mainloop integration/GstBus

     All error, warning and EOS messages from the plugins are sent to an event
     queue. The pipeline reads the messages from the queue and will either
     handle them or forward them to the main event queue that is read by the
     application.

     Specific pipelines can be written that deal with negotiation messages and
     errors in the pipeline intelligently. The basic pipeline will stop the
     pipeline when an error occurs.

     Whenever an element posts a message on the event queue, a signal is also
     fired that can be catched by the application. When dealing with those
     signals the application has to be aware that they come from the streaming
     threads and need to make sure they use proper locking to protect their
     own data structures.

     The messages will be implemented using a GstBus object that allows
     plugins to post messages and allows the application to read messages either
     synchronous or asynchronous. It is also possible to integrate the bus in
     the mainloop.

     The messages will derive from GstData to make them a lightweight refcounted
     object. Need to figure out how we can extend this method to encapsulate
     generic signals in messages too.

     This decouples the streaming thread from the application thread and should 
     avoid race conditions and pipeline stalling due to application interaction.

     It is still possible to receive the messages in the streaming thread context
     if an application wants to. When doing this, special care has to be taken
     when performing state changes.

  j) EOS

     When an element goes to EOS, it sends the EOS event to the peer plugin
     and stops sending data on that pad. The peer element that received an EOS
     event on a pad can refuse any buffers on that pad.

     All elements without source pads must post the EOS message on the message
     queue. When the pipeline receives an EOS event from all sinks, it will 
     post the EOS message on the application message queue so that the application
     knows the pipeline is in EOS. Elements without any connected sourcepads
     should also post the EOS message. This makes sure that all "dead-ends" 
     signalled the EOS.

     No state change happens when elements go to EOS but the elements with the
     GstTask will stop their tasks and so stop producing data.

     An application can issue a seek operation which makes all tasks running 
     again so that they can start streaming from the new location.
     


A) threads and lowlatency

  People often think it is a sin to use threads in low latency applications. This is true
  when using the data has to pass thread boundaries but false when it doesn't. Since
  source and loop based elements create a thread, it is possible to construct a pipeline
  where data passing has to cross thread boundaries, consider this case:

     +-----------------------------------+
     |      +--------+   +--------+      |
     |      |element1|   |element2|      |
     | .. -sink     src-sink     src- .. |
     |      +--------+   +--------+      |
     +-----------------------------------+

   The two elements are loop base and thus create a thread to drive the pipeline. At the
   border between the two elements there is a mutex to pass the data between the two 
   threads. When using these kinds of element in a pipeline, low-latency will not be
   possible. For low-latency apps, don't use these constructs!

   Note that in a typical pipeline with one get-based element and two chain-based
   elements (decoder/sink) there is only one thread, no data is crossing thread
   boundaries and thus this pipeline can be low-latency. Also note that while this
   pipeline is streaming no interaction or locking is done between it and the main
   application.

     +-------------------------------------+
     | +--------+   +---------+   +------+ |
     | | src    |   | decoder |   | sink | |
     | |       src-sink      src-sink    | |
     | +--------+   +---------+   +------+ |
     +-------------------------------------+


B) howto make non-threaded pipelines

  For low latency it is required to not have datapassing cross any thread
  borders. Here are some pointers for making sure this requirement is met:
  
  - never connect a loop or chain based element to a loop based element, this
    will create a new thread for the sink loop element.

  - do not use queues or any other decoupled element, as they implicitly 
    create a thread boundary.

  - At least one thread will be created for any source element (either in the
    connected loop-based element or in the source itself) unless the source
    elements are connected to the same loop based element. 

  - when designing sinks, make them non-blocking, use the async clock callbacks
    to schedule media rendering in the same thread (if any) as the clock. Sinks that
    provide the clock can be made blocking.