Galaxy Communicator Documentation:

Managing Session Information and Using Continuations



What is a session?

The Galaxy Communicator infrastructure is designed to support multiple simultaneous sessions. By session we mean an individual conversation with a Communicator system, such as the sequence of exchanges between the time the system answers the phone and greets an individual user and the time the system or user hangs up. A single Communicator system might support a large number of phone lines, and thus a large number of simultaneous sessions.

Although some servers connected to the Hub might be dedicated to a particular session (such as when they connect using -contact_hub and -session_id), most servers support multiple sessions through the same connection to the Hub. So, for instance, a single parser may be responsible for parsing all the user utterance strings for all the sessions currently connected to the Hub.


Starting and ending sessions

The Hub will start a session whenever it receives a session ID it does not recognize. If logging is enabled in the Hub program file, the Hub will open a log for that session as well. The Builtin server function new_session (somewhat misnamed, at this point) can be called to reset the session's utterance counter and open a new log. Reusing session IDs in this way is not recommended, however, it is supported for convenience. The most common case of reuse is when no session ID is actually declared, and the Hub uses the Default session ID as a result.

If new_session opened a new log every time it was called, any program which calls it would end up writing a very short log which covered the time from when the session was created (essentially, when the Hub first saw the session ID) to when new_session was called. Since this behavior would be unavoidable when new_session is used correctly, new_session has been implemented not to open a new log the first time it is called on a given session.

If you want to end a session explicitly, you can call the Builtin server function end_session.

If you want to control the memory state of a given session, you can do that from the server as well.


Keeping information straight

In the documentation on adding a server, we noted that the call environment structure embodies the information required to communicate properly with the Hub. These call environments are passed to dispatch functions, where they can be used to provide replies or send new messages to the Hub. But dispatch functions aren't restricted to providing a reply to an incoming message or sending new messages; they can also set up callbacks or timed tasks which may send new messages later. When these messages are sent from callbacks or timed tasks, the call environment in the dispatch function needs to be saved away cleanly, so that it isn't freed until the new message is sent.

To support this functionality, the Galaxy Communicator library provides environment-aware broker callback and timed task setup, as well as a way of maintaining an environment through successive calls to dispatch functions. In this section, we describe some of these functions.

void GalSS_TaskSetEnvironment(Gal_TaskPkg *p, GalSS_Environment *env)
Sets the environment associated with the task p to env.

GalSS_Environment *GalSS_TaskGetEnvironment(Gal_TaskPkg *p)
Returns the environment associated with the task p.

Gal_TaskPkg *GalSS_EnvAddTask(GalSS_Environment *env, void (*task)(Gal_TaskPkg *), void *caller_data, long num_millisecs, int read_blocking_available, GAL_SOCKET *read_socket, GAL_SOCKET *write_socket, GAL_SOCKET *err_socket, FILE *read_file, FILE *write_file, FILE*err_file, Gal_TaskConditionFn condition, void (*cleanup_fn)(void *))
This function provides all the functionality of Gal_AddTaskExtended, but also saves away the call environment env for access using the function GalSS_TaskGetEnvironment. A task thus established can be refired in any of the normal ways; see the timed task documentation for details.

At times, connections may be associated with UI elements, which can issue new messages. So the UI itself is an element outside the scope of dispatch functions where it would be helpful to have a call environment to use to issue new messages. It's not enough to set up the environment when the connection is established; crucial properties of the UI interaction, such as the session ID, may be changed by other servers in the course of evaluation. The safest thing to do is to create an environment for the UI when the connection is established, update it every time a dispatch function is called, and free the environment when the connection is shut down. The function GalSS_EnvMaintainInLocation does this for you.

void GalSS_EnvMaintainInLocation(GalIO_CommStruct *gcomm, char *initial_session_id, GalSS_Environment **env_loc)
The connection gcomm is the connection to the Hub which is associated with a UI element for the current session, and hosts the dispatch functions which are fired. This function seeds the location env_loc with a new environment and updates its session ID to initial_session_id. It sets a dispatch function callback via GalIO_AddConnectionDispatchFnCallback to keep the location current, and sets up a shutdown callback to free the environment.
 


