module-rtp: Retry starting stream if this failed with ENODEV errno

ENODEV is not a fatal error, and trying again later to set up the
socket usually succeeds. Do such retries with a timer.
This commit is contained in:
Carlos Rafael Giani 2025-05-20 12:57:13 +02:00
parent 71c0c8e34c
commit d258892392

View file

@ -164,6 +164,9 @@ struct impl {
uint32_t cleanup_interval; uint32_t cleanup_interval;
struct spa_source *standby_timer; struct spa_source *standby_timer;
/* This timer is used when the first stream_start() call fails because
* of an ENODEV error (see the stream_start() code for details) */
struct spa_source *stream_start_retry_timer;
struct pw_properties *stream_props; struct pw_properties *stream_props;
struct rtp_stream *stream; struct rtp_stream *stream;
@ -334,6 +337,23 @@ error:
return res; return res;
} }
static int stream_start(struct impl *impl);
static void on_stream_start_retry_timer_event(void *data, uint64_t expirations)
{
struct impl *impl = data;
pw_log_debug("trying again to start RTP listener after previous attempt failed with ENODEV");
stream_start(impl);
}
static void destroy_stream_start_retry_timer(struct impl *impl)
{
if (impl->stream_start_retry_timer != NULL) {
pw_loop_destroy_source(impl->loop, impl->stream_start_retry_timer);
impl->stream_start_retry_timer = NULL;
}
}
static int stream_start(struct impl *impl) static int stream_start(struct impl *impl)
{ {
int fd; int fd;
@ -345,10 +365,53 @@ static int stream_start(struct impl *impl)
if ((fd = make_socket((const struct sockaddr *)&impl->src_addr, if ((fd = make_socket((const struct sockaddr *)&impl->src_addr,
impl->src_len, impl->ifname)) < 0) { impl->src_len, impl->ifname)) < 0) {
pw_log_error("failed to create socket: %m"); /* If make_socket() tries to create a socket and join to a multicast
return -errno; * group while the network interfaces are not ready yet to do so
* (usually because a network manager component is still setting up
* those network interfaces), ENODEV will be returned. This is essentially
* a race condition. There is no discernible way to be notified when the
* network interfaces are ready for that operation, so the next best
* approach is to essentially do a form of polling by retrying the
* stream_start() call after some time. The stream_start_retry_timer exists
* precisely for that purpose. This means that ENODEV is not treated as
* an error, but instead, it triggers the creation of that timer. */
if (errno == ENODEV) {
pw_log_warn("failed to create socket because network device is not ready "
"and present yet; will try again");
if (impl->stream_start_retry_timer == NULL) {
struct timespec value, interval;
impl->stream_start_retry_timer = pw_loop_add_timer(impl->loop,
on_stream_start_retry_timer_event, impl);
/* Use a 1-second retry interval. The network interfaces
* are likely to be up and running then. */
value.tv_sec = 1;
value.tv_nsec = 0;
interval.tv_sec = 1;
interval.tv_nsec = 0;
pw_loop_update_timer(impl->loop, impl->stream_start_retry_timer, &value,
&interval, false);
}
/* Do nothing if the timer is already up. */
/* It is important to return 0 in this case. Otherwise, the nonzero return
* value will later be propagated through the core as an error. */
return 0;
} else {
pw_log_error("failed to create socket: %m");
/* If ENODEV was returned earlier, and the stream_start_retry_timer
* was consequently created, but then a non-ENODEV error occurred,
* the timer must be stopped and removed. */
destroy_stream_start_retry_timer(impl);
return -errno;
}
} }
/* Cleanup the timer in case ENODEV occurred earlier, and this time,
* the socket creation succeeded. */
destroy_stream_start_retry_timer(impl);
impl->source = pw_loop_add_io(impl->data_loop, fd, impl->source = pw_loop_add_io(impl->data_loop, fd,
SPA_IO_IN, true, on_rtp_io, impl); SPA_IO_IN, true, on_rtp_io, impl);
if (impl->source == NULL) { if (impl->source == NULL) {
@ -366,6 +429,8 @@ static void stream_stop(struct impl *impl)
pw_log_info("stopping RTP listener"); pw_log_info("stopping RTP listener");
destroy_stream_start_retry_timer(impl);
pw_loop_destroy_source(impl->data_loop, impl->source); pw_loop_destroy_source(impl->data_loop, impl->source);
impl->source = NULL; impl->source = NULL;
} }
@ -516,6 +581,8 @@ static void impl_destroy(struct impl *impl)
if (impl->standby_timer) if (impl->standby_timer)
pw_loop_destroy_source(impl->loop, impl->standby_timer); pw_loop_destroy_source(impl->loop, impl->standby_timer);
destroy_stream_start_retry_timer(impl);
if (impl->data_loop) if (impl->data_loop)
pw_context_release_loop(impl->context, impl->data_loop); pw_context_release_loop(impl->context, impl->data_loop);