ObjectStore C++ API User Guide

Chapter 4

Notification

The information about notification is organized in the following manner:

Notification Overview

The notification service allows an ObjectStore client to notify other ObjectStore clients that an event has taken place. Typically, a notification event corresponds to the modification of an object in a database, but applications are free to assign their own meanings to events.

A notification broadcasts to subscribers that an event (for example, a change) has occurred at a database location (for example, the location of a persistent object). ObjectStore applications can subscribe to receive notifications that are posted on a database location, or on a range of database locations. If a range is specified in a subscription, notifications posted on any location in the range are received by the subscribing application. Subscribers can poll for notifications, or block (remain in a wait state) until a notification is posted.

When an application posts a notification, it specifies the database location, an integer code, and a character string. The code (known as kind) and the string are made available to subscribers when they receive the notification. The notification is sent to all processes that are subscribed to the location at the time of the posting.

Notification

A notification specifies a database location and two additional user-defined items: a signed 32-bit integer and a null-terminated C string. These items are passed to the receiving process. The string is limited to 16,383 characters. If a notification sends a null string (0), it is received as an empty string ("").

When a notification is posted, a message is sent to the ObjectStore Server. The Server then matches the notification with subscriptions and queues messages to be sent to the receiving processes. The Server returns the number of messages queued. Notifications then proceed asynchronously to the Cache Manager of the receiving processes.

Range of Locations

A range of locations must be contiguous. You can specify a range to be an entire database, a segment, a cluster, an object or a range of objects, or a portion of an object. You can subscribe to a range of locations (that is, every location in a segment, or an array of objects), or to a single location. A notification, however, is always associated with a single database location.

Subscription

A client can subscribe to many ranges of database locations simultaneously. Subscriptions are stored in the ObjectStore Server for as long as the corresponding database is open by the client.

You can unsubscribe to ranges just as you can subscribe to them. The unsubscription is immediate. When you close a database, that unsubscribes all notifications for the database.

Notification Queuing

The Cache Manager maintains a queue of notifications for each client on a machine.

Nothing forces a client to read notifications. A client could choose to subscribe to notifications but never receive any.

To avoid resource exhaustion in the Cache Manager, the size of the notification queue for each client is fixed. If a notification is received when the client's queue is full, it is discarded. This is called an overflow.

Overflows do not cause any exception to be signaled and do not cause the application, the Cache Manager, or the Server to crash.

The Cache Manager keeps statistics on the notification queue that include

These statistics are made available to clients through the use of an API. The API, os_dbutil::cmgr_stat(), is described in Managing Cache Managers in Chapter 10, Database Utility API, of the ObjectStore Advanced C++ API User Guide.

Receiving Notifications

Notifications are received in response to a call to os_notification::receive(). This function can also be used to wait for notifications. Applications can poll for notifications without retrieving them using os_notification::queue_status().

Notification Retrieval Alternatives

There are two main methods of retrieving notifications. One relies on a thread whose sole purpose is to receive notifications. The other method requires the application to poll to determine if notifications have arrived.



A third method available on UNIX systems is called file-descriptor-based retrieval. This method works on both threads and nonthread systems.

Thread-Based Notification Retrieval

Using this method, a dedicated thread is started specifically to receive notifications. This thread calls os_notification::receive() with arguments specifying wait forever. When it receives a notification, it performs an application-specific action. For example, it might post a Windows message, modify the application's transient data structures, or otherwise queue the notification for processing by another thread. It then waits for the next notification. The notification thread typically does very little work. It might do queue management, for example, maintaining a priority queue of notifications for another thread, or coalescing similar notifications. However, processing should be minimal, so the Cache Manager notification queue does not overflow.

In contrast to most other ObjectStore APIs, os_notification::receive() and os_notification::queue_status() are not locked out when other threads are in ObjectStore operations. If the thread does not access persistent data or call other ObjectStore APIs, it can run entirely asynchronously.

Polling-Based Notification Retrieval

Using this method, the application periodically polls to see if notifications have arrived. It does so using os_notification::queue_status() or os_notification::receive(). The application can do this polling in a main loop, or under control of timers or similar features provided by the environment. This mechanism is less flexible and less efficient than thread-based notification retrieval, but it is a reasonable option on platforms not offering threads. There are situations where polling can be quite efficient. If you are uncertain about the conditions affecting the level of efficiency, contact Object Design's Consulting Services group for assistance, or consult a programming text such as UNIX Network Programming by W. Richard Stevens. UNIX systems that do not support threads can make use of File-Descriptor-Based Notification Retrieval as described below.

