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.
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.
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.
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.
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 13.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.
"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.
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.
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 13.2 shows the notify set containing both the collection object and the elements in the collection.
"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
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.
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
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 13.3 shows one way to do steps 5 and 6.
"Make a temporary copy of the set."
| tmp newObjs |
tmp := MyHoldings copy.
"Refresh the 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.
After a commit, each session 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:
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].
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.
You also use System class>>signaledObjects to poll for changes to objects in your notify set.
Example 13.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.
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.
%
Notification on object changes may occasionally produce unexpected results. The following sections outline areas of concern.
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.
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:
name := 'dowJones'
self at: 3 put: 'active'.
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:
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 13.5 shows a method that creates an object and automatically adds it to the notify set in the process.
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 14 for more on handling Exceptions such as ObjectsCommittedNotification.
GemStone enables you to send a signal from your Gem session to any other current Gem session. GsSession 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 13.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 14, “Handling Exceptions”.
To communicate, one session must send a signal and the receiving session must be set up to receive the signal.
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 13.6 shows how you might use this method to find the session ID for user1 and send a message.
| sessionId serialNum otherSession signalToSend |
sessionId := System currentSessions
detect:[:each |(((System descriptionOfSession: each) at: 1)
userId = 'user1') ]
ifNone: [nil].
sessionId notNil ifTrue: [
serialNum := GsSession serialOfSession: sessionId .
otherSession := GsSession sessionWithSerialNumber: serialNum .
signalToSend := GsInterSessionSignal signal: 4
message:'reinvest form is here'.
signalToSend sendToSession: otherSession.
]
Example 13.6 uses the expression:
signalToSend sendToSession: otherSession.
Alternatively, you might use this method:
otherSession sendSignalObject: signalToSend
Still another alternative is this one, which replaces the final two expressions in Example 13.6 with a single expression:
System sendSignal: aSignalNumber to: otherSession withMessage: aMessage
No matter how the message is sent, the other session needs to receive it, as shown in Example 13.7.
When you have the session ID, you can use the method
GsInterSessionSignal class>>signal: aSignalNumber message: aMessage.
Instead of assigning meanings to aSignalNumber, your site might agree that the integer is meaningless, but the message string is to be read as a string of characters conveying the intended message, as in Example 13.8.
For more complex information, the message could be a code where each token conveys its own meaning.
You can use signals to broadcast a message to every user logged in to GemStone. In Example 13.8, one session notifies all current sessions that it has created a new object to represent a stock that was added to the portfolio. In applications that commit whenever a new object is created, this code could be part of the instance creation method for class Holding. Otherwise, it could be application-level code, triggered by a commit.
System currentSessions do: [:each |
System sendSignal: 8 to: each
withMessage: 'new Holding: SallieMae'.].
If the message is displayed to users, they can commit or abort to get a new view of the repository and put the new object in their notify sets. Or the application could be set up so that signal 8 is handled without user visibility. The application might do an automatic abort, or automatically start a transaction if the user is not in one, and add the object to the notify set. This enables setting up a notifier on a new unknown object. Also, because signals are queued in the order received, you can service them in order.
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. (Both the structure of the signal contents and the process of enabling signals are described in detail in the following sections.)
The method System class >> signalFromGemStoneSession 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 signalFromGemStoneSession 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.
To poll for signals from other sessions, send the following message as often as you require:
System signalFromGemStoneSession
If a signal has been sent, this method returns a four-element array containing:
If no signal has been sent, this method returns an empty array.
Example 13.9 shows how to poll for Gem-to-Gem signals. If the polling process finds a signal, it immediately checks for another one until the queue is empty. Then the process sleeps for 10 seconds.
To use the exception mechanism to receive signals from other Gem sessions, you must enable receipt of the InterSessionSignal notification. This exception has the same three arguments mentioned above:
By default, the InterSessionSignal notification is disabled, except in the GemBuilder for Smalltalk interface, which enables the error as part of GbsSession>>gemSignalAction:.
To enable this exception, execute:
System enableSignaledGemStoneSessionError
To disable the exception, send the message:
System disableSignaledGemStoneSessionError
To determine whether receiving this exception is presently enabled or disabled, send the message:
System signaledGemStoneSessionErrorStatus
This method returns true if the notification 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 error. The error 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.
GemStone notifiers and Gem-to-Gem signals use the same underlying implementation. The following performance and other considerations apply when using either mechanism.
Receiving the signal can also be delayed. 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.
You should also check in your application to make sure the session does not hang. For instance, use GsSocket>>readReady to make sure your session won’t be waiting for nonexistent input at a socket connection.
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 signalFromGemStoneSession until it returns a nil. You can postpone the problem by sending very short messages, such as an OOP pointing to some string on disk or perhaps an index into a global message table. For a better idea of how the message queue works, see System class>>sendSignal:to:withMessage: in the image.
If you want to pass large amounts of data between sessions, sockets are more appropriate than Gem-to-Gem signals. Chapter 12, “File I/O and 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.
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 13.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.
| gemMessage logString |
gemMessage := System signalFromGemStoneSession.
logString := String new.
logString add:
'-----------------------------------------------------
The signal ';
add: (gemMessage at: 2) asString;
add: ' was received from GemStone sessionId = ';
add: (gemMessage at: 1) asString;
add: ' and the message is ';
addAll: (gemMessage at: 3).
(GsFile openWriteOnServer: '$GEMSTONE/gemmessage.txt')
addAll: logString; close.