[PATCH] iface-modem-time: don't hold a ref while waiting to be registered
Dan Williams
dcbw at redhat.com
Fri May 25 16:05:24 UTC 2018
On Tue, 2018-05-15 at 11:27 +0200, Aleksander Morgado wrote:
> The logic implementing the network timezone loading was holding a
> strong reference to the modem (inside the GTask) while waiting to
> be registered.
>
> This was triggering some possible memory leaks as the modem object
> could have been left around even after the modem was disconnected.
> E.g. if the modem booted without antennas plugged in, it was never
> getting registered, and if we unplugged it right away in that state,
> the network timezone logic would have left a modem reference around
> without disposing it.
>
> This issue, combined with e.g. other interfaces relying on disposing
> its own logic with the last object reference (e.g. Signal interface
> sets up its logic in a context bound to the lifetime of the object)
> would mean that a lot of different logic blocks were kept running
> even
> after the modem device was unplugged from the system.
>
> We avoid this issue by making sure that no new additional reference
> is
> taken by the logic in charge of updating the network timezone. We
> just
> make the timezone update context be bound to the lifetime of the
> object, as other interfaces do.
>
> While doing this, we also remove the logic in charge of "cancelling"
> the context, as it isn't needed. If the logic needs to be cancelled,
> we would just remove any configured timeout, which is enough.
>
> As part of the changes, the logic has also been improved so that the
> network timezone isn't only updated the first time the modem gets
> registered. If the modem gets unregistered and re-registered, we
> would
> reload the network timezone information.
LGTM; seems to work OK with a CDMA Huawei device too.
Dan
> ---
> src/mm-iface-modem-time.c | 334 ++++++++++++++--------------------
> ----
> 1 file changed, 122 insertions(+), 212 deletions(-)
>
> diff --git a/src/mm-iface-modem-time.c b/src/mm-iface-modem-time.c
> index f0c1fea6..fbc4a1ad 100644
> --- a/src/mm-iface-modem-time.c
> +++ b/src/mm-iface-modem-time.c
> @@ -21,16 +21,13 @@
> #include "mm-iface-modem-time.h"
> #include "mm-log.h"
>
> -#define SUPPORT_CHECKED_TAG "time-support-checked-tag"
> -#define SUPPORTED_TAG "time-supported-tag"
> -#define NETWORK_TIMEZONE_CANCELLABLE_TAG "time-network-timezone-
> cancellable"
> +#define SUPPORT_CHECKED_TAG "time-support-checked-tag"
> +#define SUPPORTED_TAG "time-supported-tag"
> +#define NETWORK_TIMEZONE_CONTEXT_TAG "time-network-timezone-context"
>
> static GQuark support_checked_quark;
> static GQuark supported_quark;
> -static GQuark network_timezone_cancellable_quark;
> -
> -#define TIMEZONE_POLL_INTERVAL_SEC 5
> -#define TIMEZONE_POLL_RETRIES 6
> +static GQuark network_timezone_context_quark;
>
> /*******************************************************************
> **********/
>
> @@ -123,50 +120,37 @@ handle_get_network_time (MmGdbusModemTime
> *skeleton,
> }
>
> /*******************************************************************
> **********/
> +/* Network timezone loading */
> +
> +/*
> + * As soon as we're registered in a network, we try to check
> timezone
> + * information up to NETWORK_TIMEZONE_POLL_RETRIES times. As soon as
> one of
> + * the retries succeeds, we stop polling as we don't expect the
> timezone
> + * information to change while registered in the same network.
> + */
> +#define NETWORK_TIMEZONE_POLL_INTERVAL_SEC 5
> +#define NETWORK_TIMEZONE_POLL_RETRIES 6
>
> typedef struct {
> - gulong cancelled_id;
> gulong state_changed_id;
> + MMModemState state;
> guint network_timezone_poll_id;
> guint network_timezone_poll_retries;
> -} UpdateNetworkTimezoneContext;
> -
> -static gboolean timezone_poll_cb (GTask *task);
> -
> -static gboolean
> -update_network_timezone_finish (MMIfaceModemTime *self,
> - GAsyncResult *res,
> - GError **error)
> -{
> - return g_task_propagate_boolean (G_TASK (res), error);
> -}
> +} NetworkTimezoneContext;
>
> static void
> -cancelled (GCancellable *cancellable,
> - GTask *task)
> +network_timezone_context_free (NetworkTimezoneContext *ctx)
> {
> - MMIfaceModemTime *self;
> - UpdateNetworkTimezoneContext *ctx;
> -
> - self = g_task_get_source_object (task);
> - ctx = g_task_get_task_data (task);
> -
> - /* If waiting to get registered, disconnect signal */
> - if (ctx->state_changed_id)
> - g_signal_handler_disconnect (self,
> - ctx->state_changed_id);
> -
> - /* If waiting in the timeout loop, remove the timeout */
> - else if (ctx->network_timezone_poll_id)
> + /* Note: no need to remove signal connection here, we have
> already done it
> + * in stop_network_timezone() when the logic is disabled (or
> will be done
> + * automatically when the last modem object reference is
> dropped) */
> + if (ctx->network_timezone_poll_id)
> g_source_remove (ctx->network_timezone_poll_id);
> -
> - g_task_return_new_error (task,
> - MM_CORE_ERROR,
> - MM_CORE_ERROR_CANCELLED,
> - "Network timezone loading cancelled");
> - g_object_unref (task);
> + g_free (ctx);
> }
>
> +static gboolean network_timezone_poll_cb (MMIfaceModemTime *self);
> +
> static void
> update_network_timezone_dictionary (MMIfaceModemTime *self,
> MMNetworkTimezone *tz)
> @@ -190,180 +174,160 @@ update_network_timezone_dictionary
> (MMIfaceModemTime *self,
>
> static void
> load_network_timezone_ready (MMIfaceModemTime *self,
> - GAsyncResult *res,
> - GTask *task)
> + GAsyncResult *res)
> {
> - UpdateNetworkTimezoneContext *ctx;
> GError *error = NULL;
> MMNetworkTimezone *tz;
>
> - if (g_task_return_error_if_cancelled (task)) {
> - g_object_unref (task);
> - return;
> - }
> + /* Finish the async operation */
> + tz = MM_IFACE_MODEM_TIME_GET_INTERFACE (self)-
> >load_network_timezone_finish (self, res, &error);
> + if (!tz) {
> + NetworkTimezoneContext *ctx;
>
> - ctx = g_task_get_task_data (task);
> + mm_dbg ("Couldn't load network timezone: %s", error-
> >message);
> + g_error_free (error);
> +
> + /* Note: may be NULL if the polling has been removed while
> processing the async operation */
> + ctx = (NetworkTimezoneContext *) g_object_get_qdata
> (G_OBJECT (self), network_timezone_context_quark);
> + if (!ctx)
> + return;
>
> - /* Finish the async operation */
> - tz = MM_IFACE_MODEM_TIME_GET_INTERFACE (self)-
> >load_network_timezone_finish (self,
> -
> res,
> -
> &error);
> - if (error) {
> /* Retry? */
> ctx->network_timezone_poll_retries--;
>
> - /* Fatal if no more retries, or if specific error is not
> RETRY */
> + /* If no more retries, we don't do anything else */
> if (ctx->network_timezone_poll_retries == 0 ||
> - !g_error_matches (error,
> - MM_CORE_ERROR,
> - MM_CORE_ERROR_RETRY)) {
> - g_task_return_error (task, error);
> - g_object_unref (task);
> + !g_error_matches (error, MM_CORE_ERROR,
> MM_CORE_ERROR_RETRY)) {
> + mm_warn ("Couldn't load network timezone from the
> current network");
> return;
> }
>
> - /* Otherwise, reconnect cancellable and relaunch timeout to
> query a bit
> - * later */
> - ctx->cancelled_id = g_cancellable_connect
> (g_task_get_cancellable (task),
> - G_CALLBACK
> (cancelled),
> - task,
> - NULL);
> - ctx->network_timezone_poll_id = g_timeout_add_seconds
> (TIMEZONE_POLL_INTERVAL_SEC,
> - (GSou
> rceFunc)timezone_poll_cb,
> - task)
> ;
> -
> - g_error_free (error);
> + /* Otherwise, relaunch timeout to query a bit later */
> + ctx->network_timezone_poll_id = g_timeout_add_seconds
> (NETWORK_TIMEZONE_POLL_INTERVAL_SEC,
> + (GSou
> rceFunc)network_timezone_poll_cb,
> + self)
> ;
> return;
> }
>
> /* Got final result properly, update the property in the
> skeleton */
> update_network_timezone_dictionary (self, tz);
> - g_task_return_boolean (task, TRUE);
> - g_object_unref (task);
> g_object_unref (tz);
> }
>
> static gboolean
> -timezone_poll_cb (GTask *task)
> +network_timezone_poll_cb (MMIfaceModemTime *self)
> {
> - MMIfaceModemTime *self;
> - UpdateNetworkTimezoneContext *ctx;
> -
> - self = g_task_get_source_object (task);
> - ctx = g_task_get_task_data (task);
> + NetworkTimezoneContext *ctx;
>
> + ctx = (NetworkTimezoneContext *) g_object_get_qdata (G_OBJECT
> (self), network_timezone_context_quark);
> ctx->network_timezone_poll_id = 0;
>
> - /* Before we launch the async loading of the network timezone,
> - * we disconnect the cancellable signal. We don't want to get
> - * signaled while waiting to finish this async method, we'll
> - * check the cancellable afterwards instead. */
> - g_cancellable_disconnect (g_task_get_cancellable (task),
> - ctx->cancelled_id);
> - ctx->cancelled_id = 0;
> -
> MM_IFACE_MODEM_TIME_GET_INTERFACE (self)->load_network_timezone
> (
> self,
> (GAsyncReadyCallback)load_network_timezone_ready,
> - task);
> + NULL);
>
> return G_SOURCE_REMOVE;
> }
>
> static void
> -start_timezone_poll (GTask *task)
> +start_network_timezone_poll (MMIfaceModemTime *self)
> {
> - UpdateNetworkTimezoneContext *ctx;
> + NetworkTimezoneContext *ctx;
>
> - ctx = g_task_get_task_data (task);
> + ctx = (NetworkTimezoneContext *) g_object_get_qdata (G_OBJECT
> (self), network_timezone_context_quark);
>
> - /* Setup loop to query current timezone, don't do it right away.
> - * Note that we're passing the context reference to the loop. */
> - ctx->network_timezone_poll_retries = TIMEZONE_POLL_RETRIES;
> - ctx->network_timezone_poll_id = g_timeout_add_seconds
> (TIMEZONE_POLL_INTERVAL_SEC,
> - (GSourceF
> unc)timezone_poll_cb,
> - task);
> + mm_dbg ("Network timezone polling started");
> + ctx->network_timezone_poll_retries =
> NETWORK_TIMEZONE_POLL_RETRIES;
> + ctx->network_timezone_poll_id = g_timeout_add_seconds
> (NETWORK_TIMEZONE_POLL_INTERVAL_SEC,
> (GSourceFunc)network_timezone_poll_cb, self);
> }
>
> static void
> -state_changed (MMIfaceModemTime *self,
> - GParamSpec *spec,
> - GTask *task)
> +stop_network_timezone_poll (MMIfaceModemTime *self)
> {
> - UpdateNetworkTimezoneContext *ctx;
> - MMModemState state = MM_MODEM_STATE_UNKNOWN;
> + NetworkTimezoneContext *ctx;
>
> - g_object_get (self,
> - MM_IFACE_MODEM_STATE, &state,
> - NULL);
> + ctx = (NetworkTimezoneContext *) g_object_get_qdata (G_OBJECT
> (self), network_timezone_context_quark);
>
> - /* We're waiting to get registered */
> - if (state < MM_MODEM_STATE_REGISTERED)
> - return;
> + if (ctx->network_timezone_poll_id) {
> + mm_dbg ("Network timezone polling stopped");
> + g_source_remove (ctx->network_timezone_poll_id);
> + ctx->network_timezone_poll_id = 0;
> + }
> +}
>
> - ctx = g_task_get_task_data (task);
> +static void
> +network_timezone_state_changed (MMIfaceModemTime *self)
> +{
> + NetworkTimezoneContext *ctx;
> + MMModemState old_state;
> +
> + ctx = (NetworkTimezoneContext *) g_object_get_qdata (G_OBJECT
> (self), network_timezone_context_quark);
> + old_state = ctx->state;
> +
> + g_object_get (self, MM_IFACE_MODEM_STATE, &ctx->state, NULL);
>
> - /* Got registered, disconnect signal */
> - if (ctx->state_changed_id) {
> - g_signal_handler_disconnect (self,
> - ctx->state_changed_id);
> - ctx->state_changed_id = 0;
> + /* If going from unregistered to registered, start polling */
> + if (ctx->state >= MM_MODEM_STATE_REGISTERED && old_state <
> MM_MODEM_STATE_REGISTERED) {
> + start_network_timezone_poll (self);
> + return;
> }
>
> - /* Once we know we're registered, start timezone poll */
> - start_timezone_poll (task);
> + /* If going from registered to unregistered, stop polling */
> + if (ctx->state < MM_MODEM_STATE_REGISTERED && old_state >=
> MM_MODEM_STATE_REGISTERED) {
> + stop_network_timezone_poll (self);
> + return;
> + }
> }
>
> static void
> -update_network_timezone (MMIfaceModemTime *self,
> - GCancellable *cancellable,
> - GAsyncReadyCallback callback,
> - gpointer user_data)
> +start_network_timezone (MMIfaceModemTime *self)
> {
> - MMModemState state = MM_MODEM_STATE_UNKNOWN;
> - UpdateNetworkTimezoneContext *ctx;
> - GTask *task;
> + NetworkTimezoneContext *ctx;
>
> /* If loading network timezone not supported, just finish here
> */
> if (!MM_IFACE_MODEM_TIME_GET_INTERFACE (self)-
> >load_network_timezone ||
> !MM_IFACE_MODEM_TIME_GET_INTERFACE (self)-
> >load_network_timezone_finish) {
> - g_task_report_new_error (self,
> - callback,
> - user_data,
> - update_network_timezone,
> - MM_CORE_ERROR,
> - MM_CORE_ERROR_UNSUPPORTED,
> - "Loading network timezone is not
> supported");
> + mm_dbg ("Loading network timezone is not supported");
> return;
> }
>
> - ctx = g_new0 (UpdateNetworkTimezoneContext, 1);
> + if (G_UNLIKELY (!network_timezone_context_quark))
> + network_timezone_context_quark = (g_quark_from_static_string
> (NETWORK_TIMEZONE_CONTEXT_TAG));
>
> - task = g_task_new (self, cancellable, callback, user_data);
> - g_task_set_task_data (task, ctx, g_free);
> + ctx = g_new0 (NetworkTimezoneContext, 1);
> + g_object_set_qdata_full (G_OBJECT (self),
> + network_timezone_context_quark,
> + ctx,
> + (GDestroyNotify)network_timezone_contex
> t_free);
>
> - /* Note: we don't expect to get cancelled by any other thread,
> so no
> - * need to check if we're cancelled just after connecting to the
> - * cancelled signal */
> - ctx->cancelled_id = g_cancellable_connect (cancellable,
> - G_CALLBACK
> (cancelled),
> - task,
> - NULL);
> + /* Want to get notified when modem state changes to
> enable/disable
> + * the polling logic. This signal is connected as long as the
> network timezone
> + * logic is enabled. */
> + g_object_get (self, MM_IFACE_MODEM_STATE, &ctx->state, NULL);
> + ctx->state_changed_id = g_signal_connect (self,
> + "notify::"
> MM_IFACE_MODEM_STATE,
> + G_CALLBACK
> (network_timezone_state_changed),
> + NULL);
>
> - g_object_get (self,
> - MM_IFACE_MODEM_STATE, &state,
> - NULL);
> + /* If we're registered already, start timezone poll */
> + if (ctx->state >= MM_MODEM_STATE_REGISTERED)
> + start_network_timezone_poll (self);
> +}
>
> - /* Already registered? */
> - if (state >= MM_MODEM_STATE_REGISTERED) {
> - /* Once we know we're registered, start timezone poll */
> - start_timezone_poll (task);
> - } else {
> - /* Want to get notified when modem state changes */
> - ctx->state_changed_id = g_signal_connect (self,
> - "notify::"
> MM_IFACE_MODEM_STATE,
> - G_CALLBACK
> (state_changed),
> - task);
> +static void
> +stop_network_timezone (MMIfaceModemTime *self)
> +{
> + NetworkTimezoneContext *ctx;
> +
> + ctx = (NetworkTimezoneContext *) g_object_get_qdata (G_OBJECT
> (self), network_timezone_context_quark);
> + if (ctx) {
> + /* Remove signal connection and then trigger context free */
> + if (ctx->state_changed_id) {
> + g_signal_handler_disconnect (self, ctx-
> >state_changed_id);
> + ctx->state_changed_id = 0;
> + }
> + g_object_set_qdata (G_OBJECT (self),
> network_timezone_context_quark, NULL);
> }
> }
>
> @@ -477,25 +441,11 @@ interface_disabling_step (GTask *task)
> /* Fall down to next step */
> ctx->step++;
>
> - case DISABLING_STEP_CANCEL_NETWORK_TIMEZONE_UPDATE: {
> - if (G_LIKELY (network_timezone_cancellable_quark)) {
> - GCancellable *cancellable = NULL;
> -
> - cancellable = g_object_get_qdata (G_OBJECT (self),
> - network_timezone_cance
> llable_quark);
> -
> - /* If network timezone loading is currently running,
> abort it */
> - if (cancellable) {
> - g_cancellable_cancel (cancellable);
> - g_object_set_qdata (G_OBJECT (self),
> - network_timezone_cancellable_qua
> rk,
> - NULL);
> - }
> - }
> -
> + case DISABLING_STEP_CANCEL_NETWORK_TIMEZONE_UPDATE:
> + /* Stop and cleanup context */
> + stop_network_timezone (self);
> /* Fall down to next step */
> ctx->step++;
> - }
>
> case DISABLING_STEP_DISABLE_UNSOLICITED_EVENTS:
> /* Allow cleaning up unsolicited events */
> @@ -596,26 +546,6 @@ mm_iface_modem_time_enable_finish
> (MMIfaceModemTime *self,
> return g_task_propagate_boolean (G_TASK (res), error);
> }
>
> -static void
> -update_network_timezone_ready (MMIfaceModemTime *self,
> - GAsyncResult *res)
> -{
> - GError *error = NULL;
> -
> - if (!update_network_timezone_finish (self, res, &error)) {
> - if (!g_error_matches (error,
> - MM_CORE_ERROR,
> - MM_CORE_ERROR_UNSUPPORTED))
> - mm_dbg ("Couldn't update network timezone: '%s'", error-
> >message);
> - g_error_free (error);
> - }
> -
> - /* Cleanup our cancellable in the context */
> - g_object_set_qdata (G_OBJECT (self),
> - network_timezone_cancellable_quark,
> - NULL);
> -}
> -
> static void
> setup_unsolicited_events_ready (MMIfaceModemTime *self,
> GAsyncResult *res,
> @@ -677,31 +607,11 @@ interface_enabling_step (GTask *task)
> /* Fall down to next step */
> ctx->step++;
>
> - case ENABLING_STEP_SETUP_NETWORK_TIMEZONE_RETRIEVAL: {
> - GCancellable *cancellable;
> -
> - /* We'll create a cancellable which is valid as long as
> we're updating
> - * network timezone, and we set it as context */
> - cancellable = g_cancellable_new ();
> - if (G_UNLIKELY (!network_timezone_cancellable_quark))
> - network_timezone_cancellable_quark =
> (g_quark_from_static_string (
> - NETWORK_TIMEZO
> NE_CANCELLABLE_TAG));
> - g_object_set_qdata_full (G_OBJECT (self),
> - network_timezone_cancellable_quark,
> - cancellable,
> - g_object_unref);
> -
> - update_network_timezone (self,
> - cancellable,
> - (GAsyncReadyCallback)update_network
> _timezone_ready,
> - NULL);
> -
> - /* NOTE!!!! We'll leave the timezone network update
> operation
> - * running, we don't wait for it to finish */
> -
> + case ENABLING_STEP_SETUP_NETWORK_TIMEZONE_RETRIEVAL:
> + /* We start it and schedule it to run asynchronously */
> + start_network_timezone (self);
> /* Fall down to next step */
> ctx->step++;
> - }
>
> case ENABLING_STEP_SETUP_UNSOLICITED_EVENTS:
> /* Allow setting up unsolicited events */
> --
> 2.17.0
> _______________________________________________
> ModemManager-devel mailing list
> ModemManager-devel at lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/modemmanager-devel
More information about the ModemManager-devel
mailing list