The application should only check whether notifications have arrived. The application should not wait indefinitely (forever) for a notification, because it might be holding a lock, and the application expected to send the notification might be waiting for that lock. By waiting forever for a notification, you could create a deadly embrace. The Server's deadlock-detection mechanism cannot detect this.

File-Descriptor-Based Notification Retrieval



On all UNIX platforms, ObjectStore can provide a file descriptor on which notifications arrive. This feature is not currently available on Windows or OS/2 platforms.

The clients poll or wait for notifications using the operating system functions select() or poll(). This is particularly useful in nonthreaded environments, where applications are designed to wait for multiple events by doing a multiplexed wait for activity on a set of file descriptors (fds). Many Motif implementations, for example, fall into this category.

General Notification Behavior

The following sections describe the main characteristics of notification.

Subscribing and Unsubscribing

Subscribing is accomplished by means of static member functions of class os_notification. See the ObjectStore C++ API Reference description of the os_notification class.

You can unsubscribe to ranges just as you can subscribe to them. The unsubscription is immediate.

Discarded subscriptions
Closing a database for any reason unsubscribes all notifications for the database; that is, all the subscriptions are discarded. Therefore, it is the application's responsibility to reinstitute the subscriptions.

Asynchronous processing
Notifications are processed asynchronously. After unsubscribing, notifications that might already be queued based on previous subscriptions might result in a client's receiving notifications even after unsubscribing. ObjectStore makes no guarantees whether such notifications will be received or not.

Transactions

Transactions are entirely independent of immediate notifications, subscriptions, unsubscriptions, and notification retrieval. Sending of commit-time notifications is closely integrated with transactions. Commit-time notifications can only be queued inside a transaction, and they are only sent if

There are no restrictions on transaction types. The enclosing transactions might be read-only or update, local or global. Databases can be opened read-only, read/write, or for multiversion concurrency control, or MVCC, which is described in Chapter 2, Advanced Transactions, of ObjectStore Advanced C++ API User Guide.

Database changes made by an application are not visible to other applications until the enclosing top-level transaction commits. Therefore, notifications that indicate changes to persistent data should generally be made at commit time.

In a two-phase commit transaction, either all notifications are queued for delivery (if the transaction commits), or none are (otherwise).

Security

In order to send and subscribe to notifications, you must open the database in question. Consequently, if a client does not have access to open a database, it cannot send or receive notifications associated with it.

Within a database, notifications are not integrated with ObjectStore security. A client can subscribe and notify based on database locations in any segment, even if it does not have access to the segment itself.

Performance Considerations

All notifications and subscriptions on a database go to the ObjectStore Server. The Server routes notifications to interested clients, and the Cache Manager queues the notifications for all its clients. Because the Server acknowledges each notification, sending a notification requires a round-trip message to the Server.

Polling for incoming messages only accesses shared memory and is very fast. If a client does not retrieve its notifications, the Cache Manager can run out of queue space.

Every call to os_notification::subscribe, os_notification::unsubscribe, and os_notification::notify_immediate costs the client one round-trip message to the ObjectStore Server. Polling for notifications using os_notification::receive results in a call to poll() or select() or other operating-system-specific call. Polling for notifications using os_notification::queue_status is a simple access to shared memory and is the fastest polling mechanism.

If any commit-time notifications are queued during a transaction, there is an additional RPC call to the ObjectStore Server during the top-level commit operation.

Notifications are stored and forwarded in the Server, Cache Manager, and sometimes even in the receiving application. Therefore, delivery of notifications might not be particularly fast. Performance varies according to system load and the amount of notification processing. For example, delivery could range from milliseconds to several seconds.

As a general rule, if you plan your application to use notification, you should not expect high throughput. Do not expect a client application to send or receive more than about 10 notifications per second.

Event validation
ObjectStore does not check events for validity. It is possible to specify an address in a notification that is illegal in another process. For example, you could allocate a new object and post an immediate notification using its location. Other processes see this address as invalid because the new object has not yet been committed.

Restriction on use with access hooks
Notification APIs cannot be called from within access hooks. Information about access hooks can be found in the discussion on os_database::set_access_hooks() in the ObjectStore C++ API Reference.

Notification Usage

The guidelines for sending and receiving notifications are summarized in the next paragraphs.

Sending notifications
The main class is os_notification. You must include the ostore.hh file, and link with -los on UNIX, and OSTORE.LIB on Windows and OS/2 systems. The signature of the function that sends a notification is

      /* $OS_ROOTDIR/include/ostore/client/client.hh */
      os_notification::notify_immediate
      (os_reference&, int kind=0, const char* message=0);
