PPS clients can subscribe to multiple objects, and PPS objects can have multiple subscribers. When a publisher changes an object, all clients subscribed to that object are informed of the change.
To subscribe to an object, a client simply calls open() for the object with O_RDONLY to subscribe only, or O_RDWR to publish and subscribe. The subscriber can then query the object with a read() call.
A read returns the length of the data read, in bytes. If the allocated read buffer is too small for the data being read in, the read fails.
By default, reads to PPS objects are non-blocking; that is, PPS defaults a normal open() to O_NONBLOCK, and reads made by the client that opened the object do not block. This behavior is atypical for most filesystems. It is implemented so that standard utilities will not hang waiting for a change when they make a read() call on a file.
For example, with the default behavior, you could tar up the entire state of PPS using the standard tar utility. Without this default behavior, however, tar would never make it past the first file opened and read.
Though the PPS default is to open objects for non-blocking reads, the preferred method for querying PPS objects is to use blocking reads. With this method, a read waits until the object or its attributes change, then returns data.
To have reads block, you need to open the object with the ?wait pathname open option, appended as a suffix to the pathname for the object. For example:
For information about ?wait and other pathname open options, see the chapter Options and Qualifiers.
A typical loop in a subscriber would live in its own thread. For a subscriber that used the opened the object with the ?wait option, this loop might do the following:
/* Assume that the object was opened with the ?wait option No error checking in this example. */ for(;;) { read(fd, buf, sizeof(buf)); // Read waits until the object changes. process(buf); }
If you have opened an object without the ?wait option, and want to change to blocking reads, you can clear the O_NONBLOCK bit, so that the subscriber waits for changes to an object or its attributes.
To clear the bit you can use the fcntl() function. For example:
flags = fcntl(fd, F_GETFL); flags &= ~O_NONBLOCK; fcntl(fd, F_SETFL, flags);
Or you can use the ioctl() function:
int i=0; ioctl(fd,FIONBIO,&i);
After clearing the O_NONBLOCK bit, you can issue a read that waits until the object changes.
The PPS service implements io_notify() functionality, allowing subscribers to request notification via a PULSE, SIGNAL, SEMAPHORE, etc. On notification of a change, a subscriber must issue a read() to the object file to get the contents of the object. For example:
/* Process events while there are some */ while(ionotify(fd, _NOTIFY_ACTION_POLLARM, _NOTIFY_COND_INPUT, &event) & _NOTIFY_CONT_INPUT) { if(read(fd, buf, sizeof(buf)) > 0) // Best to read with O_NONBLOCK process(buf); } /* The event will be triggered in the future to get our attention */
You can use either one of two simple mechanisms to receive notifications that data is available on a file descriptor:
A subscriber can open an object in full mode, in delta mode, or in full and delta modes at the same time. The default is full mode. To open an object in delta mode, you need to open the object with the ?delta pathname open option, appended as a suffix to the pathname for the object.
For information about ?delta and other pathname open options, see the chapter Options and Qualifiers.
In full mode (the default), the subscriber always receives a single, consistent version of the entire object as it exists at the moment when it is requested.
If a publisher changes an object several times before a subscriber asks for it, the subscriber receives the state of the object at the time of asking only. If the object changes again, the subscriber is notified again of the change. Thus, in full mode, the subscriber may miss multiple changes to an object — changes to the object that occur before the subscriber asks for it.
In delta mode, a subscriber receives only the changes (but all the changes) to an object's attributes.
On the first read, since a subscriber knows nothing about the state of an object, PPS assumes everything has changed. Therefore, a subscriber's first read in delta mode returns all attributes for an object, while subsequent reads return only the changes since that subscriber's previous read.
Thus, in delta mode, the subscriber always receives all changes to an object.
The figure below illustrates the different information sent to subscribers who open a PPS object in full mode and in delta mode.
In all cases PPS maintains persistent objects with states — there is always an object. The mode used to open an object does not change the object; it only determines the subscriber's view of changes to the object.
When a subscriber opens an object in delta mode, the PPS service creates a new queue of object changes. That is, if multiple subscribers open an object in delta mode, each subscriber has its own queue of changes to the object, and the PPS service sends each subscriber its own copy of the changes. If no subscriber has an object open in delta mode, the PPS service does not maintain any queues of changes to that object.
On shutdown, the PPS service saves its objects, but objects' delta queues are lost. |
If a publisher changes multiple attributes with a single write() call, then PPS keeps the deltas together and returns them in the same group on a subscriber's read() call. In other words, PPS deltas maintain both time and atomicity of changes. For example:
write() write() time::1.23 time::1.24 duration::4.2 write() duration::4.2 read() read() @objname @objname time::1.23 time:1.24 duration::4.2 @objname duration::4.2
When a client writes to a server object, only the application that created it with the ?server option (called the "server") will get the message. Other clients cannot see that message.
At write-time, PPS appends a unique identifier to the object name so that the "server" knows which client connection is sending the message. This allows the connection to have stateful information. For example:
@foo.1234
indicates object foo with client identifier 1234. When a client connects, the server reads a new object that is prefixed with a + symbol (for example, +@foo.1234). Disconnects are sent to the "server" and the + prefix is changed to a - prefix.
When a server replies, it must write the object name with the unique identifier appended so that the response is sent only to the client that is identified by the unique identifier. If a server does not append the unique identifier to the object name, the message will be broadcast to all clients that are connected to the object.
When an object is opened with the ?server option it automatically becomes a critical publisher of that object. It also automatically receives notifications in delta mode.
The following figure shows a PPS transaction using the ?server option:
PPS supports three special objects which facilitate subscribing to multiple objects:
PPS uses directories as a natural grouping mechanism to simplify and make more efficient the task of subscribing to multiple objects. Subscribers can open multiple objects, either by calling open() then select() on their file descriptors. More easily, they can open the special .all object, which merges all objects in its directory.
For example, assume the following object file structure under /pps:
rear/left/PlayCurrent rear/left/Time rear/left/PlayError
If you open rear/left/.all you will receive a notification when any object in the rear/left directory changes. A read in full mode will return at most one object per read.
read() @Time position::18 duration::300 read() @PlayCurrent artist::The Beatles genre::Pop ... the full set of attributes for the object
If you open a .all object in delta mode, however, you will receive a queue of every attribute that changes in any object in the directory. In this case, a single read() call may include multiple objects.
read() @Time position::18 @Time position::19 @PlayCurrent artist::The Beatles genre::Pop
PPS provides a mechanism to associate a set of file descriptors with a notification group. This mechanism allows you to read only the PPS special notification object to receive notification of changes to any of the objects associated with that notification group.
To create a notification group:
To associate a file descriptor to a group, on an open, specify the pathname open option ?notify=group:value, where:
The returned notification group string has a trailing linefeed character, which you must remove before using the string. |
For information about ?notify and other pathname open options, see the chapter Options and Qualifiers.
Once you have created a notification group and associated file descriptors to it, you can use this group to learn about changes to any of the objects associated with it.
Whenever there is data available for reading on any of the group's file descriptors, reads to the notification object's file descriptor return the string passed in the ?notify=group:value pathname option.
For example, with PPS is mounted at /pps, you could write something like the following:
char noid[16], buf[128]; int notify_fd, fd1, fd2; notify_fd = open("/pps/.notify", O_RDONLY); read(notify_fd, &noid[0], sizeof(noid)); sprintf(buf, "/pps/fish?notify=%s:water", noid); fd1 = open(buf, O_RDONLY); sprintf(buf, "/pps/dir/birds?notify=%s:air", noid); fd2 = open(buf, O_RDONLY); while(read(notify_fd, &buf, sizeof(buf) > 0) { printf("Notify %s\n", buf); }
The data printed from the “while” loop in the example above would look something like the following:
Notify 243:water Notify 243:water Notify 243:air Notify 243:water Notify 243:air
When reading from an object that is bound to a notification group, a subscriber should do multiple reads for each change indicated. There may be more than one change on an item, but there is no guarantee that every change will be indicated on the notification group's file descriptor. |
If a file descriptor for an object which is part of a notification group is closed, the string passed with the change notification is prefixed by a minus (“-”) sign. For example:
-243:air