Skip to content

Commit 69c5177

Browse files
committed
Merge pull request atomvm#606 from pguyot/w22/factorize-event-listeners
Factorize event listeners 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. These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 2623fa1 + b577792 commit 69c5177

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)