Skip to content

Commit b6bff6f

Browse files
bretambroseBret Ambrose
and
Bret Ambrose
authoredNov 12, 2024··
Event loop public api (#691)
Co-authored-by: Bret Ambrose <[email protected]>
1 parent b28743c commit b6bff6f

27 files changed

+655
-476
lines changed
 

‎include/aws/io/event_loop.h

+85-326
Large diffs are not rendered by default.
+299
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
#ifndef AWS_IO_EVENT_LOOP_IMPL_H
2+
#define AWS_IO_EVENT_LOOP_IMPL_H
3+
4+
/**
5+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
6+
* SPDX-License-Identifier: Apache-2.0.
7+
*/
8+
9+
#include <aws/io/io.h>
10+
11+
#include <aws/common/atomics.h>
12+
#include <aws/common/hash_table.h>
13+
#include <aws/common/ref_count.h>
14+
#include <aws/io/event_loop.h>
15+
16+
AWS_PUSH_SANE_WARNING_LEVEL
17+
18+
struct aws_event_loop;
19+
struct aws_overlapped;
20+
21+
typedef void(aws_event_loop_on_completion_fn)(
22+
struct aws_event_loop *event_loop,
23+
struct aws_overlapped *overlapped,
24+
int status_code,
25+
size_t num_bytes_transferred);
26+
27+
/**
28+
* The aws_win32_OVERLAPPED struct is layout-compatible with OVERLAPPED as defined in <Windows.h>. It is used
29+
* here to avoid pulling in a dependency on <Windows.h> which would also bring along a lot of bad macros, such
30+
* as redefinitions of GetMessage and GetObject. Note that the OVERLAPPED struct layout in the Windows SDK can
31+
* never be altered without breaking binary compatibility for every existing third-party executable, so there
32+
* is no need to worry about keeping this definition in sync.
33+
*/
34+
struct aws_win32_OVERLAPPED {
35+
uintptr_t Internal;
36+
uintptr_t InternalHigh;
37+
union {
38+
struct {
39+
uint32_t Offset;
40+
uint32_t OffsetHigh;
41+
} s;
42+
void *Pointer;
43+
} u;
44+
void *hEvent;
45+
};
46+
47+
/**
48+
* Use aws_overlapped when a handle connected to the event loop needs an OVERLAPPED struct.
49+
* OVERLAPPED structs are needed to make OS-level async I/O calls.
50+
* When the I/O completes, the assigned aws_event_loop_on_completion_fn is called from the event_loop's thread.
51+
* While the I/O is pending, it is not safe to modify or delete aws_overlapped.
52+
* Call aws_overlapped_init() before first use. If the aws_overlapped will be used multiple times, call
53+
* aws_overlapped_reset() or aws_overlapped_init() between uses.
54+
*/
55+
struct aws_overlapped {
56+
struct aws_win32_OVERLAPPED overlapped;
57+
aws_event_loop_on_completion_fn *on_completion;
58+
void *user_data;
59+
};
60+
61+
enum aws_io_event_type {
62+
AWS_IO_EVENT_TYPE_READABLE = 1,
63+
AWS_IO_EVENT_TYPE_WRITABLE = 2,
64+
AWS_IO_EVENT_TYPE_REMOTE_HANG_UP = 4,
65+
AWS_IO_EVENT_TYPE_CLOSED = 8,
66+
AWS_IO_EVENT_TYPE_ERROR = 16,
67+
};
68+
69+
struct aws_event_loop {
70+
struct aws_event_loop_vtable *vtable;
71+
struct aws_allocator *alloc;
72+
aws_io_clock_fn *clock;
73+
struct aws_hash_table local_data;
74+
struct aws_atomic_var current_load_factor;
75+
uint64_t latest_tick_start;
76+
size_t current_tick_latency_sum;
77+
struct aws_atomic_var next_flush_time;
78+
void *impl_data;
79+
};
80+
81+
struct aws_event_loop_local_object;
82+
typedef void(aws_event_loop_on_local_object_removed_fn)(struct aws_event_loop_local_object *);
83+
84+
struct aws_event_loop_local_object {
85+
const void *key;
86+
void *object;
87+
aws_event_loop_on_local_object_removed_fn *on_object_removed;
88+
};
89+
90+
struct aws_event_loop_options {
91+
aws_io_clock_fn *clock;
92+
struct aws_thread_options *thread_options;
93+
};
94+
95+
typedef struct aws_event_loop *(aws_new_event_loop_fn)(struct aws_allocator *alloc,
96+
const struct aws_event_loop_options *options,
97+
void *new_loop_user_data);
98+
99+
struct aws_event_loop_group {
100+
struct aws_allocator *allocator;
101+
struct aws_array_list event_loops;
102+
struct aws_ref_count ref_count;
103+
struct aws_shutdown_callback_options shutdown_options;
104+
};
105+
106+
AWS_EXTERN_C_BEGIN
107+
108+
#ifdef AWS_USE_IO_COMPLETION_PORTS
109+
110+
/**
111+
* Prepares aws_overlapped for use, and sets a function to call when the overlapped operation completes.
112+
*/
113+
AWS_IO_API
114+
void aws_overlapped_init(
115+
struct aws_overlapped *overlapped,
116+
aws_event_loop_on_completion_fn *on_completion,
117+
void *user_data);
118+
119+
/**
120+
* Prepares aws_overlapped for re-use without changing the assigned aws_event_loop_on_completion_fn.
121+
* Call aws_overlapped_init(), instead of aws_overlapped_reset(), to change the aws_event_loop_on_completion_fn.
122+
*/
123+
AWS_IO_API
124+
void aws_overlapped_reset(struct aws_overlapped *overlapped);
125+
126+
/**
127+
* Casts an aws_overlapped pointer for use as a LPOVERLAPPED parameter to Windows API functions
128+
*/
129+
AWS_IO_API
130+
struct _OVERLAPPED *aws_overlapped_to_windows_overlapped(struct aws_overlapped *overlapped);
131+
132+
/**
133+
* Associates an aws_io_handle with the event loop's I/O Completion Port.
134+
*
135+
* The handle must use aws_overlapped for all async operations requiring an OVERLAPPED struct.
136+
* When the operation completes, the aws_overlapped's completion function will run on the event loop thread.
137+
* Note that completion functions will not be invoked while the event loop is stopped. Users should wait for all async
138+
* operations on connected handles to complete before cleaning up or destroying the event loop.
139+
*
140+
* A handle may only be connected to one event loop in its lifetime.
141+
*/
142+
AWS_IO_API
143+
int aws_event_loop_connect_handle_to_io_completion_port(
144+
struct aws_event_loop *event_loop,
145+
struct aws_io_handle *handle);
146+
147+
#else
148+
149+
/**
150+
* Subscribes on_event to events on the event-loop for handle. events is a bitwise concatenation of the events that were
151+
* received. The definition for these values can be found in aws_io_event_type. Currently, only
152+
* AWS_IO_EVENT_TYPE_READABLE and AWS_IO_EVENT_TYPE_WRITABLE are honored. You always are registered for error conditions
153+
* and closure. This function may be called from outside or inside the event loop thread. However, the unsubscribe
154+
* function must be called inside the event-loop's thread.
155+
*/
156+
AWS_IO_API
157+
int aws_event_loop_subscribe_to_io_events(
158+
struct aws_event_loop *event_loop,
159+
struct aws_io_handle *handle,
160+
int events,
161+
aws_event_loop_on_event_fn *on_event,
162+
void *user_data);
163+
164+
#endif /* AWS_USE_IO_COMPLETION_PORTS */
165+
166+
/**
167+
* Creates an instance of the default event loop implementation for the current architecture and operating system.
168+
*/
169+
AWS_IO_API
170+
struct aws_event_loop *aws_event_loop_new_default(struct aws_allocator *alloc, aws_io_clock_fn *clock);
171+
172+
/**
173+
* Creates an instance of the default event loop implementation for the current architecture and operating system using
174+
* extendable options.
175+
*/
176+
AWS_IO_API
177+
struct aws_event_loop *aws_event_loop_new_default_with_options(
178+
struct aws_allocator *alloc,
179+
const struct aws_event_loop_options *options);
180+
181+
/**
182+
* Initializes common event-loop data structures.
183+
* This is only called from the *new() function of event loop implementations.
184+
*/
185+
AWS_IO_API
186+
int aws_event_loop_init_base(struct aws_event_loop *event_loop, struct aws_allocator *alloc, aws_io_clock_fn *clock);
187+
188+
/**
189+
* Fetches an object from the event-loop's data store. Key will be taken as the memory address of the memory pointed to
190+
* by key. This function is not thread safe and should be called inside the event-loop's thread.
191+
*/
192+
AWS_IO_API
193+
int aws_event_loop_fetch_local_object(
194+
struct aws_event_loop *event_loop,
195+
void *key,
196+
struct aws_event_loop_local_object *obj);
197+
198+
/**
199+
* Puts an item object the event-loop's data store. Key will be taken as the memory address of the memory pointed to by
200+
* key. The lifetime of item must live until remove or a put item overrides it. This function is not thread safe and
201+
* should be called inside the event-loop's thread.
202+
*/
203+
AWS_IO_API
204+
int aws_event_loop_put_local_object(struct aws_event_loop *event_loop, struct aws_event_loop_local_object *obj);
205+
206+
/**
207+
* Removes an object from the event-loop's data store. Key will be taken as the memory address of the memory pointed to
208+
* by key. If removed_item is not null, the removed item will be moved to it if it exists. Otherwise, the default
209+
* deallocation strategy will be used. This function is not thread safe and should be called inside the event-loop's
210+
* thread.
211+
*/
212+
AWS_IO_API
213+
int aws_event_loop_remove_local_object(
214+
struct aws_event_loop *event_loop,
215+
void *key,
216+
struct aws_event_loop_local_object *removed_obj);
217+
218+
/**
219+
* Triggers the running of the event loop. This function must not block. The event loop is not active until this
220+
* function is invoked. This function can be called again on an event loop after calling aws_event_loop_stop() and
221+
* aws_event_loop_wait_for_stop_completion().
222+
*/
223+
AWS_IO_API
224+
int aws_event_loop_run(struct aws_event_loop *event_loop);
225+
226+
/**
227+
* Triggers the event loop to stop, but does not wait for the loop to stop completely.
228+
* This function may be called from outside or inside the event loop thread. It is safe to call multiple times.
229+
* This function is called from destroy().
230+
*
231+
* If you do not call destroy(), an event loop can be run again by calling stop(), wait_for_stop_completion(), run().
232+
*/
233+
AWS_IO_API
234+
int aws_event_loop_stop(struct aws_event_loop *event_loop);
235+
236+
/**
237+
* For event-loop implementations to use for providing metrics info to the base event-loop. This enables the
238+
* event-loop load balancer to take into account load when vending another event-loop to a caller.
239+
*
240+
* Call this function at the beginning of your event-loop tick: after wake-up, but before processing any IO or tasks.
241+
*/
242+
AWS_IO_API
243+
void aws_event_loop_register_tick_start(struct aws_event_loop *event_loop);
244+
245+
/**
246+
* For event-loop implementations to use for providing metrics info to the base event-loop. This enables the
247+
* event-loop load balancer to take into account load when vending another event-loop to a caller.
248+
*
249+
* Call this function at the end of your event-loop tick: after processing IO and tasks.
250+
*/
251+
AWS_IO_API
252+
void aws_event_loop_register_tick_end(struct aws_event_loop *event_loop);
253+
254+
/**
255+
* Returns the current load factor (however that may be calculated). If the event-loop is not invoking
256+
* aws_event_loop_register_tick_start() and aws_event_loop_register_tick_end(), this value will always be 0.
257+
*/
258+
AWS_IO_API
259+
size_t aws_event_loop_get_load_factor(struct aws_event_loop *event_loop);
260+
261+
/**
262+
* Blocks until the event loop stops completely.
263+
* If you want to call aws_event_loop_run() again, you must call this after aws_event_loop_stop().
264+
* It is not safe to call this function from inside the event loop thread.
265+
*/
266+
AWS_IO_API
267+
int aws_event_loop_wait_for_stop_completion(struct aws_event_loop *event_loop);
268+
269+
/**
270+
* Unsubscribes handle from event-loop notifications.
271+
* This function is not thread safe and should be called inside the event-loop's thread.
272+
*
273+
* NOTE: if you are using io completion ports, this is a risky call. We use it in places, but only when we're certain
274+
* 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
275+
* calling it.
276+
*/
277+
AWS_IO_API
278+
int aws_event_loop_unsubscribe_from_io_events(struct aws_event_loop *event_loop, struct aws_io_handle *handle);
279+
280+
/**
281+
* Cleans up resources (user_data) associated with the I/O eventing subsystem for a given handle. This should only
282+
* ever be necessary in the case where you are cleaning up an event loop during shutdown and its thread has already
283+
* been joined.
284+
*/
285+
AWS_IO_API
286+
void aws_event_loop_free_io_event_resources(struct aws_event_loop *event_loop, struct aws_io_handle *handle);
287+
288+
AWS_IO_API
289+
struct aws_event_loop_group *aws_event_loop_group_new_internal(
290+
struct aws_allocator *allocator,
291+
const struct aws_event_loop_group_options *options,
292+
aws_new_event_loop_fn *new_loop_fn,
293+
void *new_loop_user_data);
294+
295+
AWS_EXTERN_C_END
296+
297+
AWS_POP_SANE_WARNING_LEVEL
298+
299+
#endif /* AWS_IO_EVENT_LOOP_IMPL_H */

‎include/aws/testing/io_testing_channel.h

+14-15
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
#include <aws/io/channel.h>
1010
#include <aws/io/event_loop.h>
1111
#include <aws/io/logging.h>
12+
// #include <aws/io/private/event_loop_impl.h>
1213
#include <aws/io/statistics.h>
1314
#include <aws/testing/aws_test_harness.h>
1415

1516
struct testing_loop {
17+
struct aws_allocator *allocator;
1618
struct aws_task_scheduler scheduler;
1719
bool mock_on_callers_thread;
1820
};
@@ -33,7 +35,7 @@ static int s_testing_loop_wait_for_stop_completion(struct aws_event_loop *event_
3335
}
3436

3537
static void s_testing_loop_schedule_task_now(struct aws_event_loop *event_loop, struct aws_task *task) {
36-
struct testing_loop *testing_loop = (struct testing_loop *)event_loop->impl_data;
38+
struct testing_loop *testing_loop = (struct testing_loop *)aws_event_loop_get_impl(event_loop);
3739
aws_task_scheduler_schedule_now(&testing_loop->scheduler, task);
3840
}
3941

@@ -42,26 +44,27 @@ static void s_testing_loop_schedule_task_future(
4244
struct aws_task *task,
4345
uint64_t run_at_nanos) {
4446

45-
struct testing_loop *testing_loop = (struct testing_loop *)event_loop->impl_data;
47+
struct testing_loop *testing_loop = (struct testing_loop *)aws_event_loop_get_impl(event_loop);
4648
aws_task_scheduler_schedule_future(&testing_loop->scheduler, task, run_at_nanos);
4749
}
4850

4951
static void s_testing_loop_cancel_task(struct aws_event_loop *event_loop, struct aws_task *task) {
50-
struct testing_loop *testing_loop = (struct testing_loop *)event_loop->impl_data;
52+
struct testing_loop *testing_loop = (struct testing_loop *)aws_event_loop_get_impl(event_loop);
5153
aws_task_scheduler_cancel_task(&testing_loop->scheduler, task);
5254
}
5355

5456
static bool s_testing_loop_is_on_callers_thread(struct aws_event_loop *event_loop) {
55-
struct testing_loop *testing_loop = (struct testing_loop *)event_loop->impl_data;
57+
struct testing_loop *testing_loop = (struct testing_loop *)aws_event_loop_get_impl(event_loop);
5658
return testing_loop->mock_on_callers_thread;
5759
}
5860

5961
static void s_testing_loop_destroy(struct aws_event_loop *event_loop) {
60-
struct testing_loop *testing_loop = (struct testing_loop *)event_loop->impl_data;
62+
struct testing_loop *testing_loop = (struct testing_loop *)aws_event_loop_get_impl(event_loop);
63+
struct aws_allocator *allocator = testing_loop->allocator;
6164
aws_task_scheduler_clean_up(&testing_loop->scheduler);
62-
aws_mem_release(event_loop->alloc, testing_loop);
65+
aws_mem_release(allocator, testing_loop);
6366
aws_event_loop_clean_up_base(event_loop);
64-
aws_mem_release(event_loop->alloc, event_loop);
67+
aws_mem_release(allocator, event_loop);
6568
}
6669

6770
static struct aws_event_loop_vtable s_testing_loop_vtable = {
@@ -76,18 +79,14 @@ static struct aws_event_loop_vtable s_testing_loop_vtable = {
7679
};
7780

7881
static struct aws_event_loop *s_testing_loop_new(struct aws_allocator *allocator, aws_io_clock_fn clock) {
79-
struct aws_event_loop *event_loop =
80-
(struct aws_event_loop *)aws_mem_acquire(allocator, sizeof(struct aws_event_loop));
81-
aws_event_loop_init_base(event_loop, allocator, clock);
82-
8382
struct testing_loop *testing_loop =
8483
(struct testing_loop *)aws_mem_calloc(allocator, 1, sizeof(struct testing_loop));
84+
8585
aws_task_scheduler_init(&testing_loop->scheduler, allocator);
8686
testing_loop->mock_on_callers_thread = true;
87-
event_loop->impl_data = testing_loop;
88-
event_loop->vtable = &s_testing_loop_vtable;
87+
testing_loop->allocator = allocator;
8988

90-
return event_loop;
89+
return aws_event_loop_new_base(allocator, clock, &s_testing_loop_vtable, testing_loop);
9190
}
9291

9392
typedef void(testing_channel_handler_on_shutdown_fn)(
@@ -396,7 +395,7 @@ static inline int testing_channel_init(
396395
AWS_ZERO_STRUCT(*testing);
397396

398397
testing->loop = s_testing_loop_new(allocator, options->clock_fn);
399-
testing->loop_impl = (struct testing_loop *)testing->loop->impl_data;
398+
testing->loop_impl = (struct testing_loop *)aws_event_loop_get_impl(testing->loop);
400399

401400
struct aws_channel_options args = {
402401
.on_setup_completed = s_testing_channel_on_setup_completed,

0 commit comments

Comments
 (0)
Please sign in to comment.