bio
 
Loading...
Searching...
No Matches
Service

Idiomatic coroutine interaction. More...

Data Structures

struct  bio_service_msg_base_t
 The base for a message type of a service. More...
 
struct  bio_service_options_t
 Options for service. More...
 

Macros

#define BIO_SERVICE(T)
 A service reference struct.
 
#define BIO_SERVICE_MSG   bio_service_msg_base_t bio__service_msg_base;
 Helper to include bio_service_msg_base_t in a message type.
 
#define bio_start_service(ptr, entry, args)    bio_start_service_ex(ptr, entry, args, NULL)
 Start a service coroutine.
 
#define bio_start_service_ex(ptr, entry, args, options)
 Start a service coroutine.
 
#define bio_stop_service(service)    bio__service_stop((service).coro, (service).mailbox.bio__handle)
 Stop a service.
 
#define bio_get_service_info(userdata, mailbox_ptr, args_ptr)    bio__service_get_info(userdata, &(mailbox_ptr)->bio__handle, args_ptr)
 The service entrypoint must call this as soon as possible.
 
#define bio_call_service(service, message, cancel_signal)
 Make a synchronous service call.
 
#define bio_service_state(service)    bio_coro_state((service).coro)
 Check a service's state.
 
#define bio_notify_service(service, message, retry_condition)
 Send a notification to a service.
 
#define bio_is_call_cancelled(msg)    bio__service_is_call_cancelled(&(msg).bio__service_msg_base)
 Check whether a call was cancelled.
 
#define bio_service_loop(msg, mailbox)
 Message loop helper for the service.
 
#define bio_respond(msg)
 Helper for cancellable service call.
 

Enumerations

enum  bio_call_status_t { BIO_CALL_OK , BIO_CALL_CANCELLED , BIO_CALL_TARGET_DEAD }
 The status of a call using bio_call_service. More...
 

Detailed Description

Idiomatic coroutine interaction.

This is inspired by Erlang's gen_server and proc_lib.

bio already provides Coroutine and Mailbox. However, there are several higher-level concerns that it does not address:

This module offers a simple way to deal with the above concerns.

Example:

typedef struct {
BIO_SERVICE_MSG // Required header
int lhs;
int rhs;
int* result;
} msg_t;
void service_entry(void* userdata) {
int start_arg;
BIO_MAILBOX(msg_t) mailbox;
bio_get_service_info(userdata, &mailbox, &start_arg);
bio_service_loop(msg, mailbox) {
// Calculate first, which may cause context switch
int result = msg.lhs + msg.rhs;
// Then write result in the bio_respond block
bio_respond(msg) {
*msg.result = result;
}
}
// The mailbox will be closed by the service module
}
void use_service(void) {
BIO_SERVICE(msg_t) adder;
// Start argument is stack-allocated
int start_arg = 42;
bio_start_service(&adder, service_entry, start_arg);
bio_signal_t no_cancel = { 0 };
int result;
msg_t msg = {
.lhs = 1,
.rhs = 2,
// Write result to a stack-allocated variable
.result = &result,
};
bio_call_status_t status = bio_call_service(adder, msg, no_cancel);
if (status == BIO_CALL_OK) {
BIO_INFO("The result is: %d", result);
}
}
#define BIO_INFO(...)
Log a message at BIO_LOG_LEVEL_DEBUG level.
Definition bio.h:48
#define BIO_MAILBOX(T)
Define a mailbox type.
Definition mailbox.h:97
#define bio_get_service_info(userdata, mailbox_ptr, args_ptr)
The service entrypoint must call this as soon as possible.
Definition service.h:152
#define BIO_SERVICE(T)
A service reference struct.
Definition service.h:84
#define bio_service_loop(msg, mailbox)
Message loop helper for the service.
Definition service.h:249
#define bio_call_service(service, message, cancel_signal)
Make a synchronous service call.
Definition service.h:180
#define BIO_SERVICE_MSG
Helper to include bio_service_msg_base_t in a message type.
Definition service.h:91
#define bio_start_service(ptr, entry, args)
Start a service coroutine.
Definition service.h:102
bio_call_status_t
The status of a call using bio_call_service.
Definition service.h:39
#define bio_respond(msg)
Helper for cancellable service call.
Definition service.h:316
#define bio_stop_service(service)
Stop a service.
Definition service.h:141
@ BIO_CALL_OK
The call was handled by the service.
Definition service.h:41
Handle to a signal.
Definition bio.h:147

In the above example, the service consumer makes use of several stack-allocated values. This is typical for C code. They would be invalid to access if it has terminated. However, the service modules provide several guarantee so that it is safe to do so. These will be explained in this document.

Macro Definition Documentation

◆ bio_call_service

#define bio_call_service (   service,
  message,
  cancel_signal 
)
Value:
( \
BIO__TYPECHECK_EXP((message), *((service).mailbox.bio__message)), \
bio__service_call( \
(service).coro, \
(service).mailbox.bio__handle, \
&((message).bio__service_msg_base), \
&(message), \
sizeof(message), \
cancel_signal \
) \
)

Make a synchronous service call.

The calling coroutine will be suspended until one of the following happens:

  • The service responds to the call.
  • The service terminates before or during the call.
  • The call is cancelled.

The message may contain pointers to stack-allocated variables or resources that will be freed when the calling coroutine terminates as long as both the caller and the callee follow the guidelines in this document.

To implement service call with timeout, something like this can be used:

void call_with_timeout(void) {
BIO_SERVICE(msg_t) adder; // Assume we retrieved this somehow
bio_raise_signal_after(cancel, 2000); // Allow 2s before cancelling
int result;
msg_t msg = {
.lhs = 1,
.rhs = 2,
// Write result to a stack-allocated variable
.result = &result,
};
bio_call_status_t status = bio_call_service(adder, msg, cancel);
// status might be BIO_CALL_CANCELLED
}
bio_signal_t bio_make_signal(void)
Create a new signal.
void bio_raise_signal_after(bio_signal_t signal, bio_time_t time_ms)
Raise a signal after a delay.
Parameters
serviceThe service handle
messageThe message for this call
cancel_signalCancel signal for this call. If an invalid handle is passed, this call will not be cancellable.
Returns
A value of type bio_call_status_t
See also
bio_start_service

◆ bio_get_service_info

#define bio_get_service_info (   userdata,
  mailbox_ptr,
  args_ptr 
)     bio__service_get_info(userdata, &(mailbox_ptr)->bio__handle, args_ptr)

The service entrypoint must call this as soon as possible.

Parameters
userdataThe userdata from the coroutine entrypoint.
mailbox_ptrPointer to a mailbox variable of the appropriate message type.
args_ptrPointer to a variable of the same type as args that was passed to bio_start_service

◆ bio_is_call_cancelled

#define bio_is_call_cancelled (   msg)     bio__service_is_call_cancelled(&(msg).bio__service_msg_base)

Check whether a call was cancelled.

The service handler would typically call this after it has done something that involves context-switching (e.g: querying a database).

If this returns true, the service can stop its handler early. In addition, if the respond involves allocating some resources, they should be freed as the caller is no longer interested in the result.

Parameters
msgThe message from a caller.
Returns
Whether the call was cancelled.

◆ bio_notify_service

#define bio_notify_service (   service,
  message,
  retry_condition 
)
Value:
((bio_service_state(service) != BIO_CORO_DEAD) && (retry_condition)), \
(service).mailbox, \
(message) \
)
@ BIO_CORO_DEAD
The coroutine has terminated.
Definition bio.h:512
#define bio_wait_and_send_message(condition, mailbox, message)
A more reliable way to send message.
Definition mailbox.h:176
#define bio_service_state(service)
Check a service's state.
Definition service.h:199

Send a notification to a service.

This is similar to bio_call_service but a respond is not expected. The calling coroutine might suspend until the message has been delivered or the service has terminated prematurely.

Parameters
serviceA variable of type BIO_SERVICE
messageA message for the service
retry_conditionA boolean expression
See also
bio_send_message

◆ bio_respond

#define bio_respond (   msg)
Value:
for ( \
bool bio__should_respond = bio_begin_response(msg); \
bio__should_respond; \
bio__should_respond = false, bio_end_response(msg) \
)

Helper for cancellable service call.

Instead of writing the result directly to the request message, the service should do its computation using local variables first. Only at the final step, it would write the result in a bio_respond block:

