This topic explains the operations that can be performed using publish and subscribe.
Some property operations require
a reference to the property to be established first. This is done using the RProperty::Attach()
member
function. After a call to this function, the RProperty
object
acts like a standard handle to a kernel resource. When this handle is no longer
required, it can be released in the standard way by calling the inherited RHandleBase::Close()
member
function.
Note that releasing the handle does not cause the property to disappear. This only happens if the property is deleted.
Note also that it is quite legitimate to attach to a property that has not been defined, and in this case no error will be returned either. This enables the lazy definition of properties as used in some of the usage patterns.
// attach to the ‘counter’ property RProperty counter; TInt r=counter.Attach(KMyPropertyCat,EMyPropertyName,EOwnerThread); User::LeaveIfError(r); // use the counter object... // when finished, release the handle counter.Close();
A property is defined using the RProperty::Define()
function,
specifying the attributes of that property.
A property does not need to be defined before it can be accessed. This supports programming patterns where both publishers and subscribers may define the property. Note, however that for security reasons, there are restrictions on the category that can be used when defining a property; see security issues for more information.
Once defined, a property persists
in the kernel until the system reboots, or the property is explicitly deleted.
Its lifetime is not tied to that of the thread or process that originally
defined it. This means that, when defining a property, it is important to
check the return code from the call RProperty::Define()
to
deal with the possibility that the property has previously been defined, but
not deleted.
The following code shows the definition of two properties:
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName}; static _LIT_SECURITY_POLICY_PASS(KAllowAllPolicy); static _LIT_SECURITY_POLICY_C1(KPowerMgmtPolicy,ECapabilityPowerMgmt); // define first property to be integer type TInt r=RProperty::Define(EMyPropertyCounter,RProperty::EInt,KAllowAllPolicy,KPowerMgmtPolicy); if (r!=KErrAlreadyExists) { User::LeaveIfError(r); } // define second property to be a byte array, allocating 100 bytes r=RProperty::Define(EMyPropertyName,RProperty::EByteArray,KAllowAllPolicy,KPowerMgmtPolicy,100); if (r!=KErrAlreadyExists) { User::LeaveIfError(r); } . . .
Once defined, a property value can change, but the property type cannot. Byte-array type properties can also change length provided the length does not exceed the maximum value of 512 bytes. The limit on the size of a property ensures some limit on RAM usage.
The API allows byte-array and Unicode text type properties to be pre-allocated when they are defined. This means that the time taken to set the values is bounded. However, if the length of these property types subsequently increases, then memory allocation may take place, and no guarantees can then be made on the time taken to set them.
There are further security
issues to be considered when defining a property. You need to provide
two security policies - one to govern which processes can publish the property
value, and the other to govern which processes can retrieve the property value.
Security policies are instances of TSecurityPolicy
objects,
although for efficiency reasons, you will almost always use the _LIT_SECURITY_POLICY_...
macros
to generate constant objects that behave like TSecurityPolicy
objects.
The API reference for TSecurityPolicy
provides far more
detail on this.
In this example, all processes will be allowed to retrieve the property value, but only those processes having the power management system capability (ECapabilityPowerMgmt) will be allowed to publish the property value.
Note that a process that defines a property does not have automatic rights of access to that property, other than to delete it. If the defining process also wishes to publish and/or subscribe to that property, then it must ensure that it satisfies the security policies that it itself has put in place when defining the property.
A defined property is deleted by calling RProperty::Delete()
.
Any outstanding subscriptions for this property will complete with KErrNotFound.
Only the process with the correct secure ID is allowed to delete it.
For example, extending the code fragment introduced above:
enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName}; // define first property to be integer type TInt r=RProperty::Define(EMyPropertyCounter,RProperty::EInt); if (r!=KErrAlreadyExists) { User::LeaveIfError(r); } // define second property to be a byte array, allocating 100 bytes r=RProperty::Define(EMyPropertyName,RProperty::EByteArray,100); if (r!=KErrAlreadyExists) { User::LeaveIfError(r); } . . . // much later on . . . // delete the ‘name’ property r=RProperty::Delete(EMyPropertyName); if (r!=KErrNotFound) { User::LeaveIfError(r); }
A property is published using the RProperty::Set()
family
of functions. Properties can be published:
using a previously attached RProperty
handle,
or
The former mode is guaranteed to have bounded execution time, suitable for high-priority, real-time tasks, except when publishing a byte-array property that requires the allocation of a larger space for the new value.
The latter mode offers no real-time guarantees.
Property values are written atomically. This means that it is not possible for threads reading a property to get a garbled value.
All outstanding subscriptions for a property are completed when the value is published, even if it is exactly the same as the existing value. This means that a property can be used as a simple broadcast notification service.
Publishing a property that is not
defined is not necessarily a programming error. The Set()
functions
just return an error. If this is not expected for any particular usage, then
the error must be checked and processed by the caller.
See the code fragment in the section Retrieving a property value
The current value of a property is read using
the RProperty::Get()
family of functions. Properties can
be retrieved:
using a previously attached RProperty
handle,
or
The former mode is guaranteed to have bounded execution time, suitable for high-priority, real-time tasks.
The latter mode offers no real-time guarantees.
Property values are read atomically. This means that it is not possible for threads reading a property to get a garbled value.
Retrieving
a property that is not defined is not necessarily a programming error. The Get()
functions
just return an error. If this is not expected for any particular usage, then
the error must be checked and processed by the caller.
Integer properties must be accessed using the overloads that take an integer or integer reference value, whereas byte-array properties can be accessed using the overloads that take a descriptor reference.
The following code fragment shows publication and retrieval of a property. Note that it uses the idea of attaching to a property. Note also that it contains a race condition, especially if another thread is executing the same sequence to increment the ‘counter’ value.
const TUid KMyPropertyCat={0x10012345}; enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName}; // attach to the ‘counter’ property RProperty counter; TInt r=counter.Attach(KMyPropertyCat,EMyPropertyCounter,EOwnerThread); User::LeaveIfError(r); // publish a new name value TFileName n; RProcess().Filename(n); r=RProperty::Set(KMyPropertyCat,EMyPropertyName,n); User::LeaveIfError(r); // retrieve the first 10 characters of the name value TBuf<10> name; r=RProperty::Get(KMyPropertyCat,EMyPropertyName,name); if (r!=KErrOverflow) { User::LeaveIfError(r); } // retrieve and publish a new value using the attached ‘counter’ property TInt count; r=counter.Get(count); if (r==KErrNone) { r=counter.Set(++count); } User::LeaveIfError(r); // when finised, release the handle counter.Close();
Subscribing to a property is the act of making an asynchronous request to be notified of a change to that property.
A thread makes a request for notification of a change
to a property by calling the RProperty::Subscribe()
member
function on an already attached property object. Only one subscription request
can be outstanding at any one time for a RProperty
instance.
An
outstanding subscription request can be cancelled by calling the RProperty::Cancel()
member
function. This is unsubscribing from the property.
Subscribing to a property is a single request to be notified when the property is next updated, it does not generate an ongoing sequence of notifications for every change to that property's value. Neither does it provide the caller with the new value. In essence, the act of notification should be interpreted as “Property X has changed” rather than “Property X has changed to Y”. This means that the new value must be explicitly retrieved, if required. As a result, multiple updates may be collapsed into one notification, and subscribers may not have visibility of all intermediate values.
This might appear to introduce a window of opportunity for a subscriber to be out of synchronisation with the property value – in particular, if the property is updated again before the subscriber thread has had the chance to process the original notification. However, a simple programming pattern, outlined in the second example below ensures this does not happen. The principle is that, before dealing with a subscription completion event, an active object should re-issue the subscription request.
Note that if the property has not been defined, then a subscription
request does not complete until the property is subsequently defined and published.
Note that the request will complete with KErrPermissionDenied if
the subscribing process does not have sufficient capability as defined by
the TSecurityPolicy
object supplied by the process defining
the property.
If the property is already defined, then the request completes immediately with KErrPermissionDenied if the subscribing process does not have sufficient capability.
const TUid KMyPropertyCat={0x10012345}; enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName}; // attach to the ‘counter’ property RProperty counter; TInt r=counter.Attach(KMyPropertyCat,EMyPropertyCounter,EOwnerThread); User::LeaveIfError(r); // wait for the previously attached ‘counter’ property to be updated TRequestStatus s; counter.Subscribe(s); User::WaitForRequest(s); // Notification complete, retrieve the counter value. TInt count; counter.Get(count); . . .
const TUid KMyPropertyCat={0x10012345}; enum TMyPropertyKeys={EMyPropertyCounter,EMyPropertyName}; // Active object that tracks changes to the ‘name’ property class CPropertyWatch : public CActive { enum {EPriority=0}; public: static CPropertyWatch* NewL(); private: CPropertyWatch(); void ConstructL(); ~CPropertyWatch(); void RunL(); void DoCancel(); private: RProperty iProperty; }; CPropertyWatch* CPropertyWatch::NewL() { CPropertyWatch* me=new(ELeave) CPropertyWatch; CleanupStack::PushL(me); me->ConstructL(); CleanupStack::Pop(me); return me; } CPropertyWatch::CPropertyWatch() :CActive(EPriority) {} void CPropertyWatch::ConstructL() { User::LeaveIfError(iProperty.Attach(KMyPropertyCat,KMyPropertyName)); CActiveScheduler::Add(this); // initial subscription and process current property value RunL(); } CPropertyWatch::~CPropertyWatch() { Cancel(); iProperty.Close(); } void CPropertyWatch::DoCancel() { iProperty.Cancel(); } void CPropertyWatch::RunL() { // resubscribe before processing new value to prevent missing updates iProperty.Subscribe(iStatus); SetActive(); // property updated, get new value TFileName n; if (iProperty.Get(n)==KErrNotFound) { // property deleted, do necessary actions here... NameDeleted(); } else { // use new value ... NameChanged(n); } }
There are three usage patterns that can easily be identified, labelled as: standard state, pure event distribution, and speculative publishing.
This pattern is used for events and state that are known to be used widely in the system. Examples of this might be battery level and signal strength, which are important in every phone.
The publisher calls RProperty::Define()
to
create the appropriate property. For byte array or text properties, a size
sufficient for all possible values should be reserved. An error of KErrAlreadyExists should
be ignored. The publisher then publishes the property values as, and when,
appropriate. If the RProperty::Set()
call fails, this should
be treated as a serious error, since it indicates that important system state
is not getting through. Appropriate action might include panicking or rebooting
the system. Subscribers will use RProperty::Subscribe()
to
request notification, and RProperty::Get()
to retrieve the
property value on notification.
The memory to store the property value will be permanently allocated, even if it turns out that no-one in the system needs that value. This does ensure that the value can always be published, even if the system is in an out of memory situation. For this reason, this approach should be limited to widely used and important state. The Speculative publishing pattern offers an approach for dealing with less important state.
This pattern is used when events need to be distributed, not values.
The publisher of the event simply uses an integer property, and calls RProperty::Set() with any value. Even if the value of the property is not changed by this operation, all subscribers will be notified that a Set() has occurred, and by implication that the related event has occurred.
Subscribers will be able to detect that an event has occurred, but will get no other information. The minimum possible memory is wasted on storage for the dummy value.
This
pattern is used when it is not known whether a value will be of interest to
others or not. Unlike the standard
state pattern, the publisher of the event does not call RProperty::Define()
to
create the property. Instead, it simply calls RProperty::Set()
as
appropriate, and ignores any KErrNotFound error.
When
other code in the system, i.e. a potential subscriber, is interested in the
state, it calls RProperty::Define()
to create the property
and allocate the memory for the value. An error of KErrAlreadyExists should
be ignored, as this only indicates that some other code in the system is also
interested in the value and has already created the property.
The
subscriber then calls RProperty::Subscribe()
and RProperty::Get()
as
usual to interact with the property. On the first Get()
,
the subscriber may retrieve the property default value (zero, or a zero length
descriptor). This must be substituted with a sensible default value for the
property in question.
Using this pattern, no memory is wasted on properties that have no subscribers, while the publisher code is simpler as there is no need for configuration as to which properties to publish.
The publisher, however, wastes some time attempting to publish unneeded values, but this should not be an issue unless the value is very frequently updated.
Where events are published very infrequently, the subscriber could have a dummy value for a long time, until the next publish event updates the value. Often this is not a problem as a default value can be substituted. For example a full/empty indicator for a battery level, none for signal strength etc. This pattern is unlikely to be useful if there is no suitable default value.
While the Publish and Subscribe API, as
represented by RProperty
is designed to be efficient, there
are certain usage patterns that can improve performance.
Use the attached version of the API calls if possible
If you intend
to call Set()
or Get()
repeatedly, it is
preferable to use the attached forms of the calls. The attached forms are
the ones that do not take a UID/Key parameter, and that can only be called
after calling RProperty::Attach()
. The attached variants
are constant time operations, and execute much faster than the corresponding
unattached versions.
Only preallocate space if the publishing is time critical
For byte-array
and text properties, it is possible to pre-allocate space for the data. Doing
this results in Set()
operations that do not exceed the preallocated
space, avoiding the need to do a memory allocation. However, if the data is
shorter than the reserved space, the excess is wasted. Since Set()
automatically
extends the data area if needed, then the only reason to pre-allocate space
is if the Set()
operation has to be real-time, i.e. has to
have known execution time.
Always consider using Speculative Publishing
Even in situations where the Standard State pattern may seem appropriate, speculative publishing may be a better choice, specially for low-level components that know little about how they are used or what the wider system configuration may be. The onus is then on the UI/Policy layer to ensure that the appropriate properties are defined early on in device boot according to policy rules it can define. This ensures that the policy layers in the system maintain control and can implement a wide variety of policies.
Standard state is only relevant for properties that are essential to every Symbian device. Battery level probably falls into this category, signal strength may well not.
Define the expected update frequency
When a property is changed, all subscribers are notified. This leads to their threads running to service the notification. If a property changes value frequently, it would be wasteful for subscribers to perform substantial processing for each notification.
Take a property representing signal strength as an example. Potentially, this could be updated several times a second. If a change in value were only used to update the UI signal bar, it would not be harmful. However, if it were used by many entities for serious processing (e.g. polling for email, sending unsent SMSes, re-connecting to the internet), then such frequent updates would have a severe effect on battery life.
Nevertheless, it is obviously desirable for many parts of a phone OS to know about the state of network coverage, and to take appropriate action. In cases like this, it may be worth the publisher defining multiple properties with associated update characteristics. For example, raw signal strength (updated > 1 time/sec), periodic signal strength (updated once every 10s) and network coverage (updated only when moving between some signal and none). Each subscriber can then monitor the appropriate notification and so reduce the number of threads that run when the underlying value changes.