feat: nonblocking request reads

master
Sean McBride 3 years ago
parent 03c489498f
commit d72f810567

@ -111,7 +111,6 @@
"wasm_memory.h": "c",
"sledge_abi.h": "c",
"vec.h": "c",
"module_database.h": "c",
"perf_window_t.h": "c",
"module_config.h": "c",
"tenant.h": "c",
@ -121,7 +120,8 @@
"tcp_server.h": "c",
"stdint.h": "c",
"scheduler_options.h": "c",
"route_config_parse.h": "c"
"route_config_parse.h": "c",
"route.h": "c"
},
"files.exclude": {
"**/.git": true,

@ -3,25 +3,13 @@
#include <stdlib.h>
#include <string.h>
#include "admissions_info.h"
#include "http.h"
#include "module_database.h"
#include "module.h"
#include "route.h"
#include "route_config.h"
#include "tcp_server.h"
#define HTTP_ROUTER_ROUTES_CAPACITY 32
/* Assumption: entrypoint is always _start. This should be enhanced later */
struct route {
char *route;
struct module *module;
/* HTTP State */
uint32_t relative_deadline_us;
uint64_t relative_deadline; /* cycles */
size_t response_size;
char *response_content_type;
struct admissions_info admissions_info;
};
struct http_router {
struct route routes[HTTP_ROUTER_ROUTES_CAPACITY];

@ -14,6 +14,7 @@
#include "http_request.h"
#include "http_parser.h"
#include "http_parser_settings.h"
#include "tenant.h"
#include "vec.h"
#define HTTP_SESSION_DEFAULT_REQUEST_RESPONSE_SIZE (PAGE_SIZE)
@ -28,6 +29,8 @@ struct http_session {
struct http_request http_request;
struct vec_u8 request_buffer;
struct vec_u8 response_buffer;
struct tenant *tenant; /* Backlink required when read blocks on listener core */
uint64_t request_arrival_timestamp;
};
/**
@ -35,13 +38,16 @@ struct http_session {
* @returns 0 on success, -1 on error
*/
static inline int
http_session_init(struct http_session *session, int socket_descriptor, const struct sockaddr *socket_address)
http_session_init(struct http_session *session, int socket_descriptor, const struct sockaddr *socket_address,
struct tenant *tenant, uint64_t request_arrival_timestamp)
{
assert(session != NULL);
assert(socket_descriptor >= 0);
assert(socket_address != NULL);
session->socket = socket_descriptor;
session->tenant = tenant;
session->socket = socket_descriptor;
session->request_arrival_timestamp = request_arrival_timestamp;
memcpy(&session->client_address, socket_address, sizeof(struct sockaddr));
http_parser_init(&session->http_parser, HTTP_REQUEST);
@ -76,7 +82,8 @@ http_session_init_response_buffer(struct http_session *session, size_t capacity)
}
static inline struct http_session *
http_session_alloc(int socket_descriptor, const struct sockaddr *socket_address)
http_session_alloc(int socket_descriptor, const struct sockaddr *socket_address, struct tenant *tenant,
uint64_t request_arrival_timestamp)
{
assert(socket_descriptor >= 0);
assert(socket_address != NULL);
@ -84,7 +91,7 @@ http_session_alloc(int socket_descriptor, const struct sockaddr *socket_address)
struct http_session *session = calloc(sizeof(struct http_session), 1);
if (session == NULL) return NULL;
int rc = http_session_init(session, socket_descriptor, socket_address);
int rc = http_session_init(session, socket_descriptor, socket_address, tenant, request_arrival_timestamp);
if (rc != 0) {
free(session);
return NULL;
@ -131,13 +138,32 @@ http_session_send_err(struct http_session *session, int status_code, void_cb on_
on_eagain);
}
static inline void
http_session_close(struct http_session *session)
{
assert(session != NULL);
return tcp_session_close(session->socket, &session->client_address);
}
/**
* Writes an HTTP error code to the TCP socket associated with the session
* Closes without writing if TCP socket would have blocked
* Also cleans up the HTTP session
*/
static inline int
http_session_send_err_oneshot(struct http_session *session, int status_code)
{
assert(session != NULL);
assert(status_code >= 100 && status_code <= 599);
return tcp_session_send_oneshot(session->socket, http_header_build(status_code), http_header_len(status_code));
int rc = tcp_session_send_oneshot(session->socket, http_header_build(status_code),
http_header_len(status_code));
http_session_close(session);
http_session_free(session);
return rc;
}
static inline int
@ -170,21 +196,15 @@ err:
goto done;
}
static inline void
http_session_close(struct http_session *session)
{
assert(session != NULL);
return tcp_session_close(session->socket, &session->client_address);
}
typedef void (*http_session_receive_request_egain_cb)(struct http_session *);
/**
* Receive and Parse the Request for the current sandbox
* @return 0 if message parsing complete, -1 on error, -2 if buffers run out of space
*/
static inline int
http_session_receive_request(struct http_session *session, void_cb on_eagain)
http_session_receive_request(struct http_session *session, http_session_receive_request_egain_cb on_eagain)
{
assert(session != NULL);
@ -247,12 +267,8 @@ http_session_receive_request(struct http_session *session, void_cb on_eagain)
if (bytes_received < 0) {
if (errno == EAGAIN) {
if (on_eagain == NULL) {
goto err_eagain;
} else {
on_eagain();
continue;
}
on_eagain(session);
goto err_eagain;
} else {
debuglog("Error reading socket %d - %s\n", session->socket, strerror(errno));
goto err;

@ -18,8 +18,6 @@
#include "wasm_memory.h"
#include "wasm_table.h"
#define MODULE_DATABASE_CAPACITY 128
extern thread_local int worker_thread_idx;
INIT_POOL(wasm_memory, wasm_memory_free)
@ -212,12 +210,3 @@ module_free_linear_memory(struct module *module, struct wasm_memory *memory)
wasm_memory_reinit(memory, module->abi.starting_pages * WASM_PAGE_SIZE);
wasm_memory_pool_add_nolock(&module->pools[worker_thread_idx].memory, memory);
}
struct module_database {
struct module *modules[MODULE_DATABASE_CAPACITY];
size_t count;
};
int module_database_add(struct module_database *db, struct module *module);
struct module *module_database_find_by_path(struct module_database *db, char *path);
void module_database_init(struct module_database *db);

@ -1,3 +1,13 @@
#pragma once
#include "module.h"
#define MODULE_DATABASE_CAPACITY 128
struct module_database {
struct module *modules[MODULE_DATABASE_CAPACITY];
size_t count;
};
struct module *module_database_find_by_path(struct module_database *db, char *path);
void module_database_init(struct module_database *db);

@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include "admissions_info.h"
#include "module.h"
/* Assumption: entrypoint is always _start. This should be enhanced later */
struct route {
char *route;
struct module *module;
/* HTTP State */
uint32_t relative_deadline_us;
uint64_t relative_deadline; /* cycles */
size_t response_size;
char *response_content_type;
struct admissions_info admissions_info;
};

@ -27,6 +27,7 @@
#define RUNTIME_LOG_FILE "sledge.log"
#define RUNTIME_MAX_EPOLL_EVENTS 128
#define RUNTIME_MAX_WORKER_COUNT 32 /* Static buffer size for per-worker globals */
#define RUNTIME_MAX_TENANT_COUNT 32 /* Static buffer size for per-worker globals */
#define RUNTIME_READ_WRITE_VECTOR_LENGTH 16
#define RUNTIME_RELATIVE_DEADLINE_US_MAX 3600000000 /* One Hour. Fits in uint32_t */

@ -13,7 +13,7 @@
**************************/
struct sandbox *sandbox_alloc(struct module *module, struct http_session *session, struct route *route,
struct tenant *tenant, uint64_t request_arrival_timestamp, uint64_t admissions_estimate);
struct tenant *tenant, uint64_t admissions_estimate);
int sandbox_prepare_execution_environment(struct sandbox *sandbox);
void sandbox_free(struct sandbox *sandbox);
void sandbox_main(struct sandbox *sandbox);

@ -1,17 +0,0 @@
#pragma once
#include "current_sandbox.h"
#include "sandbox_types.h"
/**
* Receive and Parse the Request for the current sandbox
* @return 0 if message parsing complete, -1 on error, -2 if buffers run out of space
*/
static inline int
sandbox_receive_request(struct sandbox *sandbox)
{
assert(sandbox != NULL);
struct http_session *session = sandbox->http;
return http_session_receive_request(session, current_sandbox_sleep);
}

@ -1,7 +1,7 @@
#pragma once
#include "http_router.h"
#include "module.h"
#include "module_database.h"
#include "tcp_server.h"
#define TENANT_DATABASE_CAPACITY 128

@ -6,6 +6,7 @@
#include "admissions_info.h"
#include "http.h"
#include "listener_thread.h"
#include "module_database.h"
#include "panic.h"
#include "scheduler_options.h"
#include "tenant.h"
@ -15,6 +16,7 @@ int tenant_database_add(struct tenant *tenant);
struct tenant *tenant_database_find_by_name(char *name);
struct tenant *tenant_database_find_by_socket_descriptor(int socket_descriptor);
struct tenant *tenant_database_find_by_port(uint16_t port);
struct tenant *tenant_database_find_by_ptr(void *ptr);
static inline struct tenant *
tenant_alloc(struct tenant_config *config)

@ -4,7 +4,6 @@
#include "current_sandbox.h"
#include "sandbox_functions.h"
#include "sandbox_receive_request.h"
#include "sandbox_set_as_asleep.h"
#include "sandbox_set_as_error.h"
#include "sandbox_set_as_returned.h"

@ -197,8 +197,7 @@ http_parser_settings_on_header_end(http_parser *parser)
return 0;
}
const size_t http_methods_len = 8;
const char *http_methods[http_methods_len] = { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT" };
const char *http_methods[] = { "OPTIONS", "GET", "HEAD", "POST", "PUT", "DELETE", "TRACE", "CONNECT" };
/**
* http-parser callback called for HTTP Bodies

@ -9,6 +9,8 @@
#include "runtime.h"
#include "sandbox_functions.h"
#include "tcp_session.h"
#include "tenant.h"
#include "tenant_functions.h"
/*
* Descriptor of the epoll instance used to monitor the socket descriptors of registered
@ -44,6 +46,41 @@ listener_thread_initialize(void)
printf("\tListener core thread: %lx\n", listener_thread_id);
}
/**
* @brief Registers a serverless tenant on the listener thread's epoll descriptor
**/
void
listener_thread_register_http_session(struct http_session *http)
{
assert(http != NULL);
if (unlikely(listener_thread_epoll_file_descriptor == 0)) {
panic("Attempting to register an http session before listener thread initialization");
}
int rc = 0;
struct epoll_event accept_evt;
accept_evt.data.ptr = (void *)http;
accept_evt.events = EPOLLIN;
epoll_ctl(listener_thread_epoll_file_descriptor, EPOLL_CTL_ADD, http->socket, &accept_evt);
}
/**
* @brief Registers a serverless tenant on the listener thread's epoll descriptor
**/
void
listener_thread_unregister_http_session(struct http_session *http)
{
assert(http != NULL);
if (unlikely(listener_thread_epoll_file_descriptor == 0)) {
panic("Attempting to unregister an http session before listener thread initialization");
}
epoll_ctl(listener_thread_epoll_file_descriptor, EPOLL_CTL_DEL, http->socket, NULL);
}
/**
* @brief Registers a serverless tenant on the listener thread's epoll descriptor
**/
@ -65,6 +102,162 @@ listener_thread_register_tenant(struct tenant *tenant)
return rc;
}
static void
panic_on_epoll_error(struct epoll_event *evt)
{
/* Check Event to determine if epoll returned an error */
if ((evt->events & EPOLLERR) == EPOLLERR) {
int error = 0;
socklen_t errlen = sizeof(error);
if (getsockopt(evt->data.fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen) == 0) {
panic("epoll_wait: %s\n", strerror(error));
}
panic("epoll_wait");
};
}
static void
handle_tcp_requests(struct epoll_event *evt)
{
assert((evt->events & EPOLLIN) == EPOLLIN);
/* Unpack tenant from epoll event */
struct tenant *tenant = (struct tenant *)evt->data.ptr;
assert(tenant);
/* Accept Client Request as a nonblocking socket, saving address information */
struct sockaddr_in client_address;
socklen_t address_length = sizeof(client_address);
/* Accept as many requests as possible, returning when we would have blocked */
while (true) {
int client_socket = accept4(tenant->tcp_server.socket_descriptor, (struct sockaddr *)&client_address,
&address_length, SOCK_NONBLOCK);
if (unlikely(client_socket < 0)) {
if (errno == EWOULDBLOCK || errno == EAGAIN) return;
panic("accept4: %s", strerror(errno));
}
uint64_t request_arrival_timestamp = __getcycles();
http_total_increment_request();
/* Allocate HTTP Session */
struct http_session *session = http_session_alloc(client_socket,
(const struct sockaddr *)&client_address, tenant,
request_arrival_timestamp);
/* Receive HTTP Request */
int rc = http_session_receive_request(session, listener_thread_register_http_session);
if (rc == -3) {
continue;
} else if (rc == -2) {
debuglog("Request size exceeded Buffer\n");
http_session_send_err_oneshot(session, 413);
continue;
} else if (rc == -1) {
http_session_send_err_oneshot(session, 400);
continue;
}
/* Route to sandbox */
struct route *route = http_router_match_route(&tenant->router, session->http_request.full_url);
if (route == NULL) {
http_session_send_err_oneshot(session, 404);
continue;
}
/*
* Perform admissions control.
* If 0, workload was rejected, so close with 429 "Too Many Requests" and continue
* TODO: Consider providing a Retry-After header
*/
uint64_t work_admitted = admissions_control_decide(route->admissions_info.estimate);
if (work_admitted == 0) {
http_session_send_err_oneshot(session, 429);
continue;
}
/* Allocate a Sandbox */
struct sandbox *sandbox = sandbox_alloc(route->module, session, route, tenant, work_admitted);
if (unlikely(sandbox == NULL)) {
http_session_send_err_oneshot(session, 503);
continue;
}
/* If the global request scheduler is full, return a 429 to the client
*/
sandbox = global_request_scheduler_add(sandbox);
if (unlikely(sandbox == NULL)) {
http_session_send_err_oneshot(session, 429);
sandbox_free(sandbox);
continue;
}
}
}
static void
resume_blocked_read(struct epoll_event *evt)
{
assert((evt->events & EPOLLIN) == EPOLLIN);
/* Unpack http session from epoll event */
struct http_session *session = (struct http_session *)evt->data.ptr;
assert(session);
/* Read HTTP request */
int rc = http_session_receive_request(session, listener_thread_register_http_session);
if (rc == -3) {
return;
} else if (rc == -2) {
debuglog("Request size exceeded Buffer\n");
/* Request size exceeded Buffer, send 413 Payload Too Large */
listener_thread_unregister_http_session(session);
http_session_send_err_oneshot(session, 413);
return;
} else if (rc == -1) {
listener_thread_unregister_http_session(session);
http_session_send_err_oneshot(session, 400);
return;
}
/* We read session to completion, so can remote from epoll */
listener_thread_unregister_http_session(session);
struct route *route = http_router_match_route(&session->tenant->router, session->http_request.full_url);
if (route == NULL) {
http_session_send_err_oneshot(session, 404);
return;
}
/*
* Perform admissions control.
* If 0, workload was rejected, so close with 429 "Too Many Requests" and continue
* TODO: Consider providing a Retry-After header
*/
uint64_t work_admitted = admissions_control_decide(route->admissions_info.estimate);
if (work_admitted == 0) {
http_session_send_err_oneshot(session, 429);
return;
}
/* Allocate a Sandbox */
struct sandbox *sandbox = sandbox_alloc(route->module, session, route, session->tenant, work_admitted);
if (unlikely(sandbox == NULL)) {
http_session_send_err_oneshot(session, 503);
return;
}
/* If the global request scheduler is full, return a 429 to the client
*/
sandbox = global_request_scheduler_add(sandbox);
if (unlikely(sandbox == NULL)) {
http_session_send_err_oneshot(session, 429);
sandbox_free(sandbox);
}
}
/**
* @brief Execution Loop of the listener core, io_handles HTTP requests, allocates sandbox request objects, and
* pushes the sandbox object to the global dequeue
@ -87,10 +280,7 @@ listener_thread_main(void *dummy)
pthread_setschedprio(pthread_self(), -20);
while (true) {
/*
* Block indefinitely on the epoll file descriptor, waiting on up to a max number of events
* TODO: Is RUNTIME_MAX_EPOLL_EVENTS actually limited to the max number of modules?
*/
/* Block indefinitely on the epoll file descriptor, waiting on up to a max number of events */
int descriptor_count = epoll_wait(listener_thread_epoll_file_descriptor, epoll_events,
RUNTIME_MAX_EPOLL_EVENTS, -1);
if (descriptor_count < 0) {
@ -98,138 +288,21 @@ listener_thread_main(void *dummy)
panic("epoll_wait: %s", strerror(errno));
}
/* Assumption: Because epoll_wait is set to not timeout, we should always have descriptors here
*/
/* Assumption: Because epoll_wait is set to not timeout, we should always have descriptors here */
assert(descriptor_count > 0);
uint64_t request_arrival_timestamp = __getcycles();
for (int i = 0; i < descriptor_count; i++) {
/* Check Event to determine if epoll returned an error */
if ((epoll_events[i].events & EPOLLERR) == EPOLLERR) {
int error = 0;
socklen_t errlen = sizeof(error);
if (getsockopt(epoll_events[i].data.fd, SOL_SOCKET, SO_ERROR, (void *)&error, &errlen)
== 0) {
panic("epoll_wait: %s\n", strerror(error));
}
panic("epoll_wait");
};
/* Assumption: We have only registered EPOLLIN events, so we should see no others here
*/
assert((epoll_events[i].events & EPOLLIN) == EPOLLIN);
/* Unpack tenant from epoll event */
struct tenant *tenant = (struct tenant *)epoll_events[i].data.ptr;
assert(tenant);
/*
* I don't think we're responsible to cleanup epoll events, but clearing to trigger
* the assertion just in case.
*/
epoll_events[i].data.ptr = NULL;
/* Accept Client Request as a nonblocking socket, saving address information */
struct sockaddr_in client_address;
socklen_t address_length = sizeof(client_address);
/*
* Accept as many requests as possible, terminating when we would have blocked
* This inner loop is used in case there are more datagrams than epoll events for some
* reason
*/
while (true) {
int client_socket = accept4(tenant->tcp_server.socket_descriptor,
(struct sockaddr *)&client_address, &address_length,
SOCK_NONBLOCK);
if (unlikely(client_socket < 0)) {
if (errno == EWOULDBLOCK || errno == EAGAIN) break;
panic("accept4: %s", strerror(errno));
}
/* We should never have accepted on fd 0, 1, or 2 */
assert(client_socket != STDIN_FILENO);
assert(client_socket != STDOUT_FILENO);
assert(client_socket != STDERR_FILENO);
/*
* According to accept(2), it is possible that the the sockaddr structure
* client_address may be too small, resulting in data being truncated to fit.
* The accept call mutates the size value to indicate that this is the case.
*/
if (address_length > sizeof(client_address)) {
debuglog("Client address %s truncated because buffer was too small\n",
tenant->name);
}
http_total_increment_request();
/* Allocate HTTP Session */
struct http_session *session =
http_session_alloc(client_socket, (const struct sockaddr *)&client_address);
/* Read HTTP request */
/* TODO: Use epoll on block instead of busy looping */
int rc = 0;
while ((rc = http_session_receive_request(session, NULL)) == -3)
;
if (rc == -2) {
debuglog("Request size exceeded Buffer\n");
/* Request size exceeded Buffer, send 413 Payload Too Large */
http_session_send_err_oneshot(session, 413);
http_session_close(session);
continue;
} else if (rc == -1) {
http_session_send_err_oneshot(session, 400);
http_session_close(session);
continue;
}
struct route *route = http_router_match_route(&tenant->router,
session->http_request.full_url);
if (route == NULL) {
http_session_send_err_oneshot(session, 404);
http_session_close(session);
continue;
}
/*
* Perform admissions control.
* If 0, workload was rejected, so close with 429 "Too Many Requests"
and continue
* TODO: Consider providing a Retry-After header
*/
uint64_t work_admitted = admissions_control_decide(route->admissions_info.estimate);
if (work_admitted == 0) {
tcp_session_send_oneshot(client_socket, http_header_build(429),
http_header_len(429));
http_session_close(session);
continue;
}
/* Allocate a Sandbox */
struct sandbox *sandbox = sandbox_alloc(route->module, session, route, tenant,
request_arrival_timestamp, work_admitted);
if (unlikely(sandbox == NULL)) {
http_session_send_err_oneshot(sandbox->http, 503);
http_session_close(sandbox->http);
}
/* If the global request scheduler is full, return a 429 to the client
*/
sandbox = global_request_scheduler_add(sandbox);
if (unlikely(sandbox == NULL)) {
http_session_send_err_oneshot(sandbox->http, 429);
http_session_close(sandbox->http);
}
} /* while true */
} /* for loop */
panic_on_epoll_error(&epoll_events[i]);
if (tenant_database_find_by_ptr(epoll_events[i].data.ptr) != NULL) {
handle_tcp_requests(&epoll_events[i]);
} else {
resume_blocked_read(&epoll_events[i]);
}
}
generic_thread_dump_lock_overhead();
} /* while true */
}
panic("Listener thread unexpectedly broke loop\n");
}

