RaceCom API Documentation

The following functions constitute the API which are required for implementing services. They are defined in the header file racecom.h, which is the only file that must be included.

Functions

registerService

Service* registerService(const char master_uri,
                         const char* address,
                         const char* name);

registerService registers a new service with the master given by master_uri. The service name is passed in name. The address field is a ZeroMQ TCP URL, e.g. tcp://eth0. It is used as a prefix to generate the actual event or operation endpoint. More specifically, using the above prefix, racecom will use the ZeroMQ endpoint name tcp://eth0:* to register an event. A pointer to the newly created service is returned if no error occurred.

If an error occurs, NULL will be returned and errno will contain further information. Error codes from zmq_socket, zmq_connect, zmq_send, zmq_msg_init or zmq_msg_recv are applicable.

unregisterService

void unregisterService(Service *service);

unregisterService unregisters the passed Service from the master node. It must be called to ensure a clean shutdown of the service.

registerOperation

int registerOperation(Service*,
                      const char* name,
                      const OperationDescriptor* descriptor,
                      const OperationHandler handler,
                      void* hint);

registerOperation registers a Service Operation for the passed Service. The operation will be registered under the passed name. The request, result and error types are defined by the passed operation descriptor, and the operation handler function call back is passed in handler. An optional user data structure may be passed as a hint void pointer, which will be passed to all invocations of the handler function.

If no error or unexpected master response is encountered, zero is returned.

If an error occurs, the return value will be -2 if there is already an operation advertised with the passed name. For internal errors, -1 will be returned and errno will contain the error description. Error codes from zmq_socket, zmq_send, zmq_msg_init, zmq_msg_recv, zmq_bind or zmq_getsockopt are applicable.

registerCancelCallback

int registerCancelCallback(CallHandle,
                           CancelCallback,
                           void *hint);

registerCancelCallback registers a new CancelCallback for the passed CallHandle. The CancelCallback will be called when the corresponding operation call is cancelled by the caller. If the passed CallHandle already has a CancelCallback registered, it is simply overwritten. An optional user data structure may be passed as a hint void pointer, which will be passed to all invocations of the handler function. Note that registerCancelCallback should be called before registerOperation returns. Otherwise the CancelCallback might not be invoked if the operation is cancelled immediately. If an error occurs, -1 will be returned, zero otherwise.

registerEvent

Publisher* registerEvent(Service*,
                         const char* name,
                         const EventDescriptor* descriptor);

registerEvent registers a new event publisher for the passed Service and returns it. The publisher will be registered under the passed name. The request, result and error types are defined by the passed event descriptor.

If an error occurs, NULL will be returned and errno will contain further information. Error codes from zmq_socket, zmq_send, zmq_msg_init, zmq_msg_recv, zmq_bind or zmq_getsockopt are applicable.

publish

int publish(Publisher*, const void* value);

publish publishes the value given by its pointer to the passed publisher. The ownership of the passed value remains with the caller, i.e. the service implementor needs to deallocate the corresponding memory. If no error occurs, zero will be returned.

If an error occurs, -1 will be returned and errno will contain further information. Error codes from zmq_msg_init_data, zmq_sendmsg or zmq_msg_close are applicable.

succeedOperation

int succeedOperation(CallHandle, void* value);

succeedOperation sends the value passed by its pointer as a result on the given CallHandle. The ownership of the passed value remains with the caller, i.e. the service implementor needs to deallocate the corresponding memory. If an error occurs, -1 will be returned, zero otherwise.

failOperation

int failOperation(CallHandle, void* value);

failOperation sends the value passed by its pointer as an error on the given CallHandle. The ownership of the passed value remains with the caller, i.e. the service implementor needs to deallocate the corresponding memory. If an error occurs, -1 will be returned, zero otherwise.

cancelOperation

int cancelOperation(CallHandle);

cancelOperation cancels the operation given by the passed CallHandle. If an error occurs, -1 will be returned, zero otherwise.

spinOnce

int spinOnce(Service*, int timeout);

