14. Signals and Notifiers

Previous chapter

Next chapter

This chapter discusses how to communicate between one session and another, and between one application and another.

Communicating Between Sessions
introduces two ways to communicate between sessions.

Object Change Notification
describes the process used to enable object change notification for your session.

Gem-to-Gem Signaling
describes one way to pass signals from one session to another.

Other Signal-Related Issues
describes performance, signal buffer overflow, and other signal related considerations.

14.1 Communicating Between Sessions

Applications that handle multiple sessions often find it convenient to allow one session to know about other sessions’ activities. GemStone provides two ways to send information from one current session to another:

Object change notification and Gem-to-Gem signals only reach logged-in sessions. For applications that need to track processes continuously, you can create a Gem that runs independently of the user sessions and monitors the system. See the instructions on creating a custom Gem in the GemBuilder for C manual.

14.2 Object Change Notification

Object change notifiers are signals that can be generated by the object server to inform you when specified objects have changed. You can request that the object server inform you of these changes by adding objects to your notify set.

When a reference to an object is placed in a notify set, you receive notification of all changes to that object (including the changes you commit) until you remove it from your notify set or end your GemStone session. The notification you receive can vary in form and content, depending on which interface to GemStone you are running and how the notification action was defined.

Your application can respond in several ways:

To set up a simple notifier for an object:

1. Create the object and commit it to the object server.

2. Add the object to your session’s notify set with one of the messages:

System addToNotifySet: aCommittedObject
System addAllToNotifySet: aCollectionOfCommittedObjects

3. Define how to receive the notifier with either a notifier message or by polling.

4. Define what your session will do upon receiving the notifier.

The following section describes each of these steps in detail.

Setting Up a Notify Set

GemStone defines a notify set for each user session to which you add or remove objects. Except for a few special cases discussed later, any object you can refer to can be added to a notify set.

Notify sets persist through transactions, living as long as the GemStone session in which they were created. When the session ends, the notify set is no longer in effect. If you need notification regarding the same objects for your next session, you must once again add those objects to the notify set.

Adding an Object to a Notify Set

To add an object to your notify set, use an expression of the form:

System addToNotifySet: aCommittedObject

When you add an object to the notify set, GemStone begins monitoring changes to it immediately.

Most GemStone objects are composite objects, made up of a root object and a few subobjects. Usually you can just ignore the subobjects. However, there are circumstances in which the both the root object and subobjects must appear in the notify set. For details, see Special Classes.

Example 14.1 creates a collection of stock holdings and then creates a notify set for the stocks in the collection. Finally, the session is set to automatically receive the notifier.

Example 14.1

"Create a Class to record stock name, number and price"
Object subclass: #Holding
	instVarNames: #('name' 'number' 'price')
	classVars: #()
	classInstVars: #()
	poolDictionaries: {}
	inDictionary: Published.
 
"Compile accessing methods"
Holding compileAccessingMethodsFor: Holding instVarNames.
 
"Add a Collection for Holdings to UserGlobals dictionary" 
UserGlobals
   at: #MyHoldings put: IdentityBag new.
 
