Skip to content

Commit bfc69db

Browse files
projectgusdpgeorge
authored andcommitted
extmod,rp2: Keep LWIP timer running if lwip poll_sockets() is called.
Fixes rp2 issue where socket.getaddrinfo() could block indefinitely if an interface goes down and still has a DNS server configured, as the LWIP timer stops running and can't time out the DNS query. Adds a regression test under multi_wlan that times out on rp2 without this fix. Fixes issue micropython#18797. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton <angus@redyak.com.au>
1 parent 95b3e72 commit bfc69db

6 files changed

Lines changed: 61 additions & 4 deletions

File tree

extmod/modlwip.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@
9595
#define MICROPY_PY_LWIP_EXIT
9696
#endif
9797

98+
#ifndef MICROPY_PY_LWIP_POLL_HOOK
99+
// Optional port-level hook called if/when LWIP is being polled
100+
#define MICROPY_PY_LWIP_POLL_HOOK
101+
#endif
102+
98103
#ifdef MICROPY_PY_LWIP_SLIP
99104
#include "netif/slipif.h"
100105
#include "lwip/sio.h"
@@ -360,6 +365,7 @@ static inline bool socket_is_timedout(lwip_socket_obj_t *socket, mp_uint_t ticks
360365
}
361366

362367
static inline void poll_sockets(void) {
368+
MICROPY_PY_LWIP_POLL_HOOK
363369
mp_event_wait_ms(1);
364370
}
365371

ports/rp2/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ typedef intptr_t mp_off_t;
294294
#include "pico/rand.h"
295295
extern void lwip_lock_acquire(void);
296296
extern void lwip_lock_release(void);
297+
extern void lwip_poll_hook(void);
297298

298299
#if MICROPY_PY_BLUETOOTH || MICROPY_PY_BLUETOOTH_CYW43
299300
// Bluetooth code only runs in the scheduler, no locking/mutex required.

ports/rp2/mphalport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#define MICROPY_PY_LWIP_ENTER lwip_lock_acquire();
5252
#define MICROPY_PY_LWIP_REENTER lwip_lock_acquire();
5353
#define MICROPY_PY_LWIP_EXIT lwip_lock_release();
54+
#define MICROPY_PY_LWIP_POLL_HOOK lwip_poll_hook();
5455

5556
// Port level Wait-for-Event macro
5657
//

ports/rp2/mpnetworkport.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,15 @@ void lwip_lock_release(void) {
134134
pendsv_resume();
135135
}
136136

137+
void lwip_poll_hook(void) {
138+
// Start the network soft timer if necessary (it may still only run once,
139+
// but ensure timeouts will complete inside any loop that's polling lwip)
140+
if (mp_network_soft_timer.mode == SOFT_TIMER_MODE_ONE_SHOT) {
141+
mp_network_soft_timer.mode = SOFT_TIMER_MODE_PERIODIC;
142+
soft_timer_reinsert(&mp_network_soft_timer, LWIP_TICK_RATE_MS);
143+
}
144+
}
145+
137146
// This is called by soft_timer and executes at PendSV level.
138147
static void mp_network_soft_timer_callback(soft_timer_entry_t *self) {
139148
// Run the lwIP internal updates.
@@ -179,10 +188,8 @@ void mod_network_lwip_init(void) {
179188
static void mp_network_netif_status_cb(struct netif *netif, netif_nsc_reason_t reason, const netif_ext_callback_args_t *args) {
180189
// Start the network soft timer any time an interface comes up, unless
181190
// it's already running
182-
if (reason == LWIP_NSC_LINK_CHANGED && args->link_changed.state
183-
&& mp_network_soft_timer.mode == SOFT_TIMER_MODE_ONE_SHOT) {
184-
mp_network_soft_timer.mode = SOFT_TIMER_MODE_PERIODIC;
185-
soft_timer_reinsert(&mp_network_soft_timer, LWIP_TICK_RATE_MS);
191+
if (reason == LWIP_NSC_LINK_CHANGED && args->link_changed.state) {
192+
lwip_poll_hook();
186193
}
187194

188195
if (reason == LWIP_NSC_NETIF_REMOVED) {

tests/multi_wlan/getaddrinfo.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# This is a regression test to ensure getaddrinfo() fails (after a timeout)
2+
# when Wi-Fi is disconnected.
3+
#
4+
# It doesn't require multiple instances, but it does require Wi-Fi to be present
5+
# but not active, and for no other network interface to be configured. The only
6+
# tests which already meet these conditions is here in multi_wlan tests.
7+
try:
8+
from network import WLAN
9+
except (ImportError, NameError):
10+
print("SKIP")
11+
raise SystemExit
12+
13+
import socket
14+
15+
16+
def instance0():
17+
WLAN(WLAN.IF_AP).active(0)
18+
wlan = WLAN(WLAN.IF_STA)
19+
wlan.active(0)
20+
21+
multitest.next()
22+
23+
try:
24+
socket.getaddrinfo("micropython.org", 80)
25+
except OSError as er:
26+
print(
27+
"active(0) failed"
28+
) # This may fail with code -6 or -2 depending on whether WLAN has been active since reset
29+
30+
wlan.active(1)
31+
32+
try:
33+
socket.getaddrinfo("micropython.org", 80)
34+
except OSError as er:
35+
print(
36+
"active(1) failed", er.errno in (-2, -202)
37+
) # This one should always be -2 or -202 depending on port
38+
39+
wlan.active(0)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
--- instance0 ---
2+
active(0) failed
3+
active(1) failed True

0 commit comments

Comments
 (0)