spinOnce takes the given Service and polls for pending requests or responses from all registered service operations with the given timeout in milliseconds. In case of errors, a value of -1 will be returned, zero otherwise. Use this variant of spinOnce if you only have one service or completely independent services in separate threads. For handling multiple services in one main loop use spinOnceMulti.

spinOnceWithPollers

int spinOnceWithPollers(Service *, int timeout, struct pollfd *additional_poll_fds, size_t n_poll_fds);

spinOnceWithPollers is similar to spinOnce. It blocks no longer than timeout and until either any service requests have been received or a file descriptor in additional_poll_fds becomes ready. That way, it allows to combine handling of RaceCom’s communication handlers with additional file descriptors, e.g. pipes or plain sockets. Events that occurred for additional_poll_fds are stored in additional_poll_fds[].revents.

spinOnceMulti

int spinOnceMulti(Service **, size_t n_services, int timeout);

spinOnceMulti is similar to spinOnce except that it accepts multiple services. It blocks no longer than timeout and until any request from any of the given services has been received. Note that the services must always be provided in the same order if spinOnceMulti is used in different threads, otherwise this could lead to deadlocks.

spinOnceMultiWithPollers

int spinOnceMultiWithPollers(Service **, size_t n_services, int timeout, struct pollfd *additional_poll_fds, size_t n_poll_fds);

spinOnceMultiWithPollers is similar to spinOnceMulti. It blocks no longer than timeout and until either any service requests have been received or a file descriptor in additional_poll_fds becomes ready. That way, it allows to combine handling of multiple RaceCom’s communication handlers with additional file descriptors, e.g. pipes or plain sockets. Note that the services must always be provided in the same order if spinOnceMulti is used in different threads, otherwise this could lead to deadlocks.

spin

void spin(Service*);

spin takes the given Service and processes all registered service operations. If a SIGINT handler has been installed with installSigIntHandler, it will return upon receiving SIGINT. Otherwise, it does not return.

installSigIntHandler

void installSigIntHandler();

installSigIntHandler installs a SIGINT handler that causes shutdownSignalled to return true.

shutdownSignalled

int shutdownSignalled();

shutdownSignalled returns true if the service is supposed to shut down due to a SIGINT signal.

allocateArray

void *allocateArray(uint64_t size, uint64_t elemSize);

allocateArray allocates a contiguous block of memory for storing an array and returns a pointer to the first element memory location. The block is large enough to contain size elements of size elemSize. The array is initialized with zeros. Arrays allocated with this function must be deallocated using the freeArray function below. If either size parameter is zero, a NULL pointer is returned.

More information concerning arrays in RaceCom can be found in the Array Manipulation section below.

arrayLength

uint64_t arrayLength(const void *);

arrayLength returns the number of elements that the array can hold.

More information concerning arrays in RaceCom can be found in the Array Manipulation section below.

freeArray

void freeArray(void*);

freeArray deallocates the block of memory allocated using allocateArray. Note that due to the internal representation of arrays in RaceCom, this is not equivalent to e.g. free.

More information concerning arrays in RaceCom can be found in the Array Manipulation section below.

resizeArray

void* resizeArray(void* array, uint64_t size, uint64_t elemSize);

resizeArray allocates a new array of size given by size and elemSize and copies the values from the old to the new array (up to the smaller of the two array sizes). The old array is freed and the new one returned. The passed elemSize must match the elemSize of the passed old array. If the new size is larger than the old size, the remainder of the new array will be initialized with zeros. Arrays allocated with this function must be deallocated using the freeArray function above. If either size parameter is zero, a NULL pointer is returned.

More information concerning arrays in RaceCom can be found in the Array Manipulation section below.

RaceCom Service Operation Callbacks

The principle behind writing a Service Operation callback function is as follows: If an incoming request for the operation contains a valid request object, it is deserialized and the registered callback handler is invoked. The signature for an operation handler is as follows:

void OperationHandler(const CallHandle h, void* request, void* hint);

The first argument is a CallHandle provided by RaceCom to allow the operation handler to succeed or fail the operation, resulting in a result or error response, respectively.

Note