To ensure that subscribers do not receive notifications until the changes are visible in the database, use os_notification::notify_on_commit.

Receiving notifications
An application receives notification using the following functions:

      os_notification::receive (os_notification*&, int timeout=-1);
      os_reference os_notification::get_reference();
      int os_notification::get_kind();
      const char* os_notification::get_string();
Be sure to delete the returned heap-allocated os_notification object when done.

Network Service

When an ObjectStore application uses notifications, it automatically establishes a second network connection to the Cache Manager daemon on the local host. The application uses this connection to receive (and acknowledge the receipt of) incoming notifications from the Cache Manager. (Outgoing notifications are sent to the Server, not the Cache Manager.) See Chapter 1, Overview of Managing ObjectStore, in ObjectStore Management for specific information about defaults.

Notification Errors

The notification APIs do not do complete validation of the arguments passed to them. Invalid arguments can therefore cause segmentation violations or other undefined behavior. See the ObjectStore C++ API Reference, Appendix A, Exception Facility, for information on specific errors.

ObjectStore Utilities for Managing Notification

The ossvrstat utility displays statistics on the number of notifications received and sent by the Server.

The oscmstat utility displays information on notifications queued for clients. This is useful in debugging applications that use notifications.

Detailed descriptions of these and other ObjectStore utilities can be found in Chapter 4, Utilities, in ObjectStore Management.

Notifications Example

The following example illustrates the use of notifications.

#include <ostore/ostore.hh>
#include <iostream.h>
#include <assert.h>
int main(int argc, char** argv) {
      const char* db_name = "notif.db";
      const char* root_name = "Test Object";
      /* can see client name with "ossvrstat -clients <host>" */
      objectstore::set_client_name(argv[0]);
      objectstore::initialize();
      cout << "Opening database "<< db_name << endl;
      os_database* db = os_database::open(db_name,0,0644);
      os_reference ref1 = 0;
      os_reference ref2 = 0;
      os_transaction* txn =
            os_transaction::begin(os_transaction::update);
      os_database_root* root = db->find_root(root_name);
      if (!root) {
            cout << "Creating a couple of ints" << endl;
            root = db->create_root(root_name);
            root->set_value(new (db, os_typespec::get_int(), 2) int[2]);
      } /* end if */
      ref1 = root->get_value(); /* &int[0] */
      ref2 = &((int*)ref1.resolve())[1];  /* &int[1] */
      txn->commit();
      delete txn;
      os_notification* note;
      int iterations = 0;
      os_transaction::begin(os_transaction::read_only);
      /* the Initiator process takes no args on command line */
      if ( argc == 1) {
            cout << "Initiator Starting notifications..." << endl;
            /* subscribe to ref2; */
            os_notification::subscribe(ref2);
            while (iterations < 10) {
                  iterations++;
                  cout << "sending notification, kind = "<< iterations << endl;
                  /* send immediate notification on ref1 with iterations */
                  /* as kind */
                  os_notification::notify_immediate(ref1,iterations);
                  /* now get response into note */
                  os_notification::receive(note);
                  /* make sure note response is on ref2 */
                  os_reference ref = note->get_reference();
                  assert(ref == ref2);
                  /* make sure correct iterations comes back */
                  int kind = note->get_kind();
                  assert(kind == iterations);
                  delete note;  /* avoid memory leak */
                  sleep(2);
            } /* end while */
            /* Tell Responder to exit by sending
            notification on ref1 with kind=0 */
            cout << "sending notification, kind = 0" << endl;
            os_notification::notify_immediate(ref1,0);
            /* Initiator done */
      } /* end if */
      else {
            /*  the Responder process takes any args on command line */
            cout << "Responder Waiting for Notifications" << endl;
            /* subscribe to ref1 */
            os_notification::subscribe(ref1);
            while(1) {
                  /* receive notification for ref1 into note */
                  os_notification::receive(note);
                  /* see what kind it is */
                  int kind = note->get_kind();
                  cout << "received notification, kind = "<< kind << endl;
                  /* if kind is 0, exit */
                  if (kind == 0)
                  break;  /* Responder done */
                  /* make sure notification is about ref1 */
                  os_reference ref = note->get_reference();
                  assert(ref == ref1);
                  delete note; /* avoid memory leak */
                  /* send notification on ref2 with kind */
                  os_notification::notify_immediate(ref2,kind);
            } /* end while */
      } /* end if */
      return 0;
}


[previous] [next]

Copyright © 1997 Object Design, Inc. All rights reserved.

Updated: 03/31/98 16:58:45