The information about notification is organized in the following manner:
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.
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.
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.
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
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. 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.
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
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.
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. 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. 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.
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.
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.
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; }
Updated: 03/31/98 16:58:45