Administrative functions

These are the functions the infrastructure uses to manage call environments. You should never need to use these functions directly.

GalSS_Environment *GalSS_EnvCreate(GalIO_CommStruct *gcomm)
Creates an environment from the connection gcomm.

void GalSS_EnvUpdateSessionID(GalSS_Environment *env, char *session_id)
Sets the session_id as the session for this environment. The session_id is copied. This function can safely be called repeatedly on the same call environment to change the session context.

char *GalSS_EnvGetSessionID(GalSS_Environment *env)
Returns the session_id  for this environment. The session_id is not copied. It may be NULL.

void GalSS_EnvLock(GalSS_Environment *env)
Increments the reference counter on the environment env to indicate that it should not be freed.

void GalSS_EnvUnlock(GalSS_Environment *env)
Decrements the reference counter on the environment env. When the reference counter reaches 0, the environment is freed.

int GalSS_EnvReturnRequired(GalSS_Environment *env)
Returns 1 if the call environment represents a dispatch function invocation for which a return is expected, or 0 if it does not.


Continuations

In the vast majority of cases, the server's reply to a message from the Hub coincides with the return value from the dispatch function invoked by that message. However, as we've already seen, we can provide that return value before the dispatch function ends (by calling one of the functions GalSS_EnvDestroyToken, GalSS_EnvError, or GalSS_EnvReply). In addition, it is also possible to postpone returning a value and provide it later. This is desirable in a number of situations.

Situation 1

Consider a case where the Hub sends a message to a recognizer that audio input is available via brokering. The recognizer server, in the appropriate dispatch function, sets up an incoming broker to capture the audio and send the result to the Hub. In the typical arrangement, exemplified by the example for GalSS_EnvBrokerDataInInit, the server writes a new message to the Hub. This new message is associated with a new token; that is, the request for recognition occurs at the end of one Hub program, and the recognition results invoke another Hub program:
PROGRAM: FromAudio

...

RULE: :audio_host & :audio_port & :call_id --> Recognizer.Recognize
IN: :audio_host :audio_port :call_id
OUT: none!

PROGRAM: FromRecognizer

RULE: :input_string --> Parser.Parse
....

However, the programmer might prefer that the call to the recognizer appear to the Hub to be a synchronous call; that is, that the input string be the reply to the Recognize message:
PROGRAM: FromAudio

...

RULE: :audio_host & :audio_port & :call_id --> Recognizer.Recognize
IN: :audio_host :audio_port :call_id
OUT: :input_string

RULE: :input_string --> Parser.Parse
....

This is easy to do, using the function GalSS_EnvPostponeReply.

int GalSS_EnvPostponeReply(GalSS_Environment *env)
Informs the Hub that the response to its message will be delayed, but that in the meantime it's available for other incoming messages. It does this by sending a pacifier message of type GAL_POSTPONE_MSG_TYPE.

The programmer would then set up the broker as follows:

Gal_Frame Recognize(Gal_Frame f, void *server_data)
{
  GalSS_Environment *env = (GalSS_Environment *) server_data;

  /* ... */
  GalSS_EnvPostponeReply(env);
  GalSS_EnvBrokerDataInInit(env, host, port, f, env_recognition_handler,
                            env_recognition_finalizer, (void *) NULL, 0);
  return (Gal_Frame) NULL;
}

static void env_recognition_handler(GalSS_Environment *env,
                                    GalIO_BrokerStruct *broker_struct,
                                    void *data, Gal_ObjectType data_type,
                                    int n_samples)
{
  if (data_type == GAL_INT_16) {
    /* ... gather the audio ... */
  }
}

static void env_recognition_finalizer(GalSS_Environment *env,
                                      GalIO_BrokerStruct *broker_struct)
{
  /* Send the reply. The name doesn't matter because it will
     be set by the environment as the original name of the
     incoming message. */
  Gal_Frame f = Gal_MakeFrame("foo", GAL_CLAUSE);

  /* ... */

  Gal_SetProp(f, ":input_string", Gal_StringObject(recognized_string));
  GalSS_EnvReply(env, f);
}

