Skip to content

Commit b577792

Browse files
committed
Factorize EventListeners
Both generic_unix and esp32 platforms featured event listeners. Factorize code adding a new header that is included by both, listeners.h. stm32 platform currently does not implement any listener. Also fix a potential concurrency bug in esp32 drivers that removed listeners without locking the list. Signed-off-by: Paul Guyot <[email protected]>
1 parent 26de3b8 commit b577792

File tree

13 files changed

+413
-210
lines changed

13 files changed

+413
-210
lines changed

src/libAtomVM/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ set(HEADER_FILES
3939
interop.h
4040
list.h
4141
linkedlist.h
42+
listeners.h
4243
mailbox.h
4344
memory.h
4445
module.h

src/libAtomVM/globalcontext.c

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ GlobalContext *globalcontext_new()
7575
synclist_init(&glb->refc_binaries);
7676
synclist_init(&glb->processes_table);
7777
synclist_init(&glb->registered_processes);
78+
synclist_init(&glb->listeners);
7879

7980
glb->last_process_id = 0;
8081

@@ -175,8 +176,13 @@ COLD_FUNC void globalcontext_destroy(GlobalContext *glb)
175176
refc_binary_destroy(refc, glb);
176177
}
177178
synclist_destroy(&glb->refc_binaries);
178-
179179
synclist_destroy(&glb->avmpack_data);
180+
struct ListHead *listeners = synclist_nolock(&glb->listeners);
181+
MUTABLE_LIST_FOR_EACH (item, tmp, listeners) {
182+
sys_listener_destroy(item);
183+
}
184+
synclist_destroy(&glb->listeners);
185+
180186
free(glb);
181187
}
182188

src/libAtomVM/globalcontext.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ struct GlobalContext
7575
struct SyncList refc_binaries;
7676
struct SyncList processes_table;
7777
struct SyncList registered_processes;
78+
struct SyncList listeners;
7879

7980
int32_t last_process_id;
8081