@ -18,7 +18,7 @@ module_database_init(struct module_database *db)
* @param module module to add
* @return 0 on success. -ENOSPC when full
*/
int
static int
module_database_add(struct module_database *db, struct module *module)
{
assert(db->count <= MODULE_DATABASE_CAPACITY);

@ -130,7 +130,6 @@ err_memory_allocation_failed:
err_globals_allocation_failed:
err_http_allocation_failed:
http_session_send_err_oneshot(sandbox->http, 503);
http_session_close(sandbox->http);
sandbox_set_as_error(sandbox, SANDBOX_ALLOCATED);
perror(error_message);
rc = -1;
@ -139,7 +138,7 @@ err_http_allocation_failed:
void
sandbox_init(struct sandbox *sandbox, struct module *module, struct http_session *session, struct route *route,
struct tenant *tenant, uint64_t request_arrival_timestamp, uint64_t admissions_estimate)
struct tenant *tenant, uint64_t admissions_estimate)
{
/* Sets the ID to the value before the increment */
sandbox->id = sandbox_total_postfix_increment();
@ -155,8 +154,8 @@ sandbox_init(struct sandbox *sandbox, struct module *module, struct http_session
sandbox->tenant = tenant;
sandbox->route = route;
sandbox->timestamp_of.request_arrival = request_arrival_timestamp;
sandbox->absolute_deadline = request_arrival_timestamp + sandbox->route->relative_deadline;
sandbox->timestamp_of.request_arrival = session->request_arrival_timestamp;
sandbox->absolute_deadline = session->request_arrival_timestamp + sandbox->route->relative_deadline;
/*
* Admissions Control State
@ -174,13 +173,12 @@ sandbox_init(struct sandbox *sandbox, struct module *module, struct http_session
* @param module the module we want to request
* @param socket_descriptor
* @param socket_address
* @param request_arrival_timestamp the timestamp of when we receives the request from the network (in cycles)
* @param admissions_estimate the timestamp of when we receives the request from the network (in cycles)
* @return the new sandbox request
*/
struct sandbox *
sandbox_alloc(struct module *module, struct http_session *session, struct route *route, struct tenant *tenant,
uint64_t request_arrival_timestamp, uint64_t admissions_estimate)
uint64_t admissions_estimate)
{
struct sandbox *sandbox = NULL;
size_t page_aligned_sandbox_size = round_up_to_page(sizeof(struct sandbox));
@ -189,7 +187,7 @@ sandbox_alloc(struct module *module, struct http_session *session, struct route
if (unlikely(sandbox == NULL)) return NULL;
sandbox_set_as_allocated(sandbox);
sandbox_init(sandbox, module, session, route, tenant, request_arrival_timestamp, admissions_estimate);
sandbox_init(sandbox, module, session, route, tenant, admissions_estimate);
return sandbox;
@ -209,6 +207,7 @@ sandbox_deinit(struct sandbox *sandbox)
assert(sandbox->memory == NULL);
if (likely(sandbox->stack != NULL)) sandbox_free_stack(sandbox);
if (likely(sandbox->http != NULL)) http_session_free(sandbox->http);
if (likely(sandbox->globals.buffer != NULL)) sandbox_free_globals(sandbox);
}

@ -79,3 +79,16 @@ tenant_database_find_by_port(uint16_t port)
}
return NULL;
}
/**
* Checks is an opaque pointer is a tenant by comparing against
*/
struct tenant *
tenant_database_find_by_ptr(void *ptr)
{
for (size_t i = 0; i < tenant_database_count; i++) {
assert(tenant_database[i]);
if (tenant_database[i] == ptr) return tenant_database[i];
}
return NULL;
}

Loading…
Cancel
Save