#ifndef AWS_IO_EVENT_LOOP_H #define AWS_IO_EVENT_LOOP_H /** * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0. */ #include #include #include #include enum aws_io_event_type { AWS_IO_EVENT_TYPE_READABLE = 1, AWS_IO_EVENT_TYPE_WRITABLE = 2, AWS_IO_EVENT_TYPE_REMOTE_HANG_UP = 4, AWS_IO_EVENT_TYPE_CLOSED = 8, AWS_IO_EVENT_TYPE_ERROR = 16, }; struct aws_event_loop; struct aws_task; struct aws_thread_options; #if AWS_USE_IO_COMPLETION_PORTS struct aws_overlapped; typedef void(aws_event_loop_on_completion_fn)( struct aws_event_loop *event_loop, struct aws_overlapped *overlapped, int status_code, size_t num_bytes_transferred); /** * The aws_win32_OVERLAPPED struct is layout-compatible with OVERLAPPED as defined in . It is used * here to avoid pulling in a dependency on which would also bring along a lot of bad macros, such * as redefinitions of GetMessage and GetObject. Note that the OVERLAPPED struct layout in the Windows SDK can * never be altered without breaking binary compatibility for every existing third-party executable, so there * is no need to worry about keeping this definition in sync. */ struct aws_win32_OVERLAPPED { uintptr_t Internal; uintptr_t InternalHigh; union { struct { uint32_t Offset; uint32_t OffsetHigh; } s; void *Pointer; } u; void *hEvent; }; /** * Use aws_overlapped when a handle connected to the event loop needs an OVERLAPPED struct. * OVERLAPPED structs are needed to make OS-level async I/O calls. * When the I/O completes, the assigned aws_event_loop_on_completion_fn is called from the event_loop's thread. * While the I/O is pending, it is not safe to modify or delete aws_overlapped. * Call aws_overlapped_init() before first use. If the aws_overlapped will be used multiple times, call * aws_overlapped_reset() or aws_overlapped_init() between uses. */ struct aws_overlapped { struct aws_win32_OVERLAPPED overlapped; aws_event_loop_on_completion_fn *on_completion; void *user_data; }; #else /* !AWS_USE_IO_COMPLETION_PORTS */ typedef void(aws_event_loop_on_event_fn)( struct aws_event_loop *event_loop, struct aws_io_handle *handle, int events, void *user_data); #endif /* AWS_USE_IO_COMPLETION_PORTS */ struct aws_event_loop_vtable { void (*destroy)(struct aws_event_loop *event_loop); int (*run)(struct aws_event_loop *event_loop); int (*stop)(struct aws_event_loop *event_loop); int (*wait_for_stop_completion)(struct aws_event_loop *event_loop); void (*schedule_task_now)(struct aws_event_loop *event_loop, struct aws_task *task); void (*schedule_task_future)(struct aws_event_loop *event_loop, struct aws_task *task, uint64_t run_at_nanos); void (*cancel_task)(struct aws_event_loop *event_loop, struct aws_task *task); #if AWS_USE_IO_COMPLETION_PORTS int (*connect_to_io_completion_port)(struct aws_event_loop *event_loop, struct aws_io_handle *handle); #else int (*subscribe_to_io_events)( struct aws_event_loop *event_loop, struct aws_io_handle *handle, int events, aws_event_loop_on_event_fn *on_event, void *user_data); #endif int (*unsubscribe_from_io_events)(struct aws_event_loop *event_loop, struct aws_io_handle *handle); void (*free_io_event_resources)(void *user_data); bool (*is_on_callers_thread)(struct aws_event_loop *event_loop); }; struct aws_event_loop { struct aws_event_loop_vtable *vtable; struct aws_allocator *alloc; aws_io_clock_fn *clock; struct aws_hash_table local_data; struct aws_atomic_var current_load_factor; uint64_t latest_tick_start; size_t current_tick_latency_sum; struct aws_atomic_var next_flush_time; void *impl_data; }; struct aws_event_loop_local_object; typedef void(aws_event_loop_on_local_object_removed_fn)(struct aws_event_loop_local_object *); struct aws_event_loop_local_object { const void *key; void *object; aws_event_loop_on_local_object_removed_fn *on_object_removed; }; struct aws_event_loop_options { aws_io_clock_fn *clock; struct aws_thread_options *thread_options; }; typedef struct aws_event_loop *(aws_new_event_loop_fn)( struct aws_allocator *alloc, const struct aws_event_loop_options *options, void *new_loop_user_data); struct aws_event_loop_group { struct aws_allocator *allocator; struct aws_array_list event_loops; struct aws_ref_count ref_count; struct aws_shutdown_callback_options shutdown_options; }; AWS_EXTERN_C_BEGIN #ifdef AWS_USE_IO_COMPLETION_PORTS /** * Prepares aws_overlapped for use, and sets a function to call when the overlapped operation completes. */ AWS_IO_API void aws_overlapped_init( struct aws_overlapped *overlapped, aws_event_loop_on_completion_fn *on_completion, void *user_data); /** * Prepares aws_overlapped for re-use without changing the assigned aws_event_loop_on_completion_fn. * Call aws_overlapped_init(), instead of aws_overlapped_reset(), to change the aws_event_loop_on_completion_fn. */ AWS_IO_API void aws_overlapped_reset(struct aws_overlapped *overlapped); /** * Casts an aws_overlapped pointer for use as a LPOVERLAPPED parameter to Windows API functions */ AWS_IO_API struct _OVERLAPPED *aws_overlapped_to_windows_overlapped(struct aws_overlapped *overlapped); #endif /* AWS_USE_IO_COMPLETION_PORTS */ /** * Creates an instance of the default event loop implementation for the current architecture and operating system. */ AWS_IO_API struct aws_event_loop *aws_event_loop_new_default(struct aws_allocator *alloc, aws_io_clock_fn *clock); /** * Creates an instance of the default event loop implementation for the current architecture and operating system using * extendable options. */ AWS_IO_API struct aws_event_loop *aws_event_loop_new_default_with_options( struct aws_allocator *alloc, const struct aws_event_loop_options *options); /** * Invokes the destroy() fn for the event loop implementation. * If the event loop is still in a running state, this function will block waiting on the event loop to shutdown. * If you do not want this function to block, call aws_event_loop_stop() manually first. * If the event loop is shared by multiple threads then destroy must be called by exactly one thread. All other threads * must ensure their API calls to the event loop happen-before the call to destroy. */ AWS_IO_API void aws_event_loop_destroy(struct aws_event_loop *event_loop); /** * Initializes common event-loop data structures. * This is only called from the *new() function of event loop implementations. */ AWS_IO_API int aws_event_loop_init_base(struct aws_event_loop *event_loop, struct aws_allocator *alloc, aws_io_clock_fn *clock); /** * Common cleanup code for all implementations. * This is only called from the *destroy() function of event loop implementations. */ AWS_IO_API void aws_event_loop_clean_up_base(struct aws_event_loop *event_loop); /** * Fetches an object from the event-loop's data store. Key will be taken as the memory address of the memory pointed to * by key. This function is not thread safe and should be called inside the event-loop's thread. */ AWS_IO_API int aws_event_loop_fetch_local_object( struct aws_event_loop *event_loop, void *key, struct aws_event_loop_local_object *obj); /** * Puts an item object the event-loop's data store. Key will be taken as the memory address of the memory pointed to by * key. The lifetime of item must live until remove or a put item overrides it. This function is not thread safe and * should be called inside the event-loop's thread. */ AWS_IO_API int aws_event_loop_put_local_object(struct aws_event_loop *event_loop, struct aws_event_loop_local_object *obj); /** * Removes an object from the event-loop's data store. Key will be taken as the memory address of the memory pointed to * by key. If removed_item is not null, the removed item will be moved to it if it exists. Otherwise, the default * deallocation strategy will be used. This function is not thread safe and should be called inside the event-loop's * thread. */ AWS_IO_API int aws_event_loop_remove_local_object( struct aws_event_loop *event_loop, void *key, struct aws_event_loop_local_object *removed_obj); /** * Triggers the running of the event loop. This function must not block. The event loop is not active until this * function is invoked. This function can be called again on an event loop after calling aws_event_loop_stop() and * aws_event_loop_wait_for_stop_completion(). */ AWS_IO_API int aws_event_loop_run(struct aws_event_loop *event_loop); /** * Triggers the event loop to stop, but does not wait for the loop to stop completely. * This function may be called from outside or inside the event loop thread. It is safe to call multiple times. * This function is called from destroy(). * * If you do not call destroy(), an event loop can be run again by calling stop(), wait_for_stop_completion(), run(). */ AWS_IO_API int aws_event_loop_stop(struct aws_event_loop *event_loop); /** * For event-loop implementations to use for providing metrics info to the base event-loop. This enables the * event-loop load balancer to take into account load when vending another event-loop to a caller. * * Call this function at the beginning of your event-loop tick: after wake-up, but before processing any IO or tasks. */ AWS_IO_API void aws_event_loop_register_tick_start(struct aws_event_loop *event_loop); /** * For event-loop implementations to use for providing metrics info to the base event-loop. This enables the * event-loop load balancer to take into account load when vending another event-loop to a caller. * * Call this function at the end of your event-loop tick: after processing IO and tasks. */ AWS_IO_API void aws_event_loop_register_tick_end(struct aws_event_loop *event_loop); /** * Returns the current load factor (however that may be calculated). If the event-loop is not invoking * aws_event_loop_register_tick_start() and aws_event_loop_register_tick_end(), this value will always be 0. */ AWS_IO_API size_t aws_event_loop_get_load_factor(struct aws_event_loop *event_loop); /** * Blocks until the event loop stops completely. * If you want to call aws_event_loop_run() again, you must call this after aws_event_loop_stop(). * It is not safe to call this function from inside the event loop thread. */ AWS_IO_API int aws_event_loop_wait_for_stop_completion(struct aws_event_loop *event_loop); /** * The event loop will schedule the task and run it on the event loop thread as soon as possible. * Note that cancelled tasks may execute outside the event loop thread. * This function may be called from outside or inside the event loop thread. * * The task should not be cleaned up or modified until its function is executed. */ AWS_IO_API void aws_event_loop_schedule_task_now(struct aws_event_loop *event_loop, struct aws_task *task); /** * The event loop will schedule the task and run it at the specified time. * Use aws_event_loop_current_clock_time() to query the current time in nanoseconds. * Note that cancelled tasks may execute outside the event loop thread. * This function may be called from outside or inside the event loop thread. * * The task should not be cleaned up or modified until its function is executed. */ AWS_IO_API void aws_event_loop_schedule_task_future( struct aws_event_loop *event_loop, struct aws_task *task, uint64_t run_at_nanos); /** * Cancels task. * This function must be called from the event loop's thread, and is only guaranteed * to work properly on tasks scheduled from within the event loop's thread. * The task will be executed with the AWS_TASK_STATUS_CANCELED status inside this call. */ AWS_IO_API void aws_event_loop_cancel_task(struct aws_event_loop *event_loop, struct aws_task *task); #if AWS_USE_IO_COMPLETION_PORTS /** * Associates an aws_io_handle with the event loop's I/O Completion Port. * * The handle must use aws_overlapped for all async operations requiring an OVERLAPPED struct. * When the operation completes, the aws_overlapped's completion function will run on the event loop thread. * Note that completion functions will not be invoked while the event loop is stopped. Users should wait for all async * operations on connected handles to complete before cleaning up or destroying the event loop. * * A handle may only be connected to one event loop in its lifetime. */ AWS_IO_API int aws_event_loop_connect_handle_to_io_completion_port( struct aws_event_loop *event_loop, struct aws_io_handle *handle); #else /* !AWS_USE_IO_COMPLETION_PORTS */ /** * Subscribes on_event to events on the event-loop for handle. events is a bitwise concatenation of the events that were * received. The definition for these values can be found in aws_io_event_type. Currently, only * AWS_IO_EVENT_TYPE_READABLE and AWS_IO_EVENT_TYPE_WRITABLE are honored. You always are registered for error conditions * and closure. This function may be called from outside or inside the event loop thread. However, the unsubscribe * function must be called inside the event-loop's thread. */ AWS_IO_API int aws_event_loop_subscribe_to_io_events( struct aws_event_loop *event_loop, struct aws_io_handle *handle, int events, aws_event_loop_on_event_fn *on_event, void *user_data); #endif /* AWS_USE_IO_COMPLETION_PORTS */ /** * Unsubscribes handle from event-loop notifications. * This function is not thread safe and should be called inside the event-loop's thread. * * NOTE: if you are using io completion ports, this is a risky call. We use it in places, but only when we're certain * there's no pending events. If you want to use it, it's your job to make sure you don't have pending events before * calling it. */ AWS_IO_API int aws_event_loop_unsubscribe_from_io_events(struct aws_event_loop *event_loop, struct aws_io_handle *handle); /** * Cleans up resources (user_data) associated with the I/O eventing subsystem for a given handle. This should only * ever be necessary in the case where you are cleaning up an event loop during shutdown and its thread has already * been joined. */ AWS_IO_API void aws_event_loop_free_io_event_resources(struct aws_event_loop *event_loop, struct aws_io_handle *handle); /** * Returns true if the event loop's thread is the same thread that called this function, otherwise false. */ AWS_IO_API bool aws_event_loop_thread_is_callers_thread(struct aws_event_loop *event_loop); /** * Gets the current timestamp for the event loop's clock, in nanoseconds. This function is thread-safe. */ AWS_IO_API int aws_event_loop_current_clock_time(struct aws_event_loop *event_loop, uint64_t *time_nanos); /** * Creates an event loop group, with clock, number of loops to manage, and the function to call for creating a new * event loop. */ AWS_IO_API struct aws_event_loop_group *aws_event_loop_group_new( struct aws_allocator *alloc, aws_io_clock_fn *clock, uint16_t el_count, aws_new_event_loop_fn *new_loop_fn, void *new_loop_user_data, const struct aws_shutdown_callback_options *shutdown_options); /** Creates an event loop group, with clock, number of loops to manage, the function to call for creating a new * event loop, and also pins all loops to hw threads on the same cpu_group (e.g. NUMA nodes). Note: * If el_count exceeds the number of hw threads in the cpu_group it will be ignored on the assumption that if you * care about NUMA, you don't want hyper-threads doing your IO and you especially don't want IO on a different node. */ AWS_IO_API struct aws_event_loop_group *aws_event_loop_group_new_pinned_to_cpu_group( struct aws_allocator *alloc, aws_io_clock_fn *clock, uint16_t el_count, uint16_t cpu_group, aws_new_event_loop_fn *new_loop_fn, void *new_loop_user_data, const struct aws_shutdown_callback_options *shutdown_options); /** * Initializes an event loop group with platform defaults. If max_threads == 0, then the * loop count will be the number of available processors on the machine / 2 (to exclude hyper-threads). * Otherwise, max_threads will be the number of event loops in the group. */ AWS_IO_API struct aws_event_loop_group *aws_event_loop_group_new_default( struct aws_allocator *alloc, uint16_t max_threads, const struct aws_shutdown_callback_options *shutdown_options); /** Creates an event loop group, with clock, number of loops to manage, the function to call for creating a new * event loop, and also pins all loops to hw threads on the same cpu_group (e.g. NUMA nodes). Note: * If el_count exceeds the number of hw threads in the cpu_group it will be clamped to the number of hw threads * on the assumption that if you care about NUMA, you don't want hyper-threads doing your IO and you especially * don't want IO on a different node. * * If max_threads == 0, then the * loop count will be the number of available processors in the cpu_group / 2 (to exclude hyper-threads) */ AWS_IO_API struct aws_event_loop_group *aws_event_loop_group_new_default_pinned_to_cpu_group( struct aws_allocator *alloc, uint16_t max_threads, uint16_t cpu_group, const struct aws_shutdown_callback_options *shutdown_options); /** * Increments the reference count on the event loop group, allowing the caller to take a reference to it. * * Returns the same event loop group passed in. */ AWS_IO_API struct aws_event_loop_group *aws_event_loop_group_acquire(struct aws_event_loop_group *el_group); /** * Decrements an event loop group's ref count. When the ref count drops to zero, the event loop group will be * destroyed. */ AWS_IO_API void aws_event_loop_group_release(struct aws_event_loop_group *el_group); AWS_IO_API struct aws_event_loop *aws_event_loop_group_get_loop_at(struct aws_event_loop_group *el_group, size_t index); AWS_IO_API size_t aws_event_loop_group_get_loop_count(struct aws_event_loop_group *el_group); /** * Fetches the next loop for use. The purpose is to enable load balancing across loops. You should not depend on how * this load balancing is done as it is subject to change in the future. Currently it uses the "best-of-two" algorithm * based on the load factor of each loop. */ AWS_IO_API struct aws_event_loop *aws_event_loop_group_get_next_loop(struct aws_event_loop_group *el_group); AWS_EXTERN_C_END #endif /* AWS_IO_EVENT_LOOP_H */