mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-01-08 08:25:33 +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>
|
2004-12-08 Thomas Vander Stichele <thomas at apestaart dot org>
|
||||||
|
|
||||||
* commited Wim's preliminary work according to his design to
|
* 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
|
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,
|
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
|
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
|
element flags should be cross-checked with the header, as there are currently two conventions in use: with and without
|
||||||
_FLAGS_ in the middle.
|
_FLAGS_ in the middle.
|
||||||
|
|
||||||
|
FIXME: check flags for consistency.
|
||||||
|
|
Loading…
Reference in a new issue