src/libAtomVM/listeners.h

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/*
2+
* This file is part of AtomVM.
3+
*
4+
* Copyright 2023 Paul Guyot <[email protected]>
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*
18+
* SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
*/
20+
21+
/**
22+
* @file listeners.h
23+
* @brief Common code for port listeners.
24+
*
25+
* @details This header defines convenient common functions to implement
26+
* listeners, and should be included in platform's `sys.c`.
27+
*
28+
* Before including this file, define listener_event_t which represent a
29+
* selectable event, as well as EventListener, which should have a list head
30+
* member called `listeners_list_head` and a handler member called `handler`.
31+
*
32+
* On a platform using select(3) with file descriptors, this typically is done
33+
* by creating a `platform_sys.h` header with:
34+
* ```
35+
* #include "sys.h"
36+
*
37+
* typedef int listener_event_t;
38+
*
39+
* struct EventListener
40+
* {
41+
* struct ListHead listeners_list_head;
42+
* event_handler_t handler;
43+
* listener_event_t fd;
44+
* };
45+
* ```
46+
*
47+
* and by including `platform_sys.h` header in `sys.c` before `listeners.h`.
48+
*/
49+
50+
#include <stdbool.h>
51+
52+
/**
53+
* @brief Add an event listener to the set of polled events.
54+
*
55+
* @details This function must be implemented and will typically access the
56+
* platform data from `glb` and add the event to the set. It is called by
57+
* `process_listener_handler` when a handler returns a new listener. It can be
58+
* called by `sys_register_listener`. It may just set a dirty flag.
59+
*
60+
* @param listener the listener to add to polling set
61+
* @param glb the global context
62+
*/
63+
static void event_listener_add_to_polling_set(struct EventListener *listener, GlobalContext *glb);
64+
65+
/**
66+
* @brief Remove an event from the set of polled events.
67+
*
68+
* @details This function must be implemented and will typically access the
69+
* platform data from `glb` and remove the event to the set. It is called by
70+
* `process_listener_handler` when a handler returns NULL or a new listener. It
71+
* can be called by `sys_unregister_listener`. It may just set a dirty flag.
72+
*
73+
* Compared to `event_listener_add_to_polling_set`, the event listener may no
74+
* longer exist if it was freed by the handler.
75+
*
76+
* @param event the listener event to remove from polling set
77+
* @param glb the global context
78+
*/
79+
static void listener_event_remove_from_polling_set(listener_event_t event, GlobalContext *glb);
80+
81+
/**
82+
* @brief Determiner if an event is a listener's event.
83+
*
84+
* @param listener the listener to test
85+
* @param event the event to test
86+
* @return true if event is the listener's event
87+
*/
88+
static bool event_listener_is_event(EventListener *listener, listener_event_t event);
89+
90+
/**
91+
* @brief Process listener handlers, optionally in advancing order, especially useful with poll(2) which returns fd in the provided order.
92+
*
93+
* @param glb the global context
94+
* @param current_event the selected event
95+
* @param listeners the list of listeners (locked for writing)
96+
* @param item_ptr the current cursor or NULL to search in items
97+
* @param previous_ptr the previous cursor (ignored and can be NULL if item_ptr is NULL).
98+
* @return true if the current_event was found
99+
*/
100+
static inline bool process_listener_handler(GlobalContext *glb, listener_event_t current_event, struct ListHead *listeners, struct ListHead **item_ptr, struct ListHead **previous_ptr)
101+
{
102+
bool result = false;
103+
struct ListHead *item;
104+
struct ListHead *previous;
105+
if (item_ptr) {
106+
item = *item_ptr;
107+
previous = *previous_ptr;
108+
} else {
109+
item = listeners->next;
110+
previous = listeners;
111+
}
112+
113+
while (item != listeners) {
114+
struct ListHead *next = item->next;
115+
EventListener *listener = GET_LIST_ENTRY(item, EventListener, listeners_list_head);
116+
if (event_listener_is_event(listener, current_event)) {
117+
EventListener *new_listener = listener->handler(glb, listener);
118+
if (new_listener == NULL) {
119+
listener_event_remove_from_polling_set(current_event, glb);
120+
previous->next = next;
121+
next->prev = previous;
122+
item = next;
123+
} else if (new_listener != listener) {
124+
listener_event_remove_from_polling_set(current_event, glb);
125+
event_listener_add_to_polling_set(new_listener, glb);
126+
// Replace listener with new_listener in the list
127+
// listener was freed by handler.
128+
previous->next = &new_listener->listeners_list_head;
129+
next->prev = &new_listener->listeners_list_head;
130+
new_listener->listeners_list_head.prev = previous;
131+
new_listener->listeners_list_head.next = next;
132+
item = &new_listener->listeners_list_head;
133+
}
134+
result = true;
135+
break;
136+
}
137+
previous = item;
138+
item = next;
139+
}
140+
if (item_ptr) {
141+
*previous_ptr = previous;
142+
*item_ptr = item;
143+
}
144+
return result;
145+
}
146+
147+
void sys_listener_destroy(struct ListHead *item)
148+
{
149+
EventListener *listener = GET_LIST_ENTRY(item, EventListener, listeners_list_head);
150+
free(listener);
151+
}

src/libAtomVM/sys.h

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
* @file sys.h
2323
* @brief Platform specific functions.
2424
*
25-
* @details This header defines platform dependent functions, that mostly deals with events.
25+
* @details This header defines platform dependent functions, that mostly deals
26+
* with events. Some functions can be implemented by using functions defined
27+
* and implemented in `listeners.h` header.
2628
*/
2729

2830
#ifndef _SYS_H_
@@ -46,18 +48,97 @@ enum
4648
};
4749

