----------------------------------------------------------- - GStreamer Scheduling / Synchronization (incsched) Notes - ----------------------------------------------------------- These notes describe deadlock scenarios and proposed solutions for GStreamer. This will be implemented in the INCSCHED1 branch. I. Miscellaneous proposals II. Liveness problems (sometimes deadlock ;) and proposed solutions III. State transition approach and responsibility MattH. -------------------------------- - I. Miscalenous proposals - -------------------------------- 1. Change the names of GstThread and GstQueue to GstPtThread and GstPtQueue for pthread versions of Thread and Queue. 2. Change GstPtQueue to check its pads' peers' managers and make sure they are different. If not, fail and generate error message. (This ensures a GstPtQueue straddles a pthread boundary.) 3. Change state transitions to NULL <-> READY <-> PAUSED <-> PLAYING. --------------------------------------------------- - II. Deadlock Scenarios and Proposed Solutions - - (in the order they will be implemented) - --------------------------------------------------- 1. A downstream element "waits" for a buffer from its upstream element, a state change happens and "pauses" the upstream element -- the downstream element is blocked and cannot execute its change_state. Note that this can only happen within a single GstPtQueue! Either a downstream element calls Pull, finds no buffer, and does a wait_cond(new buffer) or an upstream element calls Push, finds no room, and does a wait_cond(new room). Thus, GstPtQueue contains all the cond_wait / signal code. => The managing container (thread, pipeline) "wakes" up any sleep conditions of its "bottom half". (In the scenario described, it wakes the blocked downstream element's call to Pull.) The GstPtQueue cond_wait section determines that it woke up due to a pending state change and does a cothread_switch(0) to return to the main loop, which then executes the state transition. Note that a managing container will have only one sleep condition in its "bottom half." 2. Element "blocked" on getting I/O and cannot execute its change_state. => We will provide an I/O library for the elements to use that does not actually block. (A retry-loop with timeout or select() on 2 -- or more -- file descriptors: one the one you want I/O from, the other one that GStreamer uses to "wake" everyone up.) The I/O library determines that it was woken due to a pending state change and does a cothread_switch(0) to return to the main loop, which then executes the state transition. Note that a managing container will have only one elements in the middle of doing blocking I/O. 3. Element using a library (code out of its control) which blocks for some reason (e.g., using real blocking I/O) so main loop never gets run to execute change_state. => Build in some timeout in the manging container (the "top half") when waiting for bottom half to respond to pending state. If managing container times out, kill the element's thread with a signal (or series of signals -- escalating priority). This requires that the element (the "bottom half") have matching signal handler(s) that execute(s) the state-transition. -------------------------------------------------------- - III. State-transition Approach and Responsibility - -------------------------------------------------------- A. The "top half" context of the managing container. (This is likely the context of the application.) Call change_state on the managing container (GstPipeline, GstPtThread). If its "bottom half" (main_loop) is asleep, signal the condition to wake it up. Then do a cond_wait for the "bottom half" to execute the state transition and return (once the state has been changed). B. The main_loop (the "bottom half") of the managing container. Needs to check for pending state transition after every switch back from one of its elements. If a pending state is found, it calls change_state on each of its elements, signals the "top half" that the state has been changed, then continues executing the plan (if Playing) or puts itself to sleep (Paused, Ready). C. Element. Implement a change_state function to make transition for that element. The elements' change_state is what actually changes the state variable and notifies the scheduler that the state was changed. This function may also do things like close or open resources. NOTE: when an element goes through certain state transitions (e.g., from Paused to Ready) its state (stack) will be wiped out. If it wants to preserve any state or data, it needs to store the information in a safe place. D. Cothread Scheduler. Gets notified of state transition by elements' change_state functions then (re)set the plan accordingly. Assuming NULL <-> READY <-> PAUSED <-> PLAYING, some would be + Paused -> Playing: jump back where you were in the Plan and continue its execution + Ready -> Paused: reset the cothread pointers foreach cothread in the Plan (don't run)