bio
 
Loading...
Searching...
No Matches
Handle

Handles are the better pointers. More...

Data Structures

struct  bio_handle_t
 

Macros

#define BIO_INVALID_HANDLE   ((bio_handle_t){ .index = 0 })
 An invalid handle.
 

Functions

bio_handle_t bio_make_handle (void *obj, const bio_tag_t *tag)
 Create a new handle.
 
void * bio_resolve_handle (bio_handle_t handle, const bio_tag_t *tag)
 Resolve a handle, returning the associated pointer.
 
void * bio_close_handle (bio_handle_t handle, const bio_tag_t *tag)
 Close a handle and invalidate it.
 
const bio_tag_tbio_handle_info (bio_handle_t handle)
 Retrieve the associated tag of a handle.
 
static int bio_handle_compare (bio_handle_t lhs, bio_handle_t rhs)
 Compare two handles.
 

Detailed Description

Handles are the better pointers.

The mechanism to manage handles are exposed so that user programs can also make use of it.

bio uses handles for all of its resources. This alleviates the problems of stale references and double free. In a concurrent environment, the lifetime of resources are often tricky to determine. Handles are safe to pass around and act on. Any operations on a stale handle will be noop.

Handle enables the following pattern:

void client_handler(void* userdata) {
bio_socket_t socket; // Assume we got this somehow
// Spawn a writer, giving it the same socket
bio_coro_t writer = bio_spawn(writer_entry, &socket);
char buf[1024];
bio_error_t error = { 0 };
while (true) {
// Read from the socket
size_t bytes_received = bio_net_recv(socket, buf, sizeof(buf), &error);
if (bio_has_error(&error)) {
// Break out of the loop if there is an error
break;
}
// Handle message and maybe message writer for a response
}
// Potential double free but it is safe
// This will also cause the writer to terminate
bio_net_close(socket, NULL);
// Wait for writer to actually terminate
bio_join(writer);
}
void writer_entry(void* userdata) {
bio_socket_t socket = *(bio_socket_t*)userdata;
bio_error_t error = { 0 };
while (true) {
// Get a message from some source
// Send it
size_t bytes_sent = bio_net_send(socket, msg, msg_len, &error);
if (bio_has_error(&error)) {
// Break out of the loop if there is an error
break;
}
}
// Potential double free but it is safe
// This also cause the reader to terminate
bio_net_close(socket, NULL);
}
static bio_coro_t bio_spawn(bio_entrypoint_t entrypoint, void *userdata)
Spawn a new coroutine.
Definition bio.h:731
static bool bio_has_error(bio_error_t *error)
Convenient function to check whether an error was encountered.
Definition bio.h:1281
static void bio_join(bio_coro_t coro)
Convenient function to wait for a coroutine to terminate.
Definition bio.h:947
size_t bio_net_recv(bio_socket_t socket, void *buf, size_t size, bio_error_t *error)
Receive from a socket.
size_t bio_net_send(bio_socket_t socket, const void *buf, size_t size, bio_error_t *error)
Send to a socket.
bool bio_net_close(bio_socket_t socket, bio_error_t *error)
Close a socket.
Handle to a coroutine.
Definition bio.h:137
An error returned from a function.
Definition bio.h:468
Handle to a socket.
Definition net.h:15

In the above example, as soon as either the reader or the writer coroutine encounters an I/O error, it will break out of the loop and close the socket. This in turn cause the other coroutine to also break out. bio_net_close will be called twice on the same socket but it is safe and the associated resources will only be released once.

By making double free safe to execute, user code can be greatly simplified since there is no need to track when a resource was freed. In fact, freeing a resource that another coroutine is waiting on is the idiomatic way to signal to that coroutine to terminate. For example, in a web server, a route handler can close the listening socket to tell the acceptor coroutine to stop accepting new connections. This can be used to implement a "remote shutdown".

Macro Definition Documentation

◆ BIO_INVALID_HANDLE

#define BIO_INVALID_HANDLE   ((bio_handle_t){ .index = 0 })

An invalid handle.

Resolving this will always return NULL. This macro is just a short hand for setting all fields in a bio_handle_t to 0. Thus, a handle can be zero-initialized, allowing "Zero Is Initialization" (ZII).

See also
bio_resolve_handle

Function Documentation

◆ bio_close_handle()

void * bio_close_handle ( bio_handle_t  handle,
const bio_tag_t tag 
)

Close a handle and invalidate it.

This is similar to bio_resolve_handle but it also invalidates the handle.

Take note that the associated pointer is also returned so that the calling code can perform cleanup of the associated data.

Just like with bio_resolve_handle, always check for a NULL return and make it noop.

◆ bio_handle_compare()

static int bio_handle_compare ( bio_handle_t  lhs,
bio_handle_t  rhs 
)
inlinestatic

Compare two handles.

◆ bio_handle_info()

const bio_tag_t * bio_handle_info ( bio_handle_t  handle)

Retrieve the associated tag of a handle.

This should only be used for debugging.

◆ bio_make_handle()

bio_handle_t bio_make_handle ( void *  obj,
const bio_tag_t tag 
)

Create a new handle.

A handle is associated with a tag. To retrieve the associated pointer, the same tag must be provided. This is a form of runtime type checking.

For compile time, it is recommended to wrap the returned bio_handle_t value in a struct:

typedef {
bio_handle_t handle;
} my_handle_type_t;
void function_that_uses_my_handle(my_handle_type_t handle);
Definition bio.h:126

In fact, this is how bio exposes all of its handles.

Parameters
objThe object associated with the handle
tagThe "type" tag for the handle
Returns
A handle
See also
bio_resolve_handle
bio_close_handle

◆ bio_resolve_handle()

void * bio_resolve_handle ( bio_handle_t  handle,
const bio_tag_t tag 
)

Resolve a handle, returning the associated pointer.

If the provided tag is not the same as what was previously passed to bio_make_handle, this will return NULL.

If bio_close_handle was called on the handle, this will return NULL.

A function accepting handle should always check for NULL and become noop on a NULL pointer. This is how all bio functions behave.

Example:

void
bio_signal_impl_t* signal = bio_resolve_handle(ref.handle, &BIO_SIGNAL_HANDLE);
if (signal != NULL) {
// Actual code to raise signal
}
}
void * bio_resolve_handle(bio_handle_t handle, const bio_tag_t *tag)
Resolve a handle, returning the associated pointer.
bool bio_raise_signal(bio_signal_t signal)
Raise a signal.
Definition internal.h:144
Handle to a signal.
Definition bio.h:147