Observe that the dispatch function postpones the response, and then the environment-aware finalizer explicitly sends the reply when recognition is done. This same strategy could be used with timed task callbacks as well, if the appropriate situation arose.

Situation 2

Consider a case where the server sends a request to the Hub using GalSS_EnvDispatchFrame. This function returns when the Hub returns a reply. However, during that time, the server is inaccessible for other dispatch function invocations from that Hub, and in addition, a deadlock will result if the Hub needs to contact the originating server in the course of satisfying the request (if, for instance, the program that is invoked by the incoming message ends up calling a dispatch function in the originating server). In this situation, the programmer might prefer to dispatch the request, and postpone the processing of the reply to the request using a continuation funciton. The programmer can accomplish this using the function GalSS_EnvDispatchFrameWithContinuation.

typedef Gal_Frame (*GalSS_ContinuationFn)(Gal_Frame, GalIO_MsgType, GalSS_Environment *, void *);
This is the type of the continuation function.

int GalSS_EnvDispatchFrameWithContinuation(GalSS_Environment *env, Gal_Frame frame, GalSS_ContinuationFn fn, void *continuation_state, void (*continuation_state_free_fn)(void *))
This function sends frame to the Hub using the environment env, and indicates that it expects a reply. It then notifies the Hub that the dispatch server response will be postponed using GalSS_EnvPostponeReply, and stores away the environment, along with the continuation function fn and an arbitrary state continuation_state, which the programmer can use to store arbitrary data for use in the continuation function. The data will be freed using the continuation_state_free_fn. The continuation function is invoked as the continuation of the dispatch function; in particular, if the continuation function returns a frame, it will be treated as the reply to the original dispatch function. It is called with the frame and message type of the reply to the original request to the Hub, the environment env, and the continuation_state.


Hub continuations

Under some circumstances, you might want to do something comparable on the Hub scripting side. In some cases, you may wish to send a message to a server, and then monitor that server for a new message which you want to treat as a reply or error (if, for instance, the server in question uses an asynchronous programming paradigm and you prefer a synchronous one in your Hub scripts). This allows you to achieve the same results as in situation 1 above without modifying the server. There are two ways to do this.

Hub continuations in the scripting language

The best way of doing this is to use the CONTINUE_REPLY: or CONTINUE_ERROR: Hub directives:
PROGRAM: FromAudio

...

RULE: :audio_host & :audio_port & :call_id --> Recognizer.Recognize
IN: :audio_host :audio_port :call_id
CONTINUE_REPLY: {c FromRecognizer }
OUT: :input_string

RULE: :input_string --> Parser.Parse

The Hub will monitor the Recognizer server for a new message (in the current session) named FromRecognizer, and treat it as the reply to the call to Recognizer.Recognize. The processing proceeds normally from that point. The CONTINUE_ERROR: directive does the same for an error reply. Both directives can be repeated in a single rule.

Hub continuations using the Builtin server

The other way of doing this, which is more powerful but less focused, is to use the Builtin function hub_continue. This function should be called immediately after a rule which does not wait for a reply, so it will be processed essentially simultaneously. This function allows the programmer to specify a list of possible reply and continuation frames, as well as a service type and/or service provider to monitor. While on the one hand, this mechanism allows the programmer to treat as replies new messages which come from other servers, it does not allow the programmer to automatically "inherit" the identity of the server the previous message was sent to:
PROGRAM: FromAudio

...

RULE: :audio_host & :audio_port & :call_id --> Recognizer.Recognize
IN: :audio_host :audio_port :call_id
OUT: none!

RULE: --> Builtin.hub_continue
IN: (:reply_matches ( {c FromRecognizer } ) ) (:service_type "Recognizer")
OUT: :input_string

RULE: :input_string --> Parser.Parse

The Hub will monitor any provider of the Recognizer service for a new message (in the current session) named FromRecognizer, and treat it as the reply to the call to Builtin.hub_continue. The processing proceeds normally at that point.


Please send comments and suggestions to: bugs-darpacomm@linus.mitre.org
Last updated December 24, 2001.

 Copyright (c) 2000 - 2001
The MITRE Corporation
ALL RIGHTS RESERVED