Galaxy Communicator Documentation:

Upgrading from 2.1 to 3.0

License / Documentation home / Help and feedback

The 3.0 Galaxy Communicator distribution contains major improvements to outgoing brokering. There are obligatory upgrades associated with these improvements; in particular, APIs have unavoidably changed. In almost all other ways, the 3.0 upgrade should be transparent for C programmers; except for outgoing brokering, the programmer will probably not have exploited the functionality which needs to be changed.  For Java programmers, a significant internal reorganization of the Java bindings was required to bring its functionality in line with the C library, and there are a number of unavoidable API changes which are required. For Python and Allegro programmers, the changes are much more modest, but in many cases still necessary.

This document describes only the steps required or recommended to upgrade existing features of Galaxy Communicator. You can find a list of new features here. You can find the complete release notes here.
 
 
Step Who's affected Status
Step 1: Configuring your system Users of the Python and Allegro bindings, users of the MITRE PostGres DB wrapper server Required
Step 2a: Upgrading outgoing brokering All server developers Required
Step 2b: Upgrading memory management in broker callbacks Users of incoming brokering in the libGalaxy C library Optional, but highly recommended for efficient performance
Step 2c: Upgrading incoming brokering Users of incoming brokering in the libGalaxy C library Optional
Step 2d: Using broker callbacks for completion and aborting Users of brokering Optional
Step 3: Upgrading Python bindings Users of the Python bindings Required
Step 4: Upgrading Java bindings Users of the Java bindings Required
Step 5: Upgrading server listen status Users of the  server listen status functionality in the libGalaxy C library (introduced in 2.1) Required
Step 6: Upgrading references to the call environment Users of the libGalaxy C library who inadvisedly exploited the odd return properties  in 2.1 of GalSS_EnvError and GalSS_EnvDestroyToken Required
Step 7: Upgrading calls to GalIO_SetServerData Users of the libGalaxy C library who use either use GalIO_SetServerData to store arbitrary data in the server, or returned values from _GalSS_init_server for storage Required (GalIO_SetServerData), recommended (_GalSS_init_server)
Step 8a: Upgrading references to the Communicator main loop Users of the libGalaxy C library who have written servers with their own main() Optional
Step 8b: Upgrade your own main loop Users of the libGalaxy C library who have written servers with their own event loops (embedded in Tcl/Tk or CORBA, for instance) Optional but highly recommended
Step 9: Upgrade timed task invocation Users of the libGalaxy C library who set up their own timed tasks Optional
Step 10: Upgrading session management Users of the libGalaxy C library Optional but highly recommended, especially for those who intend to run multiple parallel sessions

Even though a number of these steps are optional, we strongly encourage you to consider them. The functionality they replace has not been deprecated, but in many cases, future upgrades will not extend the older functionality. In addition, steps 8-10 feature radically improved strategies for embedding Communicator servers in external main loops, and for session handling; while these upgrades are optional, these improvements are so dramatic that we strongly recommend that everyone adopt them.


Step 1: Configuring your system

The only obligatory change in configuring your system comes in the section where Python is enabled. Because we're now implementing the Python bindings on top of the core C library, the Python bindings require the Python header files. In order to compile Python, you must have PYINCLUDE in templates/<ARCHOS>/config.make set to the location of Python.h (typically include/python1.5 parallel to bin/). On Linux, you may have to install the python-devel RPM.

In addition, the 3.0 Galaxy Communicator distribution requires Allegro 5.0.

Finally, we have added the PostGres DB server to the default compilation. If you want to enable this server, uncomment the POSTGRES_INCLUDE and POSTGRES_LIB variables in your config file and set them to the appropriate values.


Step 2a: Updating outgoing brokering

Brokering has been improved in two major ways. First, outgoing brokers now share a listener port with the main server, which reduces the number of open ports and also fixes a bug involving port reuse inside processes. Second, outgoing brokers can now provide data to an arbitrary number of broker clients. This second improvement now makes timeout management of outgoing brokers considerably more important, since outgoing brokers now cache their data, and memory bloat can result if they're not destroyed promptly.

Here's the old way of doing outgoing brokering:

static Gal_Frame prepare_audio_frame(char *filename)
{
  Gal_Frame f = Gal_MakeFrame("main", GAL_CLAUSE);
  int total;
  char *buf;
  GalIO_BrokerStruct *b;
  char host_pid[1024];

  /* In the code omitted here, the data from the named file is
     read into buf, and total is the number of bytes in buf */

  /* .... */

  /* Now that we have the audio, we add a binary element. */
  /* These must be added to the broker frame BEFORE the
     broker object is initialized. */
  sprintf(host_pid, "%s:%d", GalIO_IPAddress(), (int) getpid());
  GalIO_FrameSetBrokerCallID(f, Gal_StringObject(host_pid));
  b = GalIO_BrokerDataOutInit(3900, f, 0, 0);
  if (b && (GalIO_FrameGetBrokerPort(f) > 0)) {
    Gal_SetProp(f, ":binary_host", Gal_StringObject(GalIO_IPAddress()));
    Gal_SetProp(f, ":binary_port",
                Gal_IntObject(GalIO_FrameGetBrokerPort(f)));
    GalIO_BrokerWriteBinary(b, buf, total);
    GalIO_BrokerDataOutDone(b);
  }
  return f;
}

Here's the new way
static Gal_Frame prepare_audio_frame(GalIO_CommStruct *gcomm, char *filename)
{
  Gal_Frame f = Gal_MakeFrame("main", GAL_CLAUSE);
  int total;
  char *buf;
  GalIO_BrokerStruct *b;
  char host_pid[1024];

  /* In the code omitted here, the data from the named file is
     read into buf, and total is the number of bytes in buf */

  /* .... */

  /* Now that we have the audio, we write the binary data
     through the broker. */

  b = GalIO_BrokerDataOutInit(gcomm, 0, 10);
  if (b && (GalIO_GetBrokerListenPort(b) > 0)) {
    GalIO_BrokerPopulateFrame(b, f, ":binary_host", ":binary_port");
    GalIO_BrokerWriteBinary(b, buf, total);
    GalIO_BrokerDataOutDone(b);
  }
  free(buf);
  return f;
}

Note the following contrasts: All these changes are mandatory. Before 3.0, setting the broker properties (host, port, call ID) exhibited some complicated and unnecessary ordering dependencies which have now been eliminated; in addition, the outgoing broker didn't need the connection, because it created its own server listener.

Corresponding modifications are required in Allegro, Java and Python.


Step 2b: Upgrading memory management in broker callbacks

As part of general memory management cleanup, we have observed that before version 3.0, we had failed to document appropriately the fact that data passed to broker handlers used by GalIO_CommBrokerDataInInit is not freed by the Galaxy Communicator library. Therefore, all broker handlers should take responsibility for freeing the data they're passed. We made this mistake in the 2.1 version of one of our audio examples; here's the comparison:

Version 2.1

static void audio_handler(GalIO_BrokerStruct *broker_struct,
                          void *data, Gal_ObjectType data_type,
                          int n_samples)
{
  DataHandler *d = (DataHandler *) GalIO_GetBrokerCallerData(broker_struct);

  switch (data_type) {
  case GAL_BINARY:
    if (d->data_buf)
      d->data_buf = (char *) realloc(d->data_buf, n_samples + d->size);
    else
      d->data_buf = (char *) malloc(n_samples + d->size);
    bcopy(data, d->data_buf + d->size, n_samples);
    d->size += n_samples;
    break;
  default:
    GalUtil_Warn("Unknown data type %s\n", Gal_ObjectTypeString(data_type));
  }
}

