.. include:: alias.rst .. _service_api: |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 ^^^^^^^^^^^^^^^ .. code-block:: cpp 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 ^^^^^^^^^^^^^^^^^ .. code-block:: cpp 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 ^^^^^^^^^^^^^^^^^ .. code-block:: cpp 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 ^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: cpp 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 ^^^^^^^^^^^^^ .. code-block:: cpp 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 ^^^^^^^ .. code-block:: cpp 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 ^^^^^^^^^^^^^^^^ .. code-block:: cpp 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 ^^^^^^^^^^^^^ .. code-block:: cpp 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 ^^^^^^^^^^^^^^^ .. code-block:: cpp int cancelOperation(CallHandle); ``cancelOperation`` cancels the operation given by the passed ``CallHandle``. If an error occurs, -1 will be returned, zero otherwise. spinOnce ^^^^^^^^ .. code-block:: cpp 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 ------------------- .. code-block:: cpp 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 ------------- .. code-block:: cpp 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 ------------------------ .. code-block:: cpp 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 ^^^^ .. code-block:: cpp 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 ^^^^^^^^^^^^^^^^^^^^ .. code-block:: cpp void installSigIntHandler(); ``installSigIntHandler`` installs a ``SIGINT`` handler that causes ``shutdownSignalled`` to return true. shutdownSignalled ^^^^^^^^^^^^^^^^^ .. code-block:: cpp int shutdownSignalled(); ``shutdownSignalled`` returns ``true`` if the service is supposed to shut down due to a ``SIGINT`` signal. allocateArray ^^^^^^^^^^^^^ .. code-block:: cpp 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 :ref:`Array Manipulation section below `. arrayLength ^^^^^^^^^^^ .. code-block:: cpp 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 :ref:`Array Manipulation section below `. freeArray ^^^^^^^^^ .. code-block:: cpp 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 :ref:`Array Manipulation section below `. resizeArray ^^^^^^^^^^^ .. code-block:: cpp 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 :ref:`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: .. code-block:: cpp 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 :ref:`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: .. code-block:: cpp 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: .. code-block:: cpp 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. .. _msg_functions: Function declarations ^^^^^^^^^^^^^^^^^^^^^ The remainder of the header consists of a function definition returning a descriptor of the operation or event in question, as follows: .. code-block:: cpp /* 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. .. code-block:: cpp /* 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 :ref:`Array Manipulation section below `. .. _arrays: 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``.