4850
/**
49-
* @brief Poll events (from drivers), with a timeout (in ms), or until
50-
* `sys_signal` is called.
51+
* @brief Event listener
52+
*
53+
* An event listener structure should be defined by the platform. Event
54+
* listeners belong to the `GlobalContext.listeners` synchronized list.
55+
*/
56+
typedef struct EventListener EventListener;
57+
58+
/**
59+
* @brief Event handlers (for ports)
60+
*
61+
* @details The event handler is called from the scheduler thread but outside
62+
* any process. It can send messages to processes using `globalcontext_send_message`
63+
* function.
64+
*
65+
* Result of this callback alters the list of handlers which is locked for
66+
* writing when it is called. It can:
67+
* - return `listener`, in which case the list is not modified
68+
* - return `NULL`, in which case the entry is removed. The callback is
69+
* responsible for freeing the listener.
70+
* - return another listener, in which case the current listener is replaced
71+
* by the other listener. The callback is responsible for freeing the previous
72+
* listener if it is no longer needed.
73+
*
74+
* Appending a listener is also possible by altering the list head.
75+
*
76+
* This callback is defined for platforms using `listeners.h` header and can be
77+
* ignored by others.
78+
*
79+
* @param glb global context
80+
* @param listener the current listener
81+
* @return NULL if the current listener should be removed, listener if it
82+
* should be kept or another listener if it should be replaced.
83+
*/
84+
typedef EventListener *(*event_handler_t)(GlobalContext *glb, EventListener *listener);
85+
86+
/**
87+
* @brief Poll events (from drivers and select events), with a timeout (in ms),
88+
* or until `sys_signal` is called.
5189
*
5290
* @details Depending on platforms, check all open file descriptors/queues and
5391
* call drivers that should send messages to contexts (which will unblock them).
5492
* With SMP builds, this function can be called from any scheduler.
5593
*
94+
* If selectable events are supported on the platform, this function should also:
95+
* - call `select_event_destroy` on select events that have close set to 1
96+
* - include the set of ErlNifEvent that are marked for read or write in the
97+
* select set, and if they are selected, call `select_event_notify` to send
98+
* the notification.
99+
*
100+
* `select_event_count_and_destroy_closed` defined in resources.h can be used
101+
* to process closed select events.
102+
*
56103
* @param glb the global context.
57104
* @param timeout_ms the number of ms to wait, `SYS_POLL_EVENTS_WAIT_FOREVER` to wait forever.
58105
*/
59106
void sys_poll_events(GlobalContext *glb, int timeout_ms);
60107

108+
/**
109+
* @brief Register a listener.
110+
*
111+
* @details This function is called by ports to register a listener which is a
112+
* native alternative to select events. The actual definition of listeners
113+
* is platform dependent.
114+
*
115+
* @param global the global context.
116+
* @param listener the listener to register
117+
*/
118+
void sys_register_listener(GlobalContext *global, EventListener *listener);
119+
120+
/**
121+
* @brief Unregister a listener.
122+
*
123+
* @details This function is called by ports to unregister a listener which is
124+
* a native alternative to select events. The actual definition of listeners
125+
* is platform dependent.
126+
*
127+
* @param global the global context.
128+
* @param listener the listener to unregister.
129+
*/
130+
void sys_unregister_listener(GlobalContext *global, EventListener *listener);
131+
132+
/**
133+
* @brief Free a listener
134+
*
135+
* @details This function is called when the global context is destroyed on
136+
* every remaining listener. An implementation is available in `listeners.h`.
137+
*
138+
* @param item list item
139+
*/
140+
void sys_listener_destroy(struct ListHead *item);
141+
61142
#ifndef AVM_NO_SMP
62143

