Using Publish and Subscribe

This topic explains the operations that can be performed using publish and subscribe.

Creating and closing a handle to a property

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();
    

Defining a property

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.

Deleting a 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);
    }
    

Publishing a property value

A property is published using the RProperty::Set() family of functions. Properties can be published:

  • using a previously attached RProperty handle,

or

  • by specifying the property category and key with the new value.

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

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

  • by specifying the property category and key with the new value.

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, and unsubscribing from, a property

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);
        }
    }
    

Usage patterns

There are three usage patterns that can easily be identified, labelled as: standard state, pure event distribution, and speculative publishing.

Standard state

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.

Pure event distribution

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.

Speculative publishing

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.

Making efficient use of the user side API

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.