docs/design/part-MT-refcounting.txt: Fill to 80 columns.

Original commit message from CVS:
2005-02-10  Andy Wingo  <wingo@pobox.com>

* docs/design/part-MT-refcounting.txt: Fill to 80 columns.
This commit is contained in:
Andy Wingo 2005-02-10 14:13:00 +00:00
parent 6e0dfb6f50
commit b1d9edff2f
2 changed files with 131 additions and 122 deletions

View file

@ -1,5 +1,7 @@
2005-02-10 Andy Wingo <wingo@pobox.com>
* docs/design/part-MT-refcounting.txt: Fill to 80 columns.
* gst/gstcaps.h:
* gst/gstcaps.c (gst_caps_get_writable): Change from
copy_on_write.

View file

@ -1,52 +1,53 @@
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.
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.
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.
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.
All objects 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.
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.
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.
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.
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.
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
@ -54,36 +55,38 @@ Copy-on-write:
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.
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.
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.
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.
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
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
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.
@ -99,72 +102,74 @@ Objects
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.
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.
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.
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.
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.
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.
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 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.
- 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.
- 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.
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.
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 {
...
@ -174,7 +179,7 @@ Objects
...
};
#define GST_RPAD_DIRECTION(pad) (GST_REAL_PAD_CAST(pad)->direction)
#define GST_RPAD_DIRECTION(pad) (GST_REAL_PAD_CAST(pad)->direction)
Accessing the property is therefore allowed with the following code example:
@ -184,22 +189,22 @@ Objects
* 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.
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.
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.
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);
@ -211,49 +216,51 @@ Objects
if (peer)
gst_object_unref (GST_OBJECT (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.
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.
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.
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 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:
If the object property is a list, concurrent list iteration is needed to get
the contents of the list. GStreamer uses the cookie 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
Updating the cookie is usually done by incrementing its value by one. Since cookies
use guint32 its wraparound is for all practical reasons is not a problem.
Updating the cookie is usually done by incrementing its value by one. Since
cookies use guint32 its wraparound is for all practical reasons is not a
problem.
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.
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.
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 */
@ -289,8 +296,8 @@ Objects
* GstIterator
GstIterator provides an easier way of retrieving elements in a concurrent list.
The followgin code example is equivalent to the previous example.
GstIterator provides an easier way of retrieving elements in a concurrent
list. The followgin code example is equivalent to the previous example.
Example: