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... | |
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:
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.
#define bio_call_service | ( | service, | |
message, | |||
cancel_signal | |||
) |
Make a synchronous service call.
The calling coroutine will be suspended until one of the following happens:
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:
service | The service handle |
message | The message for this call |
cancel_signal | Cancel signal for this call. If an invalid handle is passed, this call will not be cancellable. |
#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.
userdata | The userdata from the coroutine entrypoint. |
mailbox_ptr | Pointer to a mailbox variable of the appropriate message type. |
args_ptr | Pointer to a variable of the same type as args that was passed to bio_start_service |
#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.
msg | The message from a caller. |
#define bio_notify_service | ( | service, | |
message, | |||
retry_condition | |||
) |
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.
service | A variable of type BIO_SERVICE |
message | A message for the service |
retry_condition | A boolean expression |
#define bio_respond | ( | 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:
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:
Alternatively, the service might have allocated resources before hand and they should be freed upon cancellation:
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:
#define BIO_SERVICE | ( | T | ) |
A service reference struct.
This is a combination of a bio_coro_t handle and a BIO_MAILBOX handle
#define bio_service_loop | ( | msg, | |
mailbox | |||
) |
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:
#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_service_state | ( | service | ) | bio_coro_state((service).coro) |
Check a service's state.
service | A variable of type BIO_SERVICE |
#define bio_start_service | ( | ptr, | |
entry, | |||
args | |||
) | bio_start_service_ex(ptr, entry, args, NULL) |
Start a service coroutine.
ptr | Pointer to a variable of type BIO_SERVICE |
entry | Entrypoint to the service (bio_entrypoint_t) |
arg | Start argument to be passed to the service |
#define bio_start_service_ex | ( | ptr, | |
entry, | |||
args, | |||
options | |||
) |
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.
ptr | Pointer to a variable of type BIO_SERVICE |
entry | Entrypoint to the service (bio_entrypoint_t) |
arg | Start argument to be passed to the service |
options | Options for the service (const bio_service_options_t*), can be NULL |
#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.
enum 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. |