*msg.result = result;
}

This is to ensure that if the call has been cancelled and the caller has terminated, the write would not be executed.

If the result involves resource allocation, the service should check whether that is needed before responding:

bio_respond(msg) {
*msg.result = malloc(...);
}
}
#define bio_is_call_cancelled(msg)
Check whether a call was cancelled.
Definition service.h:235

Alternatively, the service might have allocated resources before hand and they should be freed upon cancellation:

// Allocate resource for computation
resource_t* resource = alloc_resource();
// Do computation
// ...
// The call might be cancelled by this point
bio_respond(msg) {
*msg.result = resource;
}
} else {
// There is no one to receive this
free_resource(resource);
}

The response writing code block will also be skipped if the caller uses bio_notify_service to send the request message. Thus, it is safe, albeit potentially inefficient to use mismatched service call types: Sending a notification using a message type with call semantics.

This must be called to resume the caller's execution, otherwise the caller might be suspended indefinitely. In a void but synchrnous service call, use an empty block:

bio_respond(msg) {}

◆ BIO_SERVICE

#define BIO_SERVICE (   T)
Value:
struct { \
bio_coro_t coro; \
BIO_MAILBOX(T) mailbox; \
}
Handle to a coroutine.
Definition bio.h:137

A service reference struct.

This is a combination of a bio_coro_t handle and a BIO_MAILBOX handle

◆ bio_service_loop

#define bio_service_loop (   msg,
  mailbox 
)
Value:
bio_foreach_message(msg, mailbox) \
for ( \
int bio__svc_loop = 0; \
(bio__svc_loop < 1) && !bio_is_call_cancelled(msg); \
++bio__svc_loop \
)
#define bio_foreach_message(msg, mailbox)
Convenient message loop macro.
Definition mailbox.h:245

Message loop helper for the service.

This should be used instead of manually receiving message. Refer to the example at the beginning of this section.

This will automatically do the following:

◆ BIO_SERVICE_MSG

#define BIO_SERVICE_MSG   bio_service_msg_base_t bio__service_msg_base;

Helper to include bio_service_msg_base_t in a message type.

◆ bio_service_state

#define bio_service_state (   service)     bio_coro_state((service).coro)

Check a service's state.

Parameters
serviceA variable of type BIO_SERVICE
Returns
A value of type bio_coro_state_t

◆ bio_start_service

#define bio_start_service (   ptr,
  entry,
  args 
)     bio_start_service_ex(ptr, entry, args, NULL)

Start a service coroutine.

Parameters
ptrPointer to a variable of type BIO_SERVICE
entryEntrypoint to the service (bio_entrypoint_t)
argStart argument to be passed to the service
See also
bio_start_service_ex

◆ bio_start_service_ex

#define bio_start_service_ex (   ptr,
  entry,
  args,
  options 
)
Value:
bio__service_start(\
&(ptr)->coro, \
&(ptr)->mailbox.bio__handle, \
sizeof(*((ptr)->mailbox.bio__message)), \
options, \
entry, \
&args, \
sizeof(args) \
)

Start a service coroutine.

The caller will suspend until the service has called bio_get_service_info. This ensures that the stack allocated args has been copied from the caller's stack into the service's stack.

Parameters
ptrPointer to a variable of type BIO_SERVICE
entryEntrypoint to the service (bio_entrypoint_t)
argStart argument to be passed to the service
optionsOptions for the service (const bio_service_options_t*), can be NULL
See also
bio_stop_service
bio_call_service

◆ bio_stop_service

#define bio_stop_service (   service)     bio__service_stop((service).coro, (service).mailbox.bio__handle)

Stop a service.

The caller will suspends until the targeted service has terminated. This is to ensure that the service can no longer refer to any shared resources that was passed through bio_start_service. The caller may safely release them after this returns.

As with all bio's resources, stopping an already stopped service is not an error.

Enumeration Type Documentation

◆ bio_call_status_t

The status of a call using bio_call_service.

Enumerator
BIO_CALL_OK 

The call was handled by the service.

BIO_CALL_CANCELLED 

The call was cancelled by the caller.

BIO_CALL_TARGET_DEAD 

The service has terminated during or before the call.