diff mbox series

gatchat: fix unref

Message ID 20240119111017.1407534-1-m.lyubimov@aqsi.ru (mailing list archive)
State Superseded, archived
Headers show
Series gatchat: fix unref | expand

Commit Message

Maxim Lyubimov Jan. 19, 2024, 11:10 a.m. UTC
If the new_bytes handler processes a modem shutdown URC, it results in
a call to the g_at_chat_unref function, which includes a call to the
chat_cleanup function, which clears the list of URC handlers that
continue to be traversed after the current handler has completed, and
may lead to errors. To correct the situation, the at_chat_ref function
has been added, which is called at the beginning of the new_bytes
handler. Added a call to the at_chat_unref function at the end of the
new_bytes handler. Added at_chat_ref function call to g_at_chat_clone.
The previous partial solution to this problem has been removed.
---
 gatchat/gatchat.c | 84 +++++++++++++++++++++++------------------------
 1 file changed, 41 insertions(+), 43 deletions(-)

Comments

Denis Kenzior Jan. 19, 2024, 5:19 p.m. UTC | #1
Hi Maxim,

On 1/19/24 05:10, Maxim Lyubimov wrote:
> If the new_bytes handler processes a modem shutdown URC, it results in

So new_bytes would call into at_chat_match_notify(), and one of the callbacks 
invoked would call g_at_chat_unref?

> a call to the g_at_chat_unref function, which includes a call to the
> chat_cleanup function, which clears the list of URC handlers that

This should be taken care of by in_notify being set?  Hmm... I guess you end up 
unrefing the last chat...

Do you have a backtrace or asan output by any chance?

> continue to be traversed after the current handler has completed, and
> may lead to errors. To correct the situation, the at_chat_ref function
> has been added, which is called at the beginning of the new_bytes

The original intent with the re-entrancy guards was to stop processing input 
once g_at_chat_unref has been called.  In theory, if all chats have been cleaned 
up via g_at_chat_unref, then the calls to:
	at_chat_cancel_group(chat->parent, chat->group);
	g_at_chat_unregister_all(chat);

...should unregister all notifications and commands.  So I think what you 
propose here is okay...  But I wonder if a more explicit stop of processing 
would be better.  Maybe:
  - Do not invoke chat_cleanup if in_read_handler is TRUE in at_chat_unref()
  - Invoke chat_cleanup in addition to g_free at the end of new_bytes?

> handler. Added a call to the at_chat_unref function at the end of the
> new_bytes handler. Added at_chat_ref function call to g_at_chat_clone.
> The previous partial solution to this problem has been removed.
> ---
>   gatchat/gatchat.c | 84 +++++++++++++++++++++++------------------------
>   1 file changed, 41 insertions(+), 43 deletions(-)
> 

Regards,
-Denis
Maxim Lyubimov Jan. 23, 2024, 1:42 p.m. UTC | #2
Hi Denis,

On 19/01/2024 в 11:19 -0600, Denis Kenzior пишет:
> 
> The original intent with the re-entrancy guards was to stop
> processing input 
> once g_at_chat_unref has been called.  In theory, if all chats have
> been cleaned 
> up via g_at_chat_unref, then the calls to:
> 	at_chat_cancel_group(chat->parent, chat->group);
> 	g_at_chat_unregister_all(chat);
> 
> ...should unregister all notifications and commands.  So I think what
> you 
> propose here is okay...  But I wonder if a more explicit stop of
> processing 
> would be better.  Maybe:
>   - Do not invoke chat_cleanup if in_read_handler is TRUE in
> at_chat_unref()
>   - Invoke chat_cleanup in addition to g_free at the end of
> new_bytes?
> 

Okay, whatever you say

Best regards,
Lyubimov Maxim
diff mbox series

Patch

diff --git a/gatchat/gatchat.c b/gatchat/gatchat.c
index 9e777107..a1c7773c 100644
--- a/gatchat/gatchat.c
+++ b/gatchat/gatchat.c
@@ -98,8 +98,6 @@  struct at_chat {
 	guint wakeup_timeout;			/* How long to wait for resp */
 	GTimer *wakeup_timer;			/* Keep track of elapsed time */
 	GAtSyntax *syntax;
-	gboolean destroyed;			/* Re-entrancy guard */
-	gboolean in_read_handler;		/* Re-entrancy guard */
 	gboolean in_notify;
 	GSList *terminator_list;		/* Non-standard terminator */
 	guint16 terminator_blacklist;		/* Blacklisted terinators */
@@ -727,17 +725,52 @@  static char *extract_line(struct at_chat *p, struct ring_buffer *rbuf)
 	return line;
 }
 
