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
.