Version 3.0
static void audio_handler(GalIO_BrokerStruct *broker_struct,
                          void *data, Gal_ObjectType data_type,
                          int n_samples)
{
  DataHandler *d = (DataHandler *) GalIO_GetBrokerCallerData(broker_struct);

  switch (data_type) {
  case GAL_BINARY:
    if (d->data_buf)
      d->data_buf = (char *) realloc(d->data_buf, n_samples + d->size);
    else
      d->data_buf = (char *) malloc(n_samples + d->size);
    bcopy(data, d->data_buf + d->size, n_samples);
    d->size += n_samples;
    free(data);
    break;
  default:
    GalUtil_Warn("Unknown data type %s\n", Gal_ObjectTypeString(data_type));
  }
}


Step 2c: Upgrading incoming brokering (optional)

As part of cleaning up our data storage and callback models, we've made some modifications to the preferred way that incoming brokers are set up. First, to support the new event-driven programming paradigm more reliably, we recommend that everyone change their incoming broker setup function from GalIO_BrokerDataInInit to GalIO_CommBrokerDataInInit, as follows:

Version 2.x

Gal_Frame receive_audio(Gal_Frame f, void *server_data)
{
  DataHandler *d = (DataHandler *) malloc(sizeof(DataHandler));
  GalIO_BrokerStruct *b;
  char *host = Gal_GetString(f, ":binary_host");
  int port = Gal_GetInt(f, ":binary_port");

  d->data_buf = (char *) NULL;
  d->size = 0;

  if (host && port) {
    b = GalIO_BrokerDataInInit(host, port, f, audio_handler, d, 0);
    if (b) {
      GalIO_SetBrokerActive(b);
    }
  } else {
    free(d);
  }
  return (Gal_Frame) NULL;
}

Version 3.0
Gal_Frame receive_audio(Gal_Frame f, void *server_data)
{
  DataHandler *d = (DataHandler *) malloc(sizeof(DataHandler));
  GalIO_BrokerStruct *b;
  char *host = Gal_GetString(f, ":binary_host");
  int port = Gal_GetInt(f, ":binary_port");

  d->data_buf = (char *) NULL;
  d->size = 0;
  d->gcomm = GalSS_EnvComm((GalSS_Environment *) server_data);

  if (host && port) {
     b = GalIO_CommBrokerDataInInit(d->gcomm, host, port, f, audio_handler,
                                    0, d, __FreeDataHandler);
    if (b) {
      GalIO_SetBrokerActive(b);
    }
  } else {
    free(d);
  }
  return (Gal_Frame) NULL;
}

Actually, our best recommendation is to use GalSS_EnvBrokerDataInInit, as shown in this upgrade step, but if you choose not to, we recommend the upgrade here.

Second, we now discourage the user of GalIO_BrokerSetFinalizer, which has been superseded by the event-driven programming model: Note that this example also illustrates the upgrade to GalSS_EnvBrokerDataInInit, but we won't highlight that contrast here:

Version 2.x

void __AudioSupport_RetrieveAudio(char *host, int port,
                                  int sample_rate,
                                  Gal_Frame f,
                                GalIO_BrokerDataFinalizer finalizer,
                                  void *callback_data)
{
  GalIO_BrokerStruct *b;

  if (host && port && (sample_rate > 0)) {
    DataContainer *c = (DataContainer *) calloc(1, sizeof(DataContainer));
    c->data_buf = (char *) NULL;
    c->size = 0;
    c->callback_data = callback_data;
    b = GalIO_BrokerDataInInit(host, port, f, __AudioCallback, c, 0);

    if (b) {

      /* Set the broker finalizer. */
      GalIO_BrokerSetFinalizer(b, finalizer);

      /* Make the broker active. Without this step, nothing will happen.
         This utility can arguably be used to manage multiple incoming
         audio signals, but it's probably inadequate. */
      GalIO_SetBrokerActive(b);
    }
  }
}

Version 3.0
void __AudioSupport_RetrieveAudio(GalSS_Environment *env,
                                  char *host, int port,
                                  int sample_rate,
                                  Gal_Frame f,
                                GalIO_BrokerCallbackFn finalizer,
                                  void *callback_data)
{
  GalIO_BrokerStruct *b;

  if (host && port && (sample_rate > 0)) {
    DataContainer *c = (DataContainer *) calloc(1, sizeof(DataContainer));
    c->data_buf = (char *) NULL;
    c->size = 0;
    c->callback_data = callback_data;
    b = GalSS_EnvBrokerDataInInit(env, host, port, f, __AudioCallback,
                                  0, (void *) c, NULL);

    if (b) {

      GalIO_AddBrokerCallback(b, GAL_BROKER_DATA_DONE_EVENT,
                              finalizer, (void *) NULL);

      /* Make the broker active. Without this step, nothing will happen.
         This utility can arguably be used to manage multiple incoming
         audio signals, but it's probably inadequate. */
      GalIO_SetBrokerActive(b);
    }
  }
}

The special finalizer has been replaced by a more general callback, and in this case the event the callback is associated with is when the broker is done with its data.


Step 2d: Using broker callbacks for completion and aborting

In version 2.1 and previous, there was no way of marking completion of a broker connection or a termination before completion without sending string semaphores through the broker connection. In version 3.0, you can use broker callbacks to implement this behavior.

With semaphores

#define AUDIO_END "end"
#define AUDIO_ABORT "abort"