"Add some stocks to my collection"
MyHoldings add:
   (Holding new name: #USSteel; number: 1000; price: 50.00).
MyHoldings add:
   (Holding new name: #VMware; number: 50000; price: 95.00).
MyHoldings add:
   (Holding new name: #ATT; number: 100000; price: 30.00).
 
"Add the collection object to the notify set"
System addToNotifySet: MyHoldings.
(System notifySet) includesIdentical: MyHoldings.
 
"Enable receipt of signals"
System enableSignaledObjectsError.
 

Objects That Cannot Be Added

Not every object can be added to a notify set. Objects in a notify set must be visible to more than one session; otherwise, other sessions could not change them. So, objects you have created for temporary use or have not committed cannot be added to a notify set. GemStone responds with an error if you try to add such objects to the notify set.

You also receive an error if you attempt to add special objects, such as true, false, nil, and instances of Character, SmallInteger and SmallDouble.

Adding a Collection to a Notify Set

To add a collection of objects to your notify set, use an expression like this:

System addAllToNotifySet: aCollectionOfCommittedObjects

This expression adds the elements of the collection to the notify set.

You don’t have to add the collection object itself, but if you do, use addToNotifySet: rather than addAllToNotifySet:.When a collection object is in the notify set, adding elements to the collection or removing elements from it trigger notification. Modifications to the elements do not trigger notification on the collection object; if you want to know when the elements change, you must add them to the notification set.

Example 14.2 shows the notify set containing both the collection object and the elements in the collection.

Example 14.2

"Add the stocks in the collection to the notify set"
System addAllToNotifySet: MyHoldings.
System notifySet.
% 
an Array 
  #1 a Holding 
  #2 a Holding 
  #3 a Holding 
 
"Add the collection object itself to the notify set"
System addToNotifySet: MyHoldings.
System notifySet.
% 
an Array 
  #1 a Holding 
  #2 a Holding 
  #3 a Holding 
  #4 an IdentityBag 
 

Very Large Notify Sets

You can register any number of objects for notification, but very large notify sets can degrade system performance. GemStone can handle thousands of objects without significant impact. Beyond that, test whether the response times are acceptable for your application.

If performance is a problem, you can set up a different system of change recording:

1. Have each session maintain its own list of the last several objects updated (a modify list). The list is a collection written only by that session.

2. Create a global collection of collections that contains every session’s list of changes.

3. Put the global collection and its elements in your notify set, so you receive notification when a session commits a modified list of changed objects. Then you can check for changes of interest.

If the modify lists are ordered, this preserves the order of the additions, so that the new objects can be serviced in the correct order. Using the notifySet, notification on a batch of changed objects is received in OOP order.

Listing Your Notify Set

To determine the objects in your notify set, execute:

System notifySet

Removing Objects From Your Notify Set

To remove an object from your notify set, use an expression of the form:

System removeFromNotifySet: anObject

To remove a collection of objects from your notify set, use an expression of the form:

System removeAllFromNotifySet: aCollection

This expression removes the elements of the collection. If the collection object itself is also in the notify set, remove it separately, using removeFromNotifySet:.

To remove all objects from your notify set, execute:

System clearNotifySet

Notification of New Objects

In a multi-user environment, objects are created in various sessions, committed, and immediately open to modification. It may not be sufficient to receive notifiers on the objects that existed at the beginning of your session. You may also need notification concerning new objects.

You cannot put unknown objects in your notify set, but you can create a collection for those kinds of objects and add that collection to the notify set. Then when the collection changes, meaning that objects have been added or removed, you can stop and look for new objects. For example, to receive notification when the price of any stock in your portfolio changes, you can perform the following steps:

1. Create a globally known collection (for example, MyHoldings) and add your existing stock holdings (instances of class Holding) to it.

2. Place all of these stocks in your notify set:

System addAllToNotifySet: MyHoldings

3. Place the collection MyHoldings in your notify set, so that you receive notification that the collection has changed when a stock is bought or sold:

System addToNotifySet: MyHoldings

4. Place new stock purchases in MyHoldings by adding code to the instance creation method for class Holding.

5. When you receive notification that the contents of MyHoldings have changed, compare the new MyHoldings with the original.

6. When you find new stocks, add them to your notify set, so that you will be notified if they are changed.

Example 14.3 shows one way to do steps 5 and 6.

Example 14.3

"Make a temporary copy of the set."
 
| tmp newObjs |
tmp := MyHoldings copy.
 
"Refresh the snapshot view (commit or abort)."
System commitTransaction.
 
"Get the difference between the old and new sets."
newObjs := (MyHoldings - tmp).
 
"Add the new elements to the notify set."
newObjs size > 0 ifTrue: [System addAllToNotifySet: newObjs].
 

You can also identify objects to remove from the notify set by doing the opposite operation:

tmp - MyHoldings

This method could be useful if you are tracking a great many objects and trying to keep the notify set as small as possible.

Note that only IdentityBag and its subclasses understand “-” as a difference operator.

Receiving Object Change Notification

After a commit, each session’s snapshot view is updated. The object server also updates its list of committed objects. This list of objects is compared with the contents of the notify set for each session, and a set of the changed objects for each notify set is compiled.

You can receive notification of committed changes to the objects in your notify set in two ways:

  • Enabling automatic notification, which is faster and uses less CPU
  • Polling for changes
Automatic Notification of Object Changes

For automatic notification, you enable your session to receive the exception ObjectsCommittedNotification. By default, ObjectsCommittedNotification is disabled (except in GemBuilder for Smalltalk, which enables the signal as part of GbsSession>>notificationAction:).

To enable the event signal for your session, execute:

System enableSignaledObjectsError

To disable the event signal, send the message:

System disableSignaledObjectsError

To determine whether this error message is enabled or disabled for your session, send the message:

System signaledObjectsErrorStatus

This method returns true if the signal is enabled, and false if it is disabled.

This setting is not affected by commits or aborts. It remains until you change it, you end the session, or you receive the signal. The signal is automatically disabled when you receive it so that the exception handler can take appropriate action.

The receiving session handles the notification with an exception handler. Your exception handler is responsible for reading the set of signaled objects (by sending the message System class>>signaledObjects) as well as taking the appropriate action.

ObjectsCommittedNotification addDefaultHandler: 
	[:ex | 
	| changes |
	changes := System signaledObjects.
	"do something with the changed objects"
	System enableSignaledObjectsError].

Reading the Set of Signaled Objects

The System class>>signaledObjects method reads the incoming changed object signals. This method returns an array, which includes all the objects in your notify set that have changed since the last time you sent signaledObjects in your current session. The array contains objects changed and committed by all sessions, including your own. If more than one session has committed, the OOPs are OR’d together. The elements of the array are arranged in OOP order, not in the order the changes were committed. If none of the objects in your notify set have been changed, the array is empty.

Use a loop to call signaledObjects repeatedly, until it returns an empty collection. The empty collection guarantees that there are no more signals in the queue.

Also see the discussion on Frequently Changing Objects.

Polling for Changes to Objects

You also use System class>>signaledObjects to poll for changes to objects in your notify set.

Example 14.4 uses the polling method to inform you if anyone has added objects to a set or changed an existing one. Notice that the set is created in a dictionary that is accessible to other users, not in UserGlobals.

Example 14.4

System disableSignaledObjectsError;
   signaledObjectsErrorStatus.
%
 
"Create a set."
Published at: #Changes put: IdentitySet new.
System commitTransaction.
 
System addToNotifySet: Changes.
%
 
"Login a separate session to perform the following"
Changes add: 'here is a change'.
System commitTransaction
%
 
"In the original session, see the signal"
| mySignaledObjs count |
System abortTransaction.
count := 0 .
[ mySignaledObjs := System signaledObjects.
mySignaledObjs size = 0 and:[ count < 50]
]
  whileTrue: [
   System sleep: 10 .
   count := count + 1
   ].
^ mySignaledObjs.
%
 

Troubleshooting

Notification on object changes may occasionally produce unexpected results. The following sections outline areas of concern.

Frequently Changing Objects

If users are committing many changes to objects in your notify set, you may not receive notification of each change. You might not be able to poll frequently enough, or your exception handler might not process the errors it receives fast enough. In such cases, you can miss some intermediate values of frequently changing objects.

Special Classes

Most GemStone objects are composite objects, but for the purposes of notification you can usually ignore this fact. They are almost always implemented so that changes to subobjects affect the root, so only the root object needs to go into the notify set.

Common operations that trigger notification on the root object include:

  • Assignment to an instance variable:
name := 'dowJones'
  • Updating the indexable portion of an object:
self at: 3 put: 'active'.
  • Adding to a collection:
self add: 3.

In a few cases, however, the changes are made only to subobjects. For the following GemStone kernel classes, both the object and the subobjects must appear in the notification set:

  • RcQueue
  • RcIdentityBag
  • RcCounter
  • RcKeyValueDictionary

You can also have the problem with your own application classes. Wherever possible, you should implement objects so that changes modify the root object. You must also balance the needs of notification with potential problems of concurrency conflicts.

If you are not being notified of changes to a composite object in your notify set, look at the code and see which objects are actually modified during common operations such as add: or remove:. When you are looking for the code that actually modifies an object, you may have to check a lower-level method to find where the work is performed.

Once you know the object’s structure and have discovered which elements are changed, add the object and its relevant elements to the notify set. For cases where elements are known, you can add them just like any other object:

System addToNotifySet: anObject

Example 14.5 shows a method that creates an object and automatically adds it to the notify set in the process.

Example 14.5

method: SetOfHoldings
add: anObject
	System addToNotifySet: anObject.
	^super add: anObject
%
 

Methods for Object Notification

Methods related to notification are implemented in class System. Browse the class System and read about these methods:

addAllToNotifySet:
addToNotifySet:
clearNotifySet
disableSignaledObjectsError
enableSignaledObjectsError
notifySet
removeAllFromNotifySet:
removeFromNotifySet:
signaledObjects
signaledObjectsErrorStatus

See Chapter 15 for more on handling Exceptions such as ObjectsCommittedNotification.

14.3 Gem-to-Gem Signaling

GemStone enables you to send a signal from your Gem session to any other current Gem session. The exception class InterSessionSignal implements several methods for communicating between two sessions. Unlike object change notification, inter-session signaling operates on the event layer and deals with events that are not being recorded in the repository. Signaling happens immediately, without waiting for a commit.

An application can use signals between sessions for situations like a queue, when you want to pass the information quickly. Signals can also be a way for one user who is currently logged in to send information to another user who is logged in.

NOTE
A signal is not an interrupt, and it does not automatically awaken an idle session. The signal can be received only when your session is actively executing Smalltalk code.

You can receive a signal from another session by polling for the signal or by receiving automatic notification.

As an example of Gem-to-Gem signaling, Figure 14.1 shows the following sequence of events:

1. session1 enables event signals from other Gem sessions. (For details, see Receiving a Notification.)

2. session2 sends a signal to session1. (See Sending a Signal.)

3. The Stone sends the exception InterSessionSignal to session1. The receiving session processes the signal with an exception handler. For details, see Chapter 15, “Handling Exceptions”.

Figure 14.1 Communicating from Session to Session

 

Sending a Signal

To communicate, one session must send a signal and the receiving session must be set up to receive the signal.

Identifying the Receiving Session

To send a signal to another Gem session, you must know its session ID.

To see a description of sessions that are currently logged in, execute the following method:

System currentSessions

This message returns an array of SmallIntegers representing session IDs for all current sessions. Example 14.6 shows how you might use this method to find the session ID for user1 and send a message.

Sending the Message

When you have the session ID, you can use the method

InterSessionSignal class 
	sendSignal: aSignalNumber 
	to: sessionId 
	withMessage: aMessage. 
  • aSignalNumber is determined by the particular protocol you arranged at your site and the specific message you wish to send. Sending the integer “1,” for example, doesn’t convey a lot unless everyone has agreed that ”1” means “Ready to trade.” Your application may use these for specific types of signals, to indicate priority, or whatever other needs your application has; meaning can also be encoded in the message argument.You may create an application-level symbol dictionary of meanings for the different signal numbers.
  • sessionId is the sessionId of the session to which the signal will be sent.
  • aMessage is a String object with up to 1023 characters.
Example 14.6 Sending a Signal

| sessionId serialNum otherSession signalToSend |
sessionId := System currentSessions
	detect:[:each |(((System descriptionOfSession: each) at: 1)
     userId = 'TradeUser') ]
	ifNone: [nil].
sessionId ifNotNil: [
	InterSessionSignal 
		sendSignal: 4 
		to: sessionId 
		withMessage: 'reinvest form is here'.
	]
 
 

Legacy protocol to send signals

While InterSessionSignal is recommended, you may also use

System sendSignal: aSignalNumber to: sessionId withMessage: aMessage

There are a number of legacy/deprecated ways to signal sessions. These use the serial number of a session rather than the sessionId, or a GsSession instance.

To determine the serial number of a session based on the sessionId, use the following expression:

GsSession 	serialOfSession: sessionId

To loop up a GsSession:

GsSession sessionWithSerialNumber: sessionSerialNumber

Signalling another session can be done using expressions such as:

  • GsInterSessionSignal signal: sessionSerialNumber message: aMessage
  • signalToSend sendToSession: otherGsSession
  • otherGsSession sendSignalObject: signalToSend

No matter how the message is sent, the other session needs to receive it, as shown in Example 14.7.

Receiving a Signal

Once the signal is sent, the other session needs to retrieves it. For example, after sending the signal as described in Example 14.6:

Example 14.7 Receiving a signal

| theSignal |
theSignal := InterSessionSignal poll.
theSignal ifNotNil: [ 
	theSignal sentInt asString, '-', theSignal sentMessage	]. 
%
4-reinvest form is here
 

You can receive a signal from another session in either of two ways: you can poll for such signals, or you can enable notification from GemStone. Signals are queued in the receiving session in the order in which they were received. If the receiving session has inadequate heap space for an incoming signal, the contents of the signal is written to stdout, whether the receiving session has enabled receiving such signals or not.

The method InterSessionSignal poll reads the incoming signals, whether you poll or receive a signal. If there are no pending signals, the array is empty.

Use a loop to call InterSessionSignal poll repeatedly, until it returns a nil. This guarantees that there are no more signals in the queue. If signals are being sent quickly, you may not receive a separate InterSessionSignal for every signal. Or, if you use polling, signals may arrive more often than your polling frequency.

Polling

To poll for signals from other sessions, send the following message as often as you require:

InterSessionSignal >> poll

This method returns nil if there is no signal waiting. Example 14.8 shows how to poll for Gem-to-Gem signals. If the polling process finds a signal, it returns; otherwise it sleeps for 10 seconds and tries again. This example includes a limit such that it stops after 10 minutes, if no signal arrives; polling, of course, must continue running.

Example 14.8 Polling

| theSignal loopLimit |
loopLimit := 0.
[  theSignal := InterSessionSignal poll.
	theSignal notNil and:[ loopLimit < 60 ]
	] whileTrue: [
   		System sleep: 10.
   		loopLimit := loopLimit + 1
   		].
^theSignal
 

Polling and Signalling

Rather than fetching a signal and then handling it, you can setup a poll that sends a signal when a signal arrives. For example

Example 14.9 Polling and Signalling

[ 60 timesRepeat: [ 
	InterSessionSignal pollAndSignal.
	System sleep: 10 ]
	] 	on: InterSessionSignal
		do: [:ex | 
			"perform handling for the signal"
			ex resume]
 

Legacy Polling protocol

Older application may use this deprecated method to poll for signals:

System signalFromGemStoneSession

If no signal has been sent, this method returns an empty array. If a signal has been sent, this method returns a four-element array containing:

  • An instance of GsSession representing the session that sent the signal. (equivalent to GsSession sessionWithSerialNumber: (anInterSessionSignal sendingSessionSerial).
  • The signal value (a SmallInteger) (equivalent to anInterSessionSignal sentInt)
  • The string containing the signal message. (equivalent to anInterSessionSignal sentMessage)
  • The number of additional pending signals.(equivalent to anInterSessionSignal numPending)

Receiving a Notification

To use the exception mechanism to receive signals from other Gem sessions without polling, you must enable receipt of the InterSessionSignal notification. By default, the InterSessionSignal notification is disabled, except in the GemBuilder for Smalltalk interface, which enables the error as part of GbsSession>>gemSignalAction:.

When enabled or disabled, the setting is not affected by commits or aborts. It remains until you change it, you end the session, or you receive the error.

To enable this exception, execute:

InterSessionSignal enableSignalling

To disable the exception, send the message:

InterSessionSignal disableSignalling

To determine whether receiving this exception is presently enabled or disabled, send the message:

InterSessionSignal signallingEnabled

If you have enabled signalling on InterSessionSignal, it is automatically disabled when you receive it, so that the exception handler can take appropriate action without further interruption. You must re-enable it afterwards; usually this is the final step in the block that handles the signal.

The following legacy protocol performs the same actions:

System enableSignaledGemStoneSessionError
System disableSignaledGemStoneSessionError
System signaledGemStoneSessionErrorStatus

14.4 Other Signal-Related Issues

GemStone notifiers and Gem-to-Gem signals use the same underlying implementation. The following performance and other considerations apply when using either mechanism.

Inactive Gem

Receiving the signal can be delayed, if the session is inactive. GemStone is not an interrupt-driven application programming interface. It is designed to make no demands on the application until the application specifically requests service. Therefore, Gem-to-Gem signals and object change notifiers are not implemented as interrupts, and they do not automatically awaken an idle session. They can be received only when GemBuilder is running, not when you are running client code, sitting at the Topaz prompt, waiting for activity on a socket, or waiting on a semaphore (as for a child process to complete). The signals are queued up and wait until you read them, which can create a problem with signal overflow if the delay is too long and the signals are coming rapidly.

You can receive signals at reliable intervals by regularly performing some operation that activates GemBuilder. For example, in a GemStone Smalltalk application, you could set up a polling process that periodically sends out GbsSession>>pollForSignal. The pollForSignal method causes GemBuilder for Smalltalk to poll the repository. GemBuilder for C also provides a function GciPollForSignal.

Dealing With Signal Overflow

Gem-to-Gem signals and object change notification signals are queued separately in the receiving session. The queues maintain the order in which the signals are received.

NOTE
For object change notification, the queue does not preserve the order in which the changes were committed to the repository. Each notification signal contains an array of OOPs, and these changes are arranged in OOP order. See Receiving Object Change Notification.

Each session has a signal buffer that will accommodate 50 signals. Signals remain in the signal buffer until they are received and read by the receiving session. If the receiving session does not read the signals, or if it does not read them fast enough to keep up with signals that are being sent, the signal buffer will fill up. In this case, further signals will cause the Exception SignalBufferFull to be signalled on the sender. Set your application so that the sender gracefully handles this error. For example, the sender might try to send the signal five times, and finally display a message of the form:

Receiver not responding. 

The most effective way to prevent signal overflow is to keep the session in a state to receive signals regularly, using the techniques discussed in the preceding section. When you do receive signals, make sure you read all the signals off the queue. Repeat signaledObjects or InterSessionSignal poll until it returns a nil.

Sending Large Amounts of Data

If you want to pass large amounts of data between sessions, sockets are more appropriate than Gem-to-Gem signals. Chapter 13, “Operating System Access”, describes the GemStone interface to TCP/IP sockets. That solution does not pass data through the Stone, so it does not create system overload when you send a great many messages or very long ones.

Maintaining Signals and Notification When Users Log Out

Object change notification and Gem-to-Gem signals only reach logged-in sessions. For applications that need to track processes continuously, you can create a Gem that runs independently of the user sessions and monitors the system. For example, such a Gem can monitor a machine and send a warning to all current sessions when something is out of tolerance. Or it might receive the information that all the users need and store it where they can find it when they log in.

Example 14.10 shows some of the code executed by an error handler installed in a monitor Gem. It traps Gem-to-Gem signals and writes them to a log file.

Example 14.10

| theSignal logString |
theSignal := InterSessionSignal poll.
logString := String new.
logString add: '---------------------'; lf;
	add: 'Signal '; 
	add: (theSignal sendInt) asString; 
	add: ' received from session ';
	add: (theSignal sendingSessionId) asString; 
	add: ' with message: '; 
	addAll: (theSignal sentMessage); lf.
(GsFile openWriteOnServer: '$GEMSTONE/gemmessage.txt') 
		addAll: logString; close.
 

Previous chapter

Next chapter