mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-07 07:55:41 +00:00
docs/design/: Added design doc for MT-safe API implementation guidelines in GST.
Original commit message from CVS: * docs/design/part-MT-refcounting.txt: * docs/design/part-conventions.txt: Added design doc for MT-safe API implementation guidelines in GST.
This commit is contained in:
parent
65285e1acf
commit
56ac821b40
3 changed files with 298 additions and 1 deletions
|
@ -1,3 +1,10 @@
|
|||
2004-12-09 Wim Taymans <wim@fluendo.com>
|
||||
|
||||
* docs/design/part-MT-refcounting.txt:
|
||||
* docs/design/part-conventions.txt:
|
||||
Added design doc for MT-safe API implementation
|
||||
guidelines in GST.
|
||||
|
||||
2004-12-08 Thomas Vander Stichele <thomas at apestaart dot org>
|
||||
|
||||
* commited Wim's preliminary work according to his design to
|
||||
|
|
288
docs/design/part-MT-refcounting.txt
Normal file
288
docs/design/part-MT-refcounting.txt
Normal file
|
@ -0,0 +1,288 @@
|
|||
Conventions for thread a safe API
|
||||
---------------------------------
|
||||
|
||||
The GStreamer API is designed to be thread safe. This means that API functions can be
|
||||
called from multiple threads at the same time. GStreamer internally uses threads to
|
||||
perform the data passing and various asynchronous services such as the clock can also
|
||||
use threads.
|
||||
|
||||
This design decision has implication for the usage of the API and the objects which
|
||||
this document explains.
|
||||
|
||||
MT safety techniques
|
||||
--------------------
|
||||
|
||||
Several design patterns are used to guarantee object consistency in GStreamer. This
|
||||
is an overview of the methods used in various GStreamer subsystems.
|
||||
|
||||
Refcounting
|
||||
|
||||
All object have a refcount associated with them. Each reference obtained to the
|
||||
object should increase the refcount and each reference lost should decrease the
|
||||
refcount.
|
||||
|
||||
The refcounting is used to make sure that when another thread destroys the object,
|
||||
the ones which still hold a reference to the object do not read from invalid
|
||||
memory when accessing the object.
|
||||
|
||||
It is a requirement that when two threads have a handle to an object, the
|
||||
refcount must be one, this means that when one thread passes an object to another
|
||||
thread it must increase the refcount. This requirement makes sure that one
|
||||
thread cannot suddenly dispose the object making the other thread crash when it
|
||||
tries to access the pointer to invalid memory.
|
||||
|
||||
Copy-on-write:
|
||||
|
||||
All object have a refcount associated with them. Each reference obtained to the
|
||||
object should increase the refcount and each reference lost should decrease the
|
||||
refcount.
|
||||
|
||||
Each thread having a refcount to the object can safely read from the object. but
|
||||
modifications made to the object should be preceeded with a _copy_on_write()
|
||||
function call. This function will check the refcount of the object and if the
|
||||
object is referenced by more than one instance, a copy is made of the object that
|
||||
is then by definition only referenced from the calling thread. This new copy
|
||||
is then modifyable without being visible to other refcount holders.
|
||||
|
||||
This technique is used for information objects that, once created, never change
|
||||
their values. The lifetime of these objects is generally short, the objects are
|
||||
usually simple and cheap to copy/create.
|
||||
|
||||
The advantage of this method is that no reader/writers locks are needed. all
|
||||
threads can concurrently read but writes happen locally on a new copy. In most
|
||||
cases _copy_on_write() can avoid a real copy because the calling method is the
|
||||
only one holding a reference, wich makes read/writes very cheap.
|
||||
|
||||
The drawback is that sometimes 1 needless copy can be done. This would happen
|
||||
when N threads call _copy_on_write() at the same time, all seeing that N references
|
||||
are held to the object. In this case 1 copy too many will be done. This is not
|
||||
a problem in any practical situation because the copy operation is fast.
|
||||
|
||||
Object locking:
|
||||
|
||||
For objects that contain state information and generally have a longer lifetime,
|
||||
object locking is used to update the information contained in the object.
|
||||
|
||||
All readers and writers acquire the lock before accessing the object. Only one
|
||||
thread is allowed access the protected structures at a time.
|
||||
|
||||
Object locking is used for all objects extending from GstObject such as GstElement,
|
||||
GstPad.
|
||||
|
||||
Atomic operations
|
||||
|
||||
Atomic operations are operations that are performed as one consistent operation
|
||||
even when executed by multiple threads. They do however not use the conventional
|
||||
aproach of using mutexes to protect the critical section but rely on CPU features
|
||||
and instructions.
|
||||
|
||||
The advantages are mostly speed related since there are no heavyweight locks
|
||||
involved. Most of this instructions also do not cause a context switch in case
|
||||
of concurrent access but use a retry mechanism or spinlocking.
|
||||
|
||||
Disadvantages are that each of these instructions usually cause a cache flush
|
||||
on multi-CPU machines when two processors perform concurrent access.
|
||||
|
||||
Atomic operations are generally used for refcounting and for the allocation of
|
||||
small fixed size objects in a memchunk. They can also be used to implement a
|
||||
lockfree list or stack.
|
||||
|
||||
|
||||
Objects
|
||||
-------
|
||||
|
||||
* Locking involved:
|
||||
|
||||
- atomic operations for refcounting
|
||||
- object locking
|
||||
|
||||
All objects should have a lock associated with them. This lock is used to keep
|
||||
internal consistency when multiple threads call API function on the object.
|
||||
|
||||
For objects that extend the GStreamer base object class this lock can be obtained with
|
||||
the macros GST_LOCK() and GST_UNLOCK(). For other object that do not extend from the
|
||||
base GstObject class these macros can be different.
|
||||
|
||||
* refcounting
|
||||
|
||||
All new objects created have the FLOATING flag set. This means that the object is
|
||||
not owned or managed yet by anybody other than the one holding a reference to
|
||||
the object. The object in this state has a reference count of 1.
|
||||
|
||||
Various object methods can take ownership of another object, this means that after
|
||||
calling a method on object A with an object B as an argument, the object B is made
|
||||
sole property of object A. This means that after the method call you are not allowed
|
||||
to access the object anymore unless you keep an extra reference to the object.
|
||||
An example of such a method is the _bin_add() method. As soon as this function is
|
||||
called in a Bin, the element passed as an argument is owned by the bin and you
|
||||
are not allowed to access it anymore without taking a _ref() before adding it
|
||||
to the bin. The reason being that after the _bin_add() call disposing the bin
|
||||
also destroys the element.
|
||||
|
||||
Taking ownership of an object happens trough the process of "sinking" the object.
|
||||
the _sink() method on an object will decrease the refcount of the object if the
|
||||
FLOATING flag is set. The act of taking ownership of an object is then performed
|
||||
as a _ref() followed by a _sink() call on the object.
|
||||
|
||||
Sinking objects is very usefull because you don't have to worry about the lifetime
|
||||
of these sunken objects anymore since it is managed by the owner element.
|
||||
|
||||
* parent-child relations
|
||||
|
||||
One can create parent child relationships with the _object_set_parent() method. This
|
||||
method sinks the object and assigns its parent property to that of the managing
|
||||
parent.
|
||||
|
||||
The child is said to have a weak link to the parent since the refcount of the parent
|
||||
is not increased in this process. This means that if the parent is disposed it has
|
||||
to unset itself as the parent of the object before disposing itself, else the child
|
||||
object holds a parent pointer to invalid memory.
|
||||
|
||||
The responsabilites for an object that sinks other objects are summarised as:
|
||||
|
||||
- taking ownership of the object
|
||||
- call _object_set_parent() to set itself as the object parent, this call will
|
||||
_ref() and _sink() the object.
|
||||
- keep reference to object in a datastructure such as a list or array.
|
||||
|
||||
- on dispose
|
||||
- call _object_unparent() to reset the parent property and unref the object.
|
||||
- remove the object from the list.
|
||||
|
||||
|
||||
* Properties
|
||||
|
||||
Most objects also expose state information with public properties in the object. Two
|
||||
types of properties might exist: accessible with or without holding the object lock.
|
||||
All properties should only be accessed with their corresponding macros.
|
||||
The public object properties are marked in the .h files with /*< public >*/. The
|
||||
public properties that require a lock to be held are marked with
|
||||
/*< public >*/ /* with <lock_type> */, where <lock_type> can be "LOCK" or "STATE_LOCK"
|
||||
to mark the type(s) of lock to be held.
|
||||
|
||||
Example:
|
||||
|
||||
in GstPad there is a public property "direction". It can be found in the section
|
||||
marked as public and requiring the LOCK to be held. There exists also a macro
|
||||
to access the property.
|
||||
|
||||
struct _GstRealPad {
|
||||
...
|
||||
/*< public >*/ /* with LOCK */
|
||||
...
|
||||
GstPadDirection direction;
|
||||
...
|
||||
};
|
||||
|
||||
#define GST_RPAD_DIRECTION(pad) (GST_REAL_PAD_CAST(pad)->direction)
|
||||
|
||||
Accessing the property is therefore allowed with the following code example:
|
||||
|
||||
GST_LOCK (pad);
|
||||
direction = GST_RPAD_DIRECTION (pad);
|
||||
GST_UNLOCK (pad);
|
||||
|
||||
* Property lifetime
|
||||
|
||||
All properties requiring a lock can change after releasing the associated lock.
|
||||
This means that as soon as long as you hold the lock, the state of the object
|
||||
regarding the locked properties is consistent with the information obtained. As
|
||||
soon as the lock is released, any values required from the properties might not
|
||||
be valid anymore and can as best be described as a snapshot of the state when
|
||||
the lock was held.
|
||||
|
||||
This means that all properties that require access beyond the scope of the critial
|
||||
section should be copied or refcounted before releasing the lock.
|
||||
|
||||
Example:
|
||||
|
||||
the following example correctly gets the peer pad of an element. It is required
|
||||
to increase the refcount of the peer pad because as soon as the lock is released,
|
||||
the peer could be unreffed and disposed, making the pointer obtained in the critical
|
||||
section point to invalid memory.
|
||||
|
||||
GST_LOCK (pad);
|
||||
peer = GST_RPAD_PEER (pad);
|
||||
if (peer)
|
||||
gst_object_ref (GST_OBJECT (peer));
|
||||
GST_UNLOCK (pad);
|
||||
... use peer ...
|
||||
|
||||
Note that after releasing the lock the peer might not actually be the peer anymore
|
||||
of the pad. If you need to be sure it is, you need to extend the critical section
|
||||
to include the operations on the peer.
|
||||
|
||||
* Accessor methods
|
||||
|
||||
For aplications it is encouraged to use the public methods of the object. Most usefull
|
||||
operations can be performed with the methods so it is seldom required to access the
|
||||
public fields manually.
|
||||
|
||||
All accessor methods that return an object should increase the refcount of the returned
|
||||
object. The called should _unref the object after usage. Each method should state this
|
||||
refcounting policy in the documentation.
|
||||
|
||||
* Accessing lists
|
||||
|
||||
If the object property is a list concurrent list iteration is needed to get the
|
||||
contents of the list. GStreamer uses the cookie design pattern mechanism to
|
||||
mark the last update of a list. The list and the cookie are protected by the
|
||||
same lock. Each update to a list requires the following actions:
|
||||
|
||||
- acquire lock
|
||||
- update list
|
||||
- update cookie
|
||||
- release lock
|
||||
|
||||
Iterating a list can safely be done by surrounding the list iteration with a
|
||||
lock/unlock of the lock.
|
||||
|
||||
In some cases it is not a good idea to hold the lock for a long time while iterating
|
||||
the list. The state change code for a bin in GStreamer, for example, has to iterate
|
||||
over each element and perform a blocking call on each of them causing infinite bin
|
||||
locking. In this case the cookie can be used to iterate a list.
|
||||
|
||||
Example:
|
||||
|
||||
The following algorithm iterates a list and reverses the updates in the case a
|
||||
concurrent update was done to the list while iterating. The idea is that whenever
|
||||
we reacquire the lock, we check for updates to the cookie to decide if we are
|
||||
still iterating the right list.
|
||||
|
||||
GST_LOCK (lock);
|
||||
/* grab list and cookie */
|
||||
cookie = object->list_cookie;
|
||||
list = object-list;
|
||||
while (list) {
|
||||
GstObject *item = GST_OBJECT (list->data);
|
||||
/* need to ref the item before releasing the lock */
|
||||
gst_object_ref (item);
|
||||
GST_UNLOCK (lock);
|
||||
|
||||
... use/change item here...
|
||||
|
||||
/* release item here */
|
||||
gst_object_unref (item);
|
||||
|
||||
GST_LOCK (lock);
|
||||
if (cookie != object->list_cookie) {
|
||||
/* concurrent modification of the list here */
|
||||
|
||||
...undo changes to items...
|
||||
|
||||
/* grab new cookie and list */
|
||||
cookie = object->list_cookie;
|
||||
list = object->list;
|
||||
}
|
||||
else {
|
||||
list = g_list_next (list);
|
||||
}
|
||||
}
|
||||
GST_UNLOCK (lock);
|
||||
|
||||
* GstIterator
|
||||
|
||||
GstIterator provides an easier way of retrieving elements in a concurrent list. See
|
||||
the documentation of the GstIterator class.
|
||||
|
||||
|
|
@ -25,7 +25,9 @@ always tell you it's a function, however, regardless of the presence or absence
|
|||
|
||||
Values and macros defined as enums and preprocessor macros will be referred to in all capitals, as per their
|
||||
definition. This includes object flags and element states, as well as general enums. Examples are the states NULL,
|
||||
READY, PLAYING, and PAUSED; the element flags DECOUPLED and USE_COTHREAD, and state return values SUCCESS, FAILURE, and
|
||||
READY, PLAYING, and PAUSED; the element flags LOCKED_STATE , and state return values SUCCESS, FAILURE, and
|
||||
ASYNC. Where there is a prefix, as in the element flags, this is usually dropped, and implied. Not however that
|
||||
element flags should be cross-checked with the header, as there are currently two conventions in use: with and without
|
||||
_FLAGS_ in the middle.
|
||||
|
||||
FIXME: check flags for consistency.
|
||||
|
|
Loading…
Reference in a new issue