It is the operation handler’s responsibility to call either succeedOperation or failOperation with this handle. However, the lifetime of the handle is independent of the operation handler, i.e. it is possible to return from the operation handler, but keep the handle stored somewhere (e.g. passed to a new thread servicing the operation), as long as it is guaranteed that the handle is closed using either succeedOperation or failOperation at some later time.

This makes it possible to create highly responsive services that give the service implementor complete flexibility with regards to multithreading and asynchronicity.

The second parameter to the operation handler (void* request) is a pointer to the actual request value.

Note

It is the operation handler’s responsibility to deallocate this structure at some point. To this effect, the header and source files created by the message generator contains a function destroy_OP_request() for a service operation message type definition called OP. See the Function Declarations section below for more information.

Finally, the last argument to the operation handler (void* hint) constitutes a convenient way to pass arbitrary additional data, e.g. a C++ this pointer, to the operation handler. It is the same pointer passed to registerOperation when registering the operation.

RaceCom Auto-generated API for Message Types

In order to be able to manipulate objects of the types defined in event or service operation files, the code generator race-gen creates a pair of files per type, a header and a source file. For the scope of this document, only the header is of interest, as it contains the function declarations required for the allocation, (de-)serializiation and destruction of the respective type.

In detail, the header file contains the following elements.

Type definitions

For an Event message, the header contains a typedef statement for the C-equivalent of the message type. For the Ping event from the example service above, this reads as follows:

typedef struct Ping {
  int32_t count;
} Ping;

For a Service Operation, there are three type definitions, one each for the request, result and error types. An example for the AddTwoInts operation:

typedef struct AddTwoInts_request {
  int32_t a;
  int32_t b;
} AddTwoInts_request;

typedef int32_t AddTwoInts_result;

typedef char * AddTwoInts_error;

As can be seen, the operation name is suffixed with an underscore followed by the field for each of the three parts.

These types can then be used by a service implementor to create objects of the respective types.

Function declarations

The remainder of the header consists of a function definition returning a descriptor of the operation or event in question, as follows:

/* in Ping.h: */
/* EventDescriptor for Ping */
const EventDescriptor *PingDescriptor();

/* in AddTwoInts.h: */
/* OperationDescriptor for AddTwoInts */
const OperationDescriptor *AddTwoIntsDescriptor();

Note that the actual contents of the operation descriptor are only of importance to internal RaceCom functionality, as the descriptors are only required to be passed to the respective register function.

After the descriptor function definition, there is a set of manipulation functions related to the actual types. We will show only an example for the Ping event, for operations each set of functions is present thrice, once for each part of the service operation definition, following similar naming coventions as for the type definitions above.

/* functions for type Ping */
uint8_t *serialize_Ping(const Ping *obj, uint8_t *data);
const uint8_t *deserialize_Ping(const uint8_t *data, Ping *obj);
uint64_t sizeof_Ping(const Ping *obj);
Ping *allocate_Ping();
void destruct_Ping(Ping *obj);
void free_Ping(Ping *obj);

In all likelihood, most of these functions need never be used by service implementors directly. The single exception to this is the descruct_TYPE function for a given type TYPE, as it recursively frees all arrays contained within on object of this type. More information concerning arrays in RaceCom can be found in the Array Manipulation section below.

Array Manipulation

When dealing with message types containing arrays, special care has to be taken when allocating and deallocating. Internally, the in-memory representation of an array contains the length of the array as well as the actual array contents, which is important for (de-)serialization purposes.

In memory, the first 8 bytes of the allocated memory block are reserved for the array size, and the pointer returned by allocateArray points to the location immediately after this size field, to allow using the pointer directly as a regular C-type array. arrayLength can then read the array length given that pointer, but for free -ing the memory block, some preliminary pointer arithmetic is necessary.

It is therefore crucial to use the provided allocateArray function to create an array for a given type, and conversely, freeArray to destruct it after use.

Another common use case, resizing an existing array, e.g. to increase its size, is covered by the resizeArray function. It allocates a new array according to its parameters and copies the contents of the passed array to this new array. If the new array is smaller than the passed array, the array contents up to this smaller size are copied. If the new array is larger than the passed array, the remainder of the array will be filled with zeros. The passed array is freed, but the returned, resized array must obviously be freed eventuall by a call to freeArray.