63144
/**

src/platforms/esp32/components/avm_builtins/gpio_driver.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,6 @@ static bool gpiodriver_is_gpio_attached(struct GPIOData *gpio_data, int gpio_num
343343
static term gpiodriver_set_int(Context *ctx, int32_t target_pid, term cmd)
344344
{
345345
GlobalContext *glb = ctx->global;
346-
struct ESP32PlatformData *platform = glb->platform_data;
347346

348347
struct GPIOData *gpio_data = ctx->platform_data;
349348

@@ -399,7 +398,7 @@ static term gpiodriver_set_int(Context *ctx, int32_t target_pid, term cmd)
399398
list_append(&gpio_data->gpio_listeners, &data->gpio_listener_list_head);
400399
data->gpio = gpio_num;
401400
data->target_local_pid = target_pid;
402-
synclist_append(&platform->listeners, &data->listener.listeners_list_head);
401+
sys_register_listener(glb, &data->listener);
403402
data->listener.sender = data;
404403
data->listener.handler = gpio_interrupt_callback;
405404

@@ -426,7 +425,7 @@ static term gpiodriver_remove_int(Context *ctx, term cmd)
426425
struct GPIOListenerData *gpio_listener = GET_LIST_ENTRY(item, struct GPIOListenerData, gpio_listener_list_head);
427426
if (gpio_listener->gpio == gpio_num) {
428427
list_remove(&gpio_listener->gpio_listener_list_head);
429-
list_remove(&gpio_listener->listener.listeners_list_head);
428+
sys_unregister_listener(ctx->global, &gpio_listener->listener);
430429
free(gpio_listener);
431430

432431
gpio_set_intr_type(gpio_num, GPIO_INTR_DISABLE);

src/platforms/esp32/components/avm_builtins/socket_driver.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ void socket_driver_init(GlobalContext *glb)
285285
struct ESP32PlatformData *platform = glb->platform_data;
286286
socket_listener->sender = netconn_events;
287287
socket_listener->handler = socket_events_handler;
288-
synclist_append(&platform->listeners, &socket_listener->listeners_list_head);
288+
sys_register_listener(glb, socket_listener);
289289

290290
synclist_init(&platform->sockets);
291291
list_init(&platform->ready_connections);

src/platforms/esp32/components/avm_builtins/uart_driver.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,15 +267,13 @@ Context *uart_driver_create_port(GlobalContext *global, term opts)
267267

268268
uart_set_pin(uart_num, tx_pin, rx_pin, rts_pin, cts_pin);
269269

270-
struct ESP32PlatformData *platform = global->platform_data;
271-
272270
struct UARTData *uart_data = malloc(sizeof(struct UARTData));
273271
if (IS_NULL_PTR(uart_data)) {
274272
fprintf(stderr, "Failed to allocate memory: %s:%i.\n", __FILE__, __LINE__);
275273
AVM_ABORT();
276274
}
277275
uart_data->listener.handler = uart_interrupt_callback;
278-
synclist_append(&platform->listeners, &uart_data->listener.listeners_list_head);
276+
sys_register_listener(global, &uart_data->listener);
279277
uart_data->reader_process_pid = term_invalid_term();
280278
uart_data->reader_ref_ticks = 0;
281279
uart_data->uart_num = uart_num;
@@ -423,7 +421,7 @@ static void uart_driver_do_close(Context *ctx, term msg)
423421

424422
int local_pid = term_to_local_process_id(pid);
425423

426-
list_remove(&uart_data->listener.listeners_list_head);
424+
sys_unregister_listener(glb, &uart_data->listener);
427425

428426
if (UNLIKELY(memory_ensure_free(ctx, TUPLE_SIZE(2) + REF_SIZE) != MEMORY_GC_OK)) {
429427
ESP_LOGE(TAG, "[uart_driver_do_close] Failed to allocate space for return value");

src/platforms/esp32/components/avm_sys/include/esp32_sys.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
#include <time.h>
2828

29+
#include "sys.h"
30+
2931
#define REGISTER_PORT_DRIVER(NAME, INIT_CB, CREATE_CB) \
3032
struct PortDriverDef NAME##_port_driver_def = { \
3133
.port_driver_name = #NAME, \
@@ -61,21 +63,17 @@
6163

6264
#define EVENT_DESCRIPTORS_COUNT 16
6365

64-
typedef struct EventListener EventListener;
65-
66-
typedef EventListener *(*event_handler_t)(GlobalContext *glb, EventListener *listener);
66+
typedef void *listener_event_t;
6767

6868
struct EventListener
6969
{
7070
struct ListHead listeners_list_head;
71-
7271
event_handler_t handler;
73-
void *sender;
72+
listener_event_t sender;
7473
};
7574

7675
struct ESP32PlatformData
7776
{
78-
struct SyncList listeners;
7977
struct SyncList sockets;
8078
struct ListHead ready_connections;
8179
};

0 commit comments

Comments
 (0)