static void audio_handler(GalIO_BrokerStruct *broker_struct,
                          void *data, Gal_ObjectType data_type,
                          int n_samples)
{
  DataHandler *d = (DataHandler *) GalIO_GetBrokerData(broker_struct);

  switch (data_type) {
  case GAL_STRING:
    if (!strcmp((char *) data, AUDIO_END)) {
      GalIO_BrokerDataDone(broker_struct);
      __report_done(broker_struct, (void *) NULL);
    } else if (!strcmp((char *) data, AUDIO_ABORT) {
      __report_abort(broker_struct, (void *) NULL);
    } else {
      GalUtil_Warn("Unknown message %s\n", (char *) data);
    }
    free(data);
    break;
  case GAL_BINARY:
    if (d->data_buf)
      d->data_buf = (char *) realloc(d->data_buf, n_samples + d->size);
    else
      d->data_buf = (char *) malloc(n_samples + d->size);
    bcopy(data, d->data_buf + d->size, n_samples);
    d->size += n_samples;
    free(data);
    break;
  default:
    GalUtil_Warn("Unknown data type %s\n", Gal_ObjectTypeString(data_type));
  }
}

void __FreeDataHandler(void *data)
{
  DataHandler *d = (DataHandler *) data;
  free(d->data_buf);
  free(d);
}

void __report_abort(GalIO_BrokerStruct *b, void *data)
{
  DataHandler *d = (DataHandler *) GalIO_GetBrokerData(b);
  Gal_Frame f;

  f = Gal_MakeFrame("notify", GAL_CLAUSE);
  Gal_SetProp(f, ":notification", Gal_StringObject("Audio aborted."));
  GalIO_CommWriteFrame(d->gcomm, f, 0);
  Gal_FreeFrame(f);
}

void __report_done(GalIO_BrokerStruct *b, void *data)
{
  DataHandler *d = (DataHandler *) GalIO_GetBrokerData(b);
  Gal_Frame f;
  FILE *fp = fopen("/dev/audio", "w");

  if (!fp) {
    GalUtil_Warn("Couldn't open /dev/audio");
  } else {
    fwrite(d->data_buf, sizeof(char), d->size, fp);
    fflush(fp);
    fclose(fp);
  }

  f = Gal_MakeFrame("notify", GAL_CLAUSE);
  Gal_SetProp(f, ":notification", Gal_StringObject("Audio received."));
  GalIO_CommWriteFrame(d->gcomm, f, 0);
  Gal_FreeFrame(f);
}

Gal_Frame receive_audio(Gal_Frame f, void *server_data)
{
  DataHandler *d = (DataHandler *) malloc(sizeof(DataHandler));
  GalIO_BrokerStruct *b;
  char *host = Gal_GetString(f, ":binary_host");
  int port = Gal_GetInt(f, ":binary_port");

  d->data_buf = (char *) NULL;
  d->size = 0;
  d->gcomm = GalSS_EnvComm((GalSS_Environment *) server_data);

  if (host && port) {
     b = GalIO_CommBrokerDataInInit(d->gcomm, host, port, f, audio_handler,
                                    0, d, __FreeDataHandler);
    if (b) {
      GalIO_SetBrokerActive(b);
    }
  } else {
    free(d);
  }
  return (Gal_Frame) NULL;
}


Without semaphores

static void audio_handler(GalIO_BrokerStruct *broker_struct,
                          void *data, Gal_ObjectType data_type,
                          int n_samples)
{
  DataHandler *d = (DataHandler *) GalIO_GetBrokerData(broker_struct);

  switch (data_type) {
  case GAL_BINARY:
    if (d->data_buf)
      d->data_buf = (char *) realloc(d->data_buf, n_samples + d->size);
    else
      d->data_buf = (char *) malloc(n_samples + d->size);
    bcopy(data, d->data_buf + d->size, n_samples);
    d->size += n_samples;
    free(data);
    break;
  default:
    GalUtil_Warn("Unknown data type %s\n", Gal_ObjectTypeString(data_type));
  }
}

void __FreeDataHandler(void *data)
{
  DataHandler *d = (DataHandler *) data;
  free(d->data_buf);
  free(d);
}

void __report_abort(GalIO_BrokerStruct *b, void *data)
{
  DataHandler *d = (DataHandler *) GalIO_GetBrokerData(b);
  Gal_Frame f;

  f = Gal_MakeFrame("notify", GAL_CLAUSE);
  Gal_SetProp(f, ":notification", Gal_StringObject("Audio aborted."));
  GalIO_CommWriteFrame(d->gcomm, f, 0);
  Gal_FreeFrame(f);
}

void __report_done(GalIO_BrokerStruct *b, void *data)
{
  DataHandler *d = (DataHandler *) GalIO_GetBrokerData(b);
  Gal_Frame f;
  FILE *fp = fopen("/dev/audio", "w");

  if (!fp) {
    GalUtil_Warn("Couldn't open /dev/audio");
  } else {
    fwrite(d->data_buf, sizeof(char), d->size, fp);
    fflush(fp);
    fclose(fp);
  }

  f = Gal_MakeFrame("notify", GAL_CLAUSE);
  Gal_SetProp(f, ":notification", Gal_StringObject("Audio received."));
  GalIO_CommWriteFrame(d->gcomm, f, 0);
  Gal_FreeFrame(f);
}

Gal_Frame receive_audio(Gal_Frame f, void *server_data)
{
  DataHandler *d = (DataHandler *) malloc(sizeof(DataHandler));
  GalIO_BrokerStruct *b;
  char *host = Gal_GetString(f, ":binary_host");
  int port = Gal_GetInt(f, ":binary_port");

  d->data_buf = (char *) NULL;
  d->size = 0;
  d->gcomm = GalSS_EnvComm((GalSS_Environment *) server_data);

  if (host && port) {
     b = GalIO_CommBrokerDataInInit(d->gcomm, host, port, f, audio_handler,
                                    0, d, __FreeDataHandler);
    if (b) {
      GalIO_SetBrokerActive(b);
      GalIO_AddBrokerCallback(b, GAL_BROKER_ABORT_EVENT,
         __report_abort, (void *) NULL);
      GalIO_AddBrokerCallback(b, GAL_BROKER_DATA_DONE_EVENT,
         __report_done, (void *) NULL);
    }
  } else {
    free(d);
  }
  return (Gal_Frame) NULL;
}

We believe that the version without semaphores is considerably more reliable than the version with semaphores; in addition to the sender and receiver not having to agree on the semaphores themselves, the detection of an incomplete connection covers all the cases where the sending server died unexpectedly and could not have sent an abort semaphore. We have upgraded all MITRE examples to use the callback-driven approach.


Step 3: Upgrading Python bindings

If you write Python servers, you'll need to make a few tiny modifications for 3.0.

First, because Python now uses the C library, a number of the elements you could set and access via instance attributes now require you to call a method. In particular:

So for example, you should now set the maximum number of connections as follows:

Version 2.x

s.max_connections = 5
Version 3.0
s.MaxConnections(5)
See the source code for more details.

Also, because Python now uses the C library:

Next, with an eye on simplifying server startup under Windows NT, we've revised the "preamble" which tells the server how to locate the Python libraries:

Version 2.x

import os, sys

sys.path.insert(0, os.environ["LIBDIR"])

import Galaxy, GalaxyIO

Version 3.0
import os, sys

sys.path.insert(0, os.path.join(os.environ["GC_HOME"],
                                "contrib", "MITRE", "templates"))

import GC_py_init

import Galaxy, GalaxyIO

In this new method, the only thing the environment needs to provide is the identity of GC_HOME; all the other path functionality,  etc., is provided in the GC_py_init module.

Finally, the Python bindings now fully embrace the new call environments (see the documentation on adding a server and session management). The GalaxyIO.Connection class, which was "secretly" a call environment in 2.x, has now been renamed GalaxyIO.CallEnvironment.The impact of this change is minimal in non-class-based servers. We use a simplified version of the multiply server from the double example to illustrate:

Version 2.x

def Multiply(conn, dict):
    return {":int": dict[":int"] * Factor}

def Welcome(conn, dict):
    global Factor
    try:
        Factor = dict[":factor"]
    except: pass
    return None

def main():
    s = GalaxyIO.Server(sys.argv, "multiply", default_port = 2900,
           conn_class = GalaxyIO.Connection)
    s.AddDispatchFunction("multiply", Multiply)
    s.AddDispatchFunction("reinitialize", Welcome)
    s.RunServer()

main()

Version 3.0
def Multiply(env, dict):
    return {":int": dict[":int"] * Factor}

def Welcome(env, dict):
    global Factor
    try:
        Factor = dict[":factor"]
    except: pass
    return None

def main():
    s = GalaxyIO.Server(sys.argv, "multiply", default_port = 2900)
    s.AddDispatchFunction("multiply", Multiply)
    s.AddDispatchFunction("reinitialize", Welcome)
    s.RunServer()

main()

For mnemonic reasons, we've changed the name of the first argument of dispatch functions from conn to env in this example; this of course has no functional import. However, observe also that the conn_class keyword to the creation method for GalaxyIO.Server has been removed, because it is no longer recognized, and in this case it was superfluous anyway. The real differences emerge when we compare class-based versions of the same example:

Version 2.x

class MultiplyConnection(GalaxyIO.Connection):

    def Multiply(self, dict):
        return {":int": dict[":int"] * self.conn.Factor}

    def Welcome(self, dict):
        try:
            self.conn.Factor = dict[":factor"]
        except: self.conn.Factor = 1
        return None

def main():
    s = GalaxyIO.Server(sys.argv, "multiply", default_port = 2900,
                conn_class = MultiplyConnection)
    s.AddDispatchFunction("multiply", MultiplyConnection.Multiply)
    s.AddDispatchFunction("reinitialize", MultiplyConnection.Welcome)
    s.RunServer()

main()

Version 3.0
class MultiplyEnvironment(GalaxyIO.CallEnvironment):

    def Multiply(self, dict):
        return {":int": dict[":int"] * self.conn.Factor}

    def Welcome(self, dict):
        try:
            self.conn.Factor = dict[":factor"]
        except: self.conn.Factor = 1
        return None

def main():
    s = GalaxyIO.Server(sys.argv, "multiply", default_port = 2900,
                env_class = MultiplyEnvironment)
    s.AddDispatchFunction("multiply", MultiplyEnvironment.Multiply)
    s.AddDispatchFunction("reinitialize", MultiplyEnvironment.Welcome)
    s.RunServer()

main()

Notice that because the call environment is an "ephemeral" object, permanent data (such as the value of Factor) must be stored on the underlying connection. This is true in both 2.x and 3.0, but it's clearer in 3.0 why this is necessary. The conn_class keyword has been replaced by the env_class keyword, etc.
 


Step 4: Upgrading Java bindings

If you write Java servers, you'll need to make some modifications for 3.0.

Brokering

Java broker classes have been modified. First, the galaxy.server.DataOutBrokerconstructor has changed:

Version 2.x

     public DataOutBroker(Server server, int port, GFrame frame) throws Exception

     public DataOutBroker(Server server, int port, GFrame frame, boolean rport) throws Exception

Version 3.0

    public DataOutBroker(Server server, int pollMilliseconds, int timeoutSeconds) throws Exception

Java outgoing brokers now rely on the listener in the main server to handle broker client connection requests, so the broker no longer needs its own listener (i.e., port). Also, the GFrame is no longer needed (it provided the broker's call ID) since DataOutBroker now generates its own call ID (accessible via DataOutBroker.getCallId). The new constructor's arguments include a reference to the galaxy.server.Server that started the broker, a polling time in milliseconds, and a timeout in seconds. The polling time controls how often the broker checks for new data to send to its clients (it also checks for new clients, to which it sends any cached data it has). The timeout determines when (if ever) the broker stops accepting new client connections. Once a timeout expires, no new connections are accepted, and the broker will exit once it has been instructed to disconnect from its clients (via DataOutBroker.close). The old constructors are still available but have been deprecated. They now only use the Server parameter (default polling time and timeout values are used internally).

When sending messages from a server to a Hub, informing the Hub that brokered data is available, use  DataOutBroker.populateFrame to add the broker contact information automatically to the frame.

The codes segments below illustrate how code for creating an outgoing broker and creating a notification frame differs between Galaxy Communicator 2.x and 3.0.

Version 2.x

public class TestAudio extends galaxy.server.Server {
{
    ...

    Clause fr = new Clause("main");
    fr.setProperty(":call_id", this.getCallId());
    DataOutBroker broker=null;
    try {
         broker = new DataOutBroker(this, 3900, fr);
    } catch (Exception ex) {
         logAndSendError("Error opening broker connection: " + ex.toString());
         return;
    }

    ... // prepare brokered data

    GFrame result = new Clause("main");
    result.setProperty(":binary_port", broker.getPort());
    result.setProperty(":call_id", this.getCallId());
    result.setProperty(":binary_host", this.getHostAddress());

    ... // send the frame

Version 3.0

public class AudioOut extends galaxy.server.Server
{
    ...

    DataOutBroker broker;
    int pollMs = 50;
    int timeoutSecs = 50;
    try {
         broker = new DataOutBroker(this, pollMs, timeoutSecs);
    } catch (Exception e) {
         logAndSendError("Error opening broker connection: " + e.toString());
         return;
    }

    ... // prepare brokered data

    GFrame result = new Clause("main");
    broker.populateFrame(result, ":binary_host", ":binary_port");

    ... // send the frame

The manner in which an incoming broker detects and processes the end of a brokered data stream has also changed. Subclasses of galaxy.server.DataInBroker should implement the disconnectReceived method. This method is called automatically when the incoming broker receives a disconnect message from the outgoing broker, indicating that no more brokered data will be sent. Incoming brokers can also choose to implement abortReceived, which is called when the broker receives an unexpected exception while receiving brokered data.

Version 2.x

// Outgoing broker code segment

    DataOutBroker broker;

    ... // create broker

    broker.write("start");

    // write brokered data here

    broker.write("end");
    broker.close();
 
 

// Incoming broker code segment

public  void receivedString(String str)
{
     if (str.equals("start")) {
         System.out.println("About to start receiving data");
     } else if (str.equals("end")) {

     ... // process data

Version 3.0

// Outgoing broker code segment

    DataOutBroker broker;

    ... // create broker

    // write brokered data here

    broker.close();

Note that the outgoing broker no longer has to send explicit markers for the beginning and end of the brokered data.

// Incoming broker code segment

protected void disconnectReceived()
{
     ... // process data
 

As a result of these and other changes to brokering, there are a number of incompatabilities between 2.x and 3.0 code:
 

  • The galaxy.server.DataInBroker thread is no longer started in the DataInBroker constructor. Instead, DataInBroker.start must be called explicitly (e.g., at the end of a DataInBroker subclass constructor). This is required in order to ensure that a DataInBroker does not attempt to retrieve and process any brokered data until all custom initialization has taken place:
  •           Version 3.0

        public class AudioInBroker extends galaxy.server.DataInBroker
        {
            private GBinary bdata;

            public AudioInBroker(Server server, InetAddress ip, int port, GFrame frame)
                throws Exception
            {
                 super(server,ip,port,frame);
                 bdata = new GBinary();

                 // You must explicitly call start() once your broker is initialized.
                 // For example, you can call it at the end of the constructor, as is
                 // done here.
                 start();
            }
        .
        .
        .
     

  • In order to enable all Galaxy data types to be available via brokering, a DataInBroker must implement four additional methods: receivedList, receivedInteger, receivedFloat, and receivedSymbol.

  •  

     
     
     
     
     
     
     
     
     
     
     
     
     
     
     

  • Since out brokers now rely on the main server listener to receive client connection requests, the following DataOutBroker methods have been removed:
  • setPort
  • getPort
  • startListening
  • init
  • flush
  • stopListening
  • In addition, galaxy.server.Server.getCallID has been removed. Use DataOutBroker.getCallId instead.


    Threads

    Thread management has been redesigned in the Java bindings, especially with regards to stopping various threads. As a result, the following methods have been removed:

    Also, Server.prepareStop is now private. Use Server.stop to stop the server. Finally, these MainServer methods have been removed and reimplemented elsewhere:

    Command Line Arguments

    The syntax of command line arguments has changed in the Java bindings. Equal signs (=) are no longer needed when assigning values (e.g., -port=123 is now -port 123). Also, double quotes (") are now needed when listing multiple values for one argument (e.g., -contact_hub=localhost:123,localhost:456 is now -contact_hub "localhost:123 localhost:456").

    The -noui argument has been replaced with -ui (i.e., graphical user interfaces based on ServerUI, if present, are now off by default; use -ui to turn them on).

    Finally, the arguments appendLog and -mainServerClass have been renamed to -append_log and -main_server_class respectively.

    See the Java binding documentation for details on new command line arguments.

    Miscellaneous

    This section contains information on other potential 2.x/3.0 incompatibilities in addition to those discussed above.

    These classes have been removed from 3.0:

  • galaxy.server.WrongTypeInDispatch (should not impact Galaxy Communicator developers)
  • galaxy.io.GalaxyStreamable (should not impact Galaxy Communicator developers)
  • galaxy.io.GalaxyStream (should not impact Galaxy Communicator developers)
  • galaxy.util.HostPort (should not impact Galaxy Communicator developers)
  • galaxy.lang.Symbols (symbol definitions are now public static members of galaxy.lang.Symbol)
  • galaxy.lang.NullFrameException (should not impact Galaxy Communicator developers)
  • These methods have been removed from 3.0 or renamed:
  • galaxy.server.MainServer.getAllowMultipleConnections
  • galaxy.server.MainServer.setAllowMultipleConnections (use the new -maxconns command line argument or MainServer.getMaxConnections and setMaxConnections to configure the number of maximum connections)
  • galaxy.server.MainServer.setHostPortCollection (this functionality has been replaced by MainServer.setHubContactInfoCollection, and there should not be any impact Galaxy Communicator developers)
  • galaxy.lang.Symbol.setOrdinal
  • galaxy.lang.Symbol.getOrdinal
  • galaxy.util.ArgParser.getArgCollections (should not impact Galaxy Communicator developers)
  • galaxy.io.GalaxyInputStream.write(ObjectStorage)(should not impact Galaxy Communicator developers)
  • The signatures of these methods have changed in 3.0:
  • galaxy.io.GalaxyInputStream.readBinary now returns galaxy.lang.GBinary instead of galaxy.lang.BinaryObject (which has been deprecated as of 3.0)
  • The galaxy.util.ArgParser.getArg methods now return java.util.List (was java.lang.String) since one command line argument can have a list of associated values (e.g., -contact_hub). In a related change, the version of getArg that takes a default argument value now expects a java.util.List (was java.lang.String).
  • The galaxy.lang.Gframeconstructor has been changed. It takes a galaxy.lang.Symbol and an integer that represents the frame type (types are now defined in galaxy.lang.GFrame: see  GAL_NULLFRAME, GAL_TOPIC, GAL_CLAUSE, GAL_PRED).
  • The default constructors for the array objects (galaxy.lang.Float32, galaxy.lang.Float64,galaxy.lang.Int16, galaxy.lang.Int32, and galaxy.lang.Int64) now initialize numerical arrays (e.g., the default Int16 constructor creates an empty array of shorts). In 2.1, the default constructors created empty byte arrays, but using the array objects to hold byte arrays has been deprecated in 3.0.

    Finally, the Java method galaxy.server.Server.serverOpReinitialize is now public since the reinitialize method is treated like any other dispatch function in 3.0.

    See the Galaxy Communicator release notes for additional information on changes and deprecations in the Java bindings.


    Step 5: Upgrading server listen status

    In 2.1, we began to develop a more complex model of determining the server listen status, since the listener-in-Hub functionality raised the possibility that servers might not have listeners associated with them. In 3.0, the situation is even more complex, since outgoing brokers share a listener with the main server. So the server listener may be listening for normal connections, for broker connections, for both, or for neither. As a result, the flags which help determine this have changed, and the most common queries have been implemented as API calls. For instance, the ways to determine if a server has Hub client connections or if it has an active listener have changed:

    Version 2.1

    if ((GalIO_ServerListenStatus(scomm) & GAL_SERVER_TYPE_MASK) == GAL_HUB_CLIENT))
      ...

    if ((GalIO_ServerListenStatus(scomm) & GAL_SERVER_TYPE_MASK) == GAL_LISTENING_SERVER)
      ...

    Version 3.0
    if (GalIO_ServerIsClient(scomm))
      ...

    if (GalIO_ServerIsListener(scomm))
      ...

    Also, in 3.0, it's possible for servers to be both clients and listeners; in 2.1, servers were one or the other. See also the functions GalIO_ServerListensForConnections and GalIO_ServerListensForBrokers.


    Step 6: Upgrading references to the call environment

    The semantics of call environments have been significantly clarified in version 3.0. This clarification has a small but possibly significant impact on users of the functions GalSS_EnvError and GalSS_EnvDestroyToken.

    In version 2.1, the call environment could "force a reply", which would cause a message to be sent to the Hub which counted as the reply to the Hub message which caused the dispatch function to fire. All subsequent messages which were written through the call environment, including the return value from the dispatch function, counted as new messages to the Hub. This functionality supported some idiosyncratic properties of the reinitialize dispatch function. We strongly discouraged people from exploiting this functionality in other cases. So for instance, while we also used this reply forcing technique to provide error and destroy replies to Hub messages, we advised programmers not to send any further "replies" through that call environment, since they would be treated as new messages. So the recommended use of GalSS_EnvError, for instance, was as follows:

    Version 2.1

    Gal_Frame dispatch_fn(Gal_Frame f, void *server_data)
    {
      GalSS_Environment *env = (GalSS_Environment *) server _data;

      ...

      if (something_bad_happened) {
        GalSS_EnvError(env, "Something bad happened");
        return (Gal_Frame) NULL;
      }

      ...
    }

    If the programmer had sent additional messages through the call environment (including dispatch function replies), all the additional messages would have been treated by the Hub as new messages:

    Version 2.1

    Gal_Frame dispatch_fn(Gal_Frame f, void *server_data)
    {
      GalSS_Environment *env = (GalSS_Environment *) server _data;

      ...

      if (something_bad_happened) {
        GalSS_EnvError(env, "Something bad happened");
        GalSS_EnvError(env, "It was really bad");
        GalSS_SetProp(f, ":details", Gal_StringObject("Really, really bad"));
        return f;
      }

      ...
    }

    Although this was documented functionality, programmers were strongly discouraged from exploiting it.

    This behavior was incoherent and difficult to understand, and it has been changed in 3.0, as part of a general clarification and extension of call environments. In 3.0, a call environment will never send more than one return message to the Hub. Only four things count as return messages: a return value from a dispatch function, a call to GalSS_EnvError, a call to GalSS_EnvDestroyToken, or a call to GalSS_EnvReply. After the first reply is sent, all subsequent replies are ignored. So in the second example here, the second call to GalSS_EnvError will have no effect, and the frame returned from the dispatch function will be ignored.


    Step 7: Upgrading calls to GalIO_SetServerData

    In three cases (brokers, connections, and servers), it is possible to store caller data for later retrieval and use inside the context of these objects. It is important that functions to free this data be provided (for when the object itself is freed, among other things), but in some cases, this was not happening. The function GalIO_SetServerData was changed to accommodate this requirement.

    As a consequence of this observation, we recommend that users now avoid returning data from _GalSS_init_server, as shown in this pre-3.0 example:

    void *_GalSS_init_server(GalIO_ServerStruct *s, int argc, char **argv)
    {
      int i, increment = 1;

      if (!GalUtil_OACheckUsage(argc, argv, oas, &i))
        exit(1);
      GalUtil_OAExtract(argc, argv, oas, "-increment", GAL_OA_INT, &increment);
      signal(SIGINT, exit_gracefully);
      /* Return value is server data. */
      return (void *) increment;
    }

    The effect of this code is to store the return value as data via GalIO_SetServerData; but in addition to this being counterintuitive, it also doesn't allow the user the opportunity to provide a data freeing function. As of now, we recommend that users call GalIO_SetServerData explicitly in their server initialization functions, as shown here:
    void *_GalSS_init_server(GalIO_ServerStruct *s, int argc, char **argv)
    {
      int i, increment = 1;

      if (!GalUtil_OACheckUsage(argc, argv, oas, &i))
        exit(1);
      GalUtil_OAExtract(argc, argv, oas, "-increment", GAL_OA_INT, &increment);
      signal(SIGINT, exit_gracefully);
      GalIO_SetServerData(s, (void *) increment, NULL);
      return (void *) NULL;
    }

    The server library has been rewritten so that a NULL return value does not overwrite the existing value. The original version still works, but it is strongly discouraged.


    Step 8a: Upgrading references to the Communicator main loop (optional)

    In 2.1 and previous releases, the preferred way of writing a main() which used the Communicator main loop looked like this:
    int main(int argc, char **argv)
    {
      GalIO_ServerStruct *server;

      server = GalSS_CmdlineInitializeServer(argc, argv);
      if (!server) {
        GalUtil_Fatal("Failed to initialize server!\n");
      }
      GalSS_RunServer(server);
      exit(0);
    }

    However, the function GalSS_CmdlineInitializeServer actually starts up the server listener, which doesn't allow us to reconfigure a number of features on an instantiated server which is not yet running. As a result, we now recommend a different way of writing main() for servers in version 3.0 and later:
    int main(int argc, char **argv)
    {
      GalIO_ServerStruct *server;

      server = GalSS_CmdlineSetupServer(argc, argv);
      if (!server) {
        GalUtil_Fatal("Failed to set up server!\n");
      }
      GalSS_StartAndRunServer(server);
      exit(0);
    }

    The old way still works (and will continue to work for the foreseeable future), but it is dispreferred.


    Step 8b: Upgrade your own main loop (advanced, optional)

    For those of you who are working with main loops other than the Communicator main loop, we've repeatedly worked to improve the embedding properties of the Galaxy Communicator API. In 3.0, as a byproduct of embedding the API in the Python and Allegro bindings, we provide another round of recommended improvements. This is an optional upgrade; however, it is such a clear improvement that we recommend that everyone do it. The current strategy is discussed in detail in the external mainloop documentation; here, we focus on the contrasts with version 2.1.

    The main loop

    In the main loop, we provide a cleaner set of callbacks, with the following changes: Note that it is not possible to embed the Communicator API with file descriptor callbacks alone, since sometimes extra data can be read into a connection's inbound queue which is not immediately processed. Therefore, at a minimum, timer callbacks must be used to handle broker and connection readers, although file descriptor based callbacks can also be used for efficiency.

    Version 2.1

    int main(int argc, char **argv)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) NULL;
      int new_argc;
      char **new_argv;
      GalSS_ServerArgs *arg_pkg;

      gcr = (GalaxyCallbackRecord *) malloc(sizeof(GalaxyCallbackRecord));
      gcr->timer_cb = (TimerCallback *) NULL;
      gcr->l = SM_NewLooper();

      /* If you want to use the built-in server arguments, you
         can use GalSS_ExtractServerArgs. Otherwise, you can just
         call GalSS_InitializeServerToplevel(). */

      arg_pkg = GalSS_DefaultServerArgs();

      /* Make sure it knows that we're using our own main loop. */
      GalSS_SAFixLoopType(arg_pkg, GAL_LOOP_EXTERNAL);
      arg_pkg = GalSS_ExtractServerArgs(arg_pkg, argc, argv,
                                        &new_argc, &new_argv);

      if (!arg_pkg) {
        /* Something bad happened, or -help was passed. */
        exit(1);
      }

      gcr->gcomm = GalSS_InitializeServerFromServerArgs(arg_pkg,
                                                        new_argc,
                                                        new_argv);
      GalSS_FreeArgPkg(arg_pkg);

      if (!gcr->gcomm) {
        fprintf(stderr, "Couldn't create a server\n");
        fflush(stderr);
        exit(1);
      }

      /* If the server is acting as a client, we'll want to
         set up callbacks for the connections; if it has its own
         listener, we'll want to set up a callback for the server,
         which will set up the listeners for the connections as
         they're received. */

      if ((GalIO_ServerListenStatus(gcr->gcomm) & GAL_SERVER_TYPE_MASK) ==
          GAL_LISTENING_SERVER) {
        gcr->server_sock = GalIO_GetServerListenSocket(gcr->gcomm);
        SM_AddFDCallback(gcr->l, gcr->server_sock, DoubleServerHandler,
                         (void *) gcr);
      } else {
        GalIO_ServerCheckHubContacts(gcr->gcomm);
        if (GalIO_GetServerNumConnections(gcr->gcomm) == 0) {
          fprintf(stderr, "Couldn't contact any Hubs\n");
          fflush(stderr);
          exit(1);
        }
        /* Add a callback for the connections. */
        GalIO_OperateOnConnections(gcr->gcomm, (void *) gcr,
                                   __AddConnectionCallback);
      }
      SM_Mainloop(gcr->l);
      exit(0);
    }

    Version 3.0
    int main(int argc, char **argv)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) NULL;
      int new_argc;
      char **new_argv;
      GalSS_ServerArgs *arg_pkg;

      gcr = (GalaxyCallbackRecord *) malloc(sizeof(GalaxyCallbackRecord));
      gcr->timer_cb = (TimerCallback *) NULL;
      gcr->l = SM_NewLooper();

      /* If you want to use the built-in server arguments, you
         can use GalSS_ExtractCmdlineServerArgs. Otherwise, you can just
         call GalSS_InitializeServerToplevel(). */

      arg_pkg = GalSS_DefaultServerArgs();

      /* Make sure it knows that we're using our own main loop. We set this
       before we ever parse the server arguments, because we don't even want
       the arguments pertaining to the loop type enabled for the user. */

      GalSS_SAFixLoopType(arg_pkg, GAL_LOOP_EXTERNAL);
      arg_pkg = GalSS_ExtractCmdlineServerArgs(arg_pkg, argc, argv,
                                               &new_argc, &new_argv);

      if (!arg_pkg) {
        /* Something bad happened, or -help was passed. */
        exit(1);
      }

      /* Now, we call GalSS_InitializeServerFromServerArgs, and we don't have
         to worry about the signature of GalSS_InitializeServerToplevel. */

      gcr->scomm = GalSS_SetupServer(arg_pkg, new_argc, new_argv);
      GalSS_FreeArgPkg(arg_pkg);

      if (!gcr->scomm) {
        fprintf(stderr, "Couldn't create a server\n");
        fflush(stderr);
        exit(1);
      }

      /* Set the connect callback for the server. This gets called
         whenever a new connection is established. */

      GalIO_AddServerConnectCallback(gcr->scomm,
                                     GCRAddConnectionCallback, (void *) gcr);
      /* The server can be a listener when it starts out, or
         it can become a listener when an outgoing broker starts up. So
         we set a callback to handle whenever this happens. */
      GalIO_AddServerCallback(gcr->scomm,
                              GAL_SERVER_LISTENER_STARTUP_EVENT,
                              GCRSetupServerListener, (void *) gcr);
      /* Similarly, if someone calls GalIO_ContactHub, it may lead to
         a new poller starting up. So we should deal with that
         as a callback too. */
      GalIO_AddServerCallback(gcr->scomm,
                              GAL_SERVER_CLIENT_POLL_STARTUP_EVENT,
                              GCRSetupServerClient, (void *) gcr);
      /* And now, something that will shut down the loop when
         the server is destroyed. */
      GalIO_AddServerCallback(gcr->scomm,
                              GAL_SERVER_DESTRUCTION_EVENT,
                              GCRDestroyServer, (void *) gcr);

      /* Now, start the server, and then the main loop. */
      if (!GalIO_ServerStart(gcr->scomm)) {
        fprintf(stderr, "Couldn't start the server\n");
        fflush(stderr);
        exit(1);
      }
      SM_Mainloop(gcr->l);
      exit(0);
    }

    The server callbacks

    Besides the fact that we've generalized this mainloop example and changed the names of the callbacks as a result, the following substantive changes can be found. We can't illustrate the changes in boldface, since they're so extensive. Version 2.1
    /* This function is only used when the server is running its
       own listener. */

    static void DoubleServerHandler(void *client_data)
    {
      int status;
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) client_data;
      GalIO_CommStruct *new_conn = (GalIO_CommStruct *) NULL;

      status = GalIO_ServerHandler(gcr->gcomm, &new_conn);

      switch (status) {
      case 1:
        /* We've got a connection. */
        __AddConnectionCallback(new_conn, (void *) gcr);
        break;
      case -1:
        /* An error has occurred. */
        GalUtil_Warn("The server has failed.\n");
        SM_RemoveAllFDCallbacks(gcr->l);
        GalIO_SetServerDone(gcr->gcomm);
        GalIO_DestroyServerStruct(gcr->gcomm);
        break;
      default:
        break;
      }
    }

    Version 3.0
    static void GCRServerListenerHandler(void *client_data)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) client_data;

      GalIO_ServerCallbackHandler(gcr->scomm, 0,
                                  (GalIO_CommStruct **) NULL);
    }

    static void GCRShutdownServerListener(GalIO_ServerStruct *scomm,
                                          void *callback_data)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) callback_data;

      GalUtil_Warn("The server has failed.\n");
      SM_RemoveAllFDCallbacks(gcr->l);
      SM_RemoveAllTimerCallbacks(gcr->l);
    }

    static void GCRSetupServerListener(GalIO_ServerStruct *scomm,
                                       void *callback_data)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) callback_data;
      /* You only need a file descriptor callback here, since
         there will be no connection requests in any internal queue. */
      SM_AddFDCallback(gcr->l, GalIO_GetServerListenSocket(scomm),
                       GCRServerListenerHandler,
                       (void *) gcr);
      GalIO_AddServerCallback(scomm,
                              GAL_SERVER_LISTENER_SHUTDOWN_EVENT,
                              GCRShutdownServerListener,
                              (void *) gcr);
    }

    /* This function is used when the server is subscribing to
       Hub listeners. */

    static void GCRServerClientHandler(void *client_data)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) client_data;

      GalIO_ServerCheckHubContacts(gcr->scomm);
    }

    static void GCRShutdownServerClient(GalIO_ServerStruct *scomm,
                                        void *callback_data)
    {
      TimerCallback *cb = (TimerCallback *) callback_data;

      SM_RemoveTimerCallback(cb);
    }

    static void GCRSetupServerClient(GalIO_ServerStruct *scomm,
                                     void *callback_data)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) callback_data;
      TimerCallback *cb;

      /* Set up a periodic task to check the hub contacts. */
      cb = SM_AddTimerCallback(gcr->l, 10, GCRServerClientHandler, (void *) gcr);
      /* Add a shutdown callback now. */
      GalIO_AddServerCallback(scomm,
                              GAL_SERVER_DESTRUCTION_EVENT,
                              GCRShutdownServerClient,
                              (void *) cb);
    }

    static void GCRDestroyServer(GalIO_ServerStruct *scomm,

                                 void *callback_data)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) callback_data;

      GalUtil_Warn("The server has been destroyed.\n");
      SM_LooperExit(gcr->l);
    }
     

    The connection callbacks

    The differences here are considerable, and the structure is so different that highlighting the contrasts is impossible. The differences are: Version 2.1
    static void DoubleConnectionHandler(void *client_data)
    {
      GalIO_CommStruct *new_conn = (GalIO_CommStruct *) client_data;
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) GalIO_GetCommData(new_conn);
      int fd = GalIO_GetCommSocket(new_conn);

      /* GalIO_ConnectionPoll returns 1 or -1 when the client
         should be terminated. */
      switch (GalIO_ConnectionPoll(new_conn)) {
      case 1:
        /* Done, stop polling. */
      case -1:
        /* Error, stop polling. */
        SM_RemoveFDCallback(gcr->l, fd);
        /* If this is the last connection, and the server
           is acting as a client, exit. */
        if (((GalIO_ServerListenStatus(gcr->gcomm) & GAL_SERVER_TYPE_MASK) ==
             GAL_HUB_CLIENT) &&
            ((GalIO_ServerListenStatus(gcr->gcomm) & GAL_HUB_CLIENT_DISCONNECT_MASK) ==
             GAL_HUB_CLIENT_DISCONNECT_SHUTDOWN) &&
            (SM_NumFDCallbacks(gcr->l) == 0)) {
          SM_LooperExit(gcr->l);
        }
        break;
      default:
        break;
      }
    }

    static void __AddConnectionCallback(GalIO_CommStruct *gcomm, void *arg)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) arg;
      int client_sock = GalIO_GetCommSocket(gcomm);

      GalIO_SetCommData(gcomm, gcr, NULL);
      SM_AddFDCallback(gcr->l, client_sock, DoubleConnectionHandler,
                       (void *) gcomm);

      /* Sometimes, God help us, there's already data coming in.
         Particularly if this is being called when the listener
         is in the Hub. */
      DoubleConnectionHandler((void *) gcomm);
    }

    Version 3.0
    typedef struct __connection_container {
      GalIO_CommStruct *gcomm;
      GalaxyCallbackRecord *gcr;
      TimerCallback *t;
      GAL_SOCKET fd;
    } ConnectionContainer;

    typedef struct __connection_container {
      GalIO_CommStruct *gcomm;
      GalaxyCallbackRecord *gcr;
      TimerCallback *t;
      GAL_SOCKET fd;
    } ConnectionContainer;

    /* GalIO_ConnectionCallbackHandler():
       -1 means an error was encountered and the connection has been destroyed.
       0 means we're in the midst of things.
       1 means we're done and the connection has been destroyed. */

    static void GCRConnectionDisconnect(GalIO_CommStruct *gcomm, void *caller_data)
    {
      ConnectionContainer *c = (ConnectionContainer *) caller_data;
      GalaxyCallbackRecord *gcr = c->gcr;

      SM_RemoveFDCallback(gcr->l, c->fd);
      SM_RemoveTimerCallback(c->t);
      free(c);
    }

    /* The loop cleanup is handled in the disconnect callback. */

    static void GCRConnectionHandler(void *client_data)
    {
      ConnectionContainer *c = (ConnectionContainer *) client_data;
      GalIO_CommStruct *gcomm = c->gcomm;

      GalIO_ConnectionCallbackHandler(gcomm, 0);
    }

    static void GCRConnectionTimerHandler(void *client_data)
    {
      /* This is called from the timer. We could go ahead and
         just call the normal connection handler, which would
         try to read from the file descriptor, but since there's
         already a file descriptor callback which triggers that
         handler, we'll only do something if there's stuff
         in the internal queues. */
      ConnectionContainer *c = (ConnectionContainer *) client_data;
      GalIO_CommStruct *gcomm = c->gcomm;

      if (GalIO_CommReadReady(gcomm) || GalIO_CommWriteReady(gcomm)) {
        GCRConnectionHandler(client_data);
      }
    }

    static void GCRAddConnectionCallback(GalIO_ServerStruct *scomm,
                                         GalIO_CommStruct *gcomm,
                                         void *callback_data)
    {
      GalaxyCallbackRecord *gcr = (GalaxyCallbackRecord *) callback_data;
      ConnectionContainer *c = (ConnectionContainer *) calloc(1, sizeof(ConnectionContainer));

      c->gcr = gcr;
      c->gcomm = gcomm;
      c->fd = GalIO_GetCommSocket(gcomm);

      /* We'll use the file descriptor callback to check the file
         descriptor, and the timer callback to check the internal queue. */
      SM_AddFDCallback(gcr->l, c->fd,
                       GCRConnectionHandler,
                       (void *) c);
      c->t = SM_AddTimerCallback(gcr->l, 5, GCRConnectionTimerHandler,
                                 (void *) c);
      /* Finally, to support brokers, and to deal with
         disconnections, we need to use the
         data slot for the connection. */
      /* Make sure you stop polling when the connection dies. */
      GalIO_AddConnectionCallback(gcomm,
                                  GAL_CONNECTION_SHUTDOWN_EVENT,
                                  GCRConnectionDisconnect,
                                  (void *) c);
      /* And now, add the callbacks for the broker setups. */
      GalIO_AddConnectionBrokerCallback(gcomm,
                                        GAL_CONNECTION_BROKER_OUT_STARTUP_EVENT,
                                        GCRSetupBrokerOut,
                                        (void *) gcr);
      GalIO_AddConnectionBrokerCallback(gcomm,
                                        GAL_CONNECTION_BROKER_IN_STARTUP_EVENT,
                                        GCRSetupBrokerIn,
                                        (void *) gcr);
    }

    Finally, we provide explicit examples of how to embed brokering in 3.0, while in 2.1 it was unclear. For more details, consult the external mainloop documentation.


    Step 9: Upgrade timed task invocation (advanced, optional)

    In version 2.1, the basic timed task was a simple function which took a void * argument. This task was set up using the functions Gal_AddTimedTask, Gal_AddTimedTaskWithFileIO, etc. There was a more complex version, represented by Gal_AddTask, etc., which manipulated a structure called a Gal_TaskPkg; this set of functions was used internally throughout the Galaxy Communicator library, but was built on top of the simpler functions, and there was no particular reason for application programmers to use them for setting up timed tasks.

    However, the timed task mechanism had a glaring shortcoming in version 2.1: while it was possible to associate both read and write sockets or both read or write file pointers with a timed task, it was not possible to tell which one led the task to be fired. Using the simpler tasks as an underlying representation, it was impossible to address this shortcoming; however, we were able to address it by "inverting" the implementation, making the complex tasks basic and implementing the simple tasks on top of them. Users of Gal_AddTask, etc., can now determine why a task was fired using the function Gal_TaskPkgRunReasons.

    In version 3.0, we recommend that everyone upgrade to complex tasks. See the timed tasks documentation for full details on the functions. Here's the sort of change we recommend making (from the  MITRE stdin polling utility):

    Version 2.1

    static void stdin_poll(void *server_data);

    Gal_AddTimedTaskWithFileIO(stdin_poll,
                               (void *) poll_struct,
                               poll_struct->ms,
                               stdin, (FILE *) NULL);

    Version 3.0
    static void stdin_poll(Gal_TaskPkg *p);

    Gal_AddTaskWithFileIO(stdin_poll,
                          (void *) poll_struct,
                          poll_struct->ms, 1,
                          stdin, (FILE *) NULL, NULL);

    Note also that when you reset this task, you should call Gal_ReAddTask, etc., on the existing task rather than create a new task. This makes a considerable difference if you ever use the threaded toplevel loop.

    Version 2.1

    static void stdin_poll(void *server_data)
    {
      ...
      Gal_AddTimedTaskWithFileIO(stdin_poll,
                                 (void *) poll_struct,
                                 poll_struct->ms,
                                 stdin, (FILE *) NULL);
      ...
    }
    Version 3.0
    static void stdin_poll(Gal_TaskPkg *p)
    {
      ...
      Gal_ReAddTaskWithFileIO(p,
                              (void *) poll_struct,
                              poll_struct->ms, 1,
                              stdin, (FILE *) NULL, NULL);
      ...
    }

    Step 10: Upgrading session management (advanced, optional)

    As discussed in detail in the new session management documentation, it's very important to keep sessions separate, especially if you expect multiple users to interact with your system simultaneously. If all your dispatch functions do is return values, and they never set up other callbacks, like timed tasks or broker callbacks, and they never write new messages to the Hub, you don't need to worry about anything, since the server wrappers have always handled that case cleanly. However, all the other cases have been poorly supported up to this point. All these problems have been fixed in version 3.0, as far as we know, and we strongly encourage people to take advantage of the improvements. For instance, the function GalSS_EnvBrokerDataInInit allows the programmer to set up an environment-aware broker callback.

    Without environments

    static void audio_handler(GalIO_BrokerStruct *broker_struct,
                              void *data, Gal_ObjectType data_type,
                              int n_samples)
    {
      DataHandler *d = (DataHandler *) GalIO_GetBrokerCallerData(broker_struct);
      Gal_Frame f;

      ...

          f = Gal_MakeFrame("notify", GAL_CLAUSE);
          Gal_SetProp(f, ":notification", Gal_StringObject("Audio received."));
          GalIO_CommWriteFrame(d->gcomm, f, 0);
          Gal_FreeFrame(f);

      ...
    }

    void __FreeDataHandler(GalIO_BrokerStruct *b, void *data);

    Gal_Frame receive_audio(Gal_Frame f, void *server_data)
    {
      DataHandler *d = (DataHandler *) malloc(sizeof(DataHandler));
      GalIO_BrokerStruct *b;
      char *host = Gal_GetString(f, ":binary_host");
      int port = Gal_GetInt(f, ":binary_port");

      d->data_buf = (char *) NULL;
      d->size = 0;
      d->gcomm = GalSS_EnvComm((GalSS_Environment *) server_data);

      if (host && port) {
         b = GalIO_CommBrokerDataInInit(d->gcomm, host, port, f, audio_handler,
                                        0, d, __FreeDataHandler);
        if (b) {
          GalIO_SetBrokerActive(b);
        }
      } else {
        free(d);
      }
      return (Gal_Frame) NULL;
    }

    With environments
    static void env_audio_handler(GalIO_BrokerStruct *broker_struct,
                                  void *data, Gal_ObjectType data_type,
                                  int n_samples)
    {
      DataHandler *d = (DataHandler *) GalIO_GetBrokerCallerData(broker_struct);
      Gal_Frame f;
      GalSS_Environment *env = GalSS_BrokerGetEnvironment(broker_struct);

      ...

          f = Gal_MakeFrame("notify", GAL_CLAUSE);
          Gal_SetProp(f, ":notification", Gal_StringObject("Audio received."));
          GalSS_EnvWriteFrame(env, f, 0);
          Gal_FreeFrame(f);

      ...
    }

    void __FreeDataHandler(GalIO_BrokerStruct *b, void *data);

    Gal_Frame receive_audio(Gal_Frame f, void *server_data)
    {
      DataHandler *d = (DataHandler *) malloc(sizeof(DataHandler));
      GalIO_BrokerStruct *b;
      char *host = Gal_GetString(f, ":binary_host");
      int port = Gal_GetInt(f, ":binary_port");

      d->data_buf = (char *) NULL;
      d->size = 0;

      if (host && port) {
        b = GalSS_EnvBrokerDataInInit((GalSS_Environment *) server_data,
                                      host, port, f,
                                      env_audio_handler, 0, d, __FreeDataHandler);
        if (b) {
          GalIO_SetBrokerActive(b);
        }
      } else {
        free(d);
      }
      return (Gal_Frame) NULL;
    }

    At this point, we know of no reason for the programmer to use GalIO_CommWriteFrame and GalIO_DispatchViaHub. , and we strongly encourage everyone to replace all calls to these functions with calls to GalSS_EnvWriteFrame and GalSS_EnvDispatchFrame. We've provided support for the following contexts: If you discover a circumstance in which it is not possible to use an environment for writing, please notify us.
    License / Documentation home / Help and feedback
    Last updated January 17, 2001