+static void at_chat_suspend(struct at_chat *chat)
+{
+	chat->suspended = TRUE;
+
+	g_at_io_set_write_handler(chat->io, NULL, NULL);
+	g_at_io_set_read_handler(chat->io, NULL, NULL);
+	g_at_io_set_debug(chat->io, NULL, NULL);
+}
+
+static struct at_chat *at_chat_ref(struct at_chat *chat)
+{
+	if (chat == NULL)
+		return NULL;
+
+	g_atomic_int_inc(&chat->ref_count);
+
+	return chat;
+}
+
+static void at_chat_unref(struct at_chat *chat)
+{
+	gboolean is_zero;
+
+	is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
+
+	if (is_zero == FALSE)
+		return;
+
+	if (chat->io) {
+		at_chat_suspend(chat);
+		g_at_io_unref(chat->io);
+		chat->io = NULL;
+		chat_cleanup(chat);
+	}
+
+	g_free(chat);
+}
+
 static void new_bytes(struct ring_buffer *rbuf, gpointer user_data)
 {
-	struct at_chat *p = user_data;
+	struct at_chat *p = at_chat_ref(user_data);
 	unsigned int len = ring_buffer_len(rbuf);
 	unsigned int wrap = ring_buffer_len_no_wrap(rbuf);
 	unsigned char *buf = ring_buffer_read_ptr(rbuf, p->read_so_far);
-
 	GAtSyntaxResult result;
 
-	p->in_read_handler = TRUE;
-
 	while (p->suspended == FALSE && (p->read_so_far < len)) {
 		gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
 		result = p->syntax->feed(p->syntax, (char *)buf, &rbytes);
@@ -778,10 +811,7 @@  static void new_bytes(struct ring_buffer *rbuf, gpointer user_data)
 		p->read_so_far = 0;
 	}
 
-	p->in_read_handler = FALSE;
-
-	if (p->destroyed)
-		g_free(p);
+	at_chat_unref(p);
 }
 
 static void wakeup_cb(gboolean ok, GAtResult *result, gpointer user_data)
@@ -931,15 +961,6 @@  static void chat_wakeup_writer(struct at_chat *chat)
 	g_at_io_set_write_handler(chat->io, can_write_data, chat);
 }
 
-static void at_chat_suspend(struct at_chat *chat)
-{
-	chat->suspended = TRUE;
-
-	g_at_io_set_write_handler(chat->io, NULL, NULL);
-	g_at_io_set_read_handler(chat->io, NULL, NULL);
-	g_at_io_set_debug(chat->io, NULL, NULL);
-}
-
 static void at_chat_resume(struct at_chat *chat)
 {
 	chat->suspended = FALSE;
@@ -958,28 +979,6 @@  static void at_chat_resume(struct at_chat *chat)
 		chat_wakeup_writer(chat);
 }
 
-static void at_chat_unref(struct at_chat *chat)
-{
-	gboolean is_zero;
-
-	is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
-
-	if (is_zero == FALSE)
-		return;
-
-	if (chat->io) {
-		at_chat_suspend(chat);
-		g_at_io_unref(chat->io);
-		chat->io = NULL;
-		chat_cleanup(chat);
-	}
-
-	if (chat->in_read_handler)
-		chat->destroyed = TRUE;
-	else
-		g_free(chat);
-}
-
 static gboolean at_chat_set_disconnect_function(struct at_chat *chat,
 						GAtDisconnectFunc disconnect,
 						gpointer user_data)
@@ -1372,10 +1371,9 @@  GAtChat *g_at_chat_clone(GAtChat *clone)
 	if (chat == NULL)
 		return NULL;
 
-	chat->parent = clone->parent;
+	chat->parent = at_chat_ref(clone->parent);
 	chat->group = chat->parent->next_gid++;
 	chat->ref_count = 1;
-	g_atomic_int_inc(&chat->parent->ref_count);
 
 	if (clone->slave != NULL)
 		chat->slave = g_at_chat_clone(clone->slave);