Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optionally use GUsbSource on Linux to avoid threading issues #83

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 69 additions & 7 deletions gusb/gusb-context.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@

#include "gusb-context-private.h"
#include "gusb-device-private.h"
#include "gusb-source-private.h"
#include "gusb-util.h"

enum { PROP_0, PROP_LIBUSB_CONTEXT, PROP_DEBUG_LEVEL, N_PROPERTIES };
enum { PROP_0, PROP_LIBUSB_CONTEXT, PROP_DEBUG_LEVEL, PROP_FLAGS, N_PROPERTIES };

enum { DEVICE_ADDED_SIGNAL, DEVICE_REMOVED_SIGNAL, LAST_SIGNAL };

Expand All @@ -43,6 +44,8 @@ typedef struct {
GThread *thread_event;
gboolean done_enumerate;
volatile gint thread_event_run;
GMutex source_mutex;
GUsbSource *source;
guint hotplug_poll_id;
guint hotplug_poll_interval;
int debug_level;
Expand Down Expand Up @@ -119,7 +122,7 @@ g_usb_context_dispose(GObject *object)
GUsbContextPrivate *priv = GET_PRIVATE(self);

/* this is safe to call even when priv->hotplug_id is unset */
if (g_atomic_int_dec_and_test(&priv->thread_event_run)) {
if (priv->thread_event != NULL && g_atomic_int_dec_and_test(&priv->thread_event_run)) {
libusb_hotplug_deregister_callback(priv->ctx, priv->hotplug_id);
g_thread_join(priv->thread_event);
}
Expand All @@ -132,6 +135,10 @@ g_usb_context_dispose(GObject *object)
g_source_remove(priv->idle_events_id);
priv->idle_events_id = 0;
}
if (priv->source != NULL) {
_g_usb_source_destroy(priv->source);
priv->source = NULL;
}

g_clear_pointer(&priv->main_ctx, g_main_context_unref);
g_clear_pointer(&priv->devices, g_ptr_array_unref);
Expand Down Expand Up @@ -166,6 +173,9 @@ g_usb_context_get_property(GObject *object, guint prop_id, GValue *value, GParam
case PROP_DEBUG_LEVEL:
g_value_set_int(value, priv->debug_level);
break;
case PROP_FLAGS:
g_value_set_uint64(value, priv->flags);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
Expand All @@ -187,6 +197,9 @@ g_usb_context_set_property(GObject *object, guint prop_id, const GValue *value,
libusb_set_debug(priv->ctx, priv->debug_level);
#endif
break;
case PROP_FLAGS:
g_usb_context_set_flags(self, g_value_get_uint64(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
Expand Down Expand Up @@ -215,6 +228,12 @@ g_usb_context_class_init(GUsbContextClass *klass)
pspecs[PROP_DEBUG_LEVEL] =
g_param_spec_int("debug_level", NULL, NULL, 0, 3, 0, G_PARAM_READWRITE);

/**
* GUsbContext:flags:
*/
pspecs[PROP_FLAGS] =
g_param_spec_uint64("flags", NULL, NULL, 0, G_MAXUINT64, 0, G_PARAM_READWRITE);

g_object_class_install_properties(object_class, N_PROPERTIES, pspecs);

/**
Expand Down Expand Up @@ -767,6 +786,9 @@ g_usb_context_enumerate(GUsbContext *self)
g_ptr_array_index(priv->devices, i));
}

/* setup with the default mainloop if not already done */
g_usb_context_get_source(self, NULL);

/* any queued up hotplug events are queued as idle handlers */
}

Expand All @@ -784,7 +806,10 @@ void
g_usb_context_set_flags(GUsbContext *self, GUsbContextFlags flags)
{
GUsbContextPrivate *priv = GET_PRIVATE(self);
if (priv->flags == flags)
return;
priv->flags = flags;
g_object_notify_by_pspec(G_OBJECT(self), pspecs[PROP_FLAGS]);
}

/**
Expand Down Expand Up @@ -831,6 +856,7 @@ g_usb_context_init(GUsbContext *self)
priv->devices_removed = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
priv->dict_usb_ids = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
priv->dict_replug = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
g_mutex_init(&priv->source_mutex);

/* to escape the thread into the mainloop */
g_rec_mutex_init(&priv->idle_events_mutex);
Expand Down Expand Up @@ -859,8 +885,13 @@ g_usb_context_initable_init(GInitable *initable, GCancellable *cancellable, GErr

priv->main_ctx = g_main_context_ref(g_main_context_default());
priv->ctx = ctx;
priv->thread_event_run = 1;
priv->thread_event = g_thread_new("GUsbEventThread", g_usb_context_event_thread_cb, self);

/* FreeBSD cannot use libusb_set_pollfd_notifiers(), so use a thread instead */
if (priv->flags & G_USB_CONTEXT_FLAGS_USE_HOTPLUG_THREAD) {
priv->thread_event_run = 1;
priv->thread_event =
g_thread_new("GUsbEventThread", g_usb_context_event_thread_cb, self);
}

/* watch for add/remove */
if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) {
Expand Down Expand Up @@ -910,7 +941,11 @@ _g_usb_context_get_context(GUsbContext *self)
* @self: a #GUsbContext
* @main_ctx: a #GMainContext, or %NULL
*
* This function does nothing.
* Returns a source for this context. The first call actually creates the source and the result
* is returned in all future calls, unless threading is being used.
*
* If the platform does not support libusb_set_pollfd_notifiers() then a thread is being used,
* and this function returns %NULL.
*
* Return value: (transfer none): the #GUsbSource.
*
Expand All @@ -919,7 +954,16 @@ _g_usb_context_get_context(GUsbContext *self)
GUsbSource *
g_usb_context_get_source(GUsbContext *self, GMainContext *main_ctx)
{
return NULL;
GUsbContextPrivate *priv = GET_PRIVATE(self);
g_autoptr(GMutexLocker) locker = g_mutex_locker_new(&priv->source_mutex);

g_assert(locker != NULL);

if (priv->thread_event != NULL)
return NULL;
if (priv->source == NULL)
priv->source = _g_usb_source_new(main_ctx, self);
return priv->source;
}

/**
Expand Down Expand Up @@ -1314,5 +1358,23 @@ g_usb_context_wait_for_replug(GUsbContext *self,
GUsbContext *
g_usb_context_new(GError **error)
{
return g_initable_new(G_USB_TYPE_CONTEXT, NULL, error, NULL);
return g_usb_context_new_full(G_USB_CONTEXT_FLAGS_USE_HOTPLUG_THREAD, NULL, error);
}

/**
* g_usb_context_new_full:
* @flags: a #GUsbContextFlags, e.g. %G_USB_CONTEXT_FLAGS_SAVE_EVENTS
* @cancellable: a #GCancellable, or %NULL
* @error: a #GError, or %NULL
*
* Creates a new context for accessing USB devices.
*
* Return value: a new %GUsbContext object or %NULL on error.
*
* Since: 0.4.2
**/
GUsbContext *
g_usb_context_new_full(GUsbContextFlags flags, GCancellable *cancellable, GError **error)
{
return g_initable_new(G_USB_TYPE_CONTEXT, cancellable, error, "flags", flags, NULL);
}
4 changes: 3 additions & 1 deletion gusb/gusb-context.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ typedef enum {
G_USB_CONTEXT_FLAGS_NONE = 0,
G_USB_CONTEXT_FLAGS_AUTO_OPEN_DEVICES = 1 << 0,
G_USB_CONTEXT_FLAGS_SAVE_EVENTS = 1 << 1,
G_USB_CONTEXT_FLAGS_USE_HOTPLUG_THREAD = 1 << 2,
/*< private >*/
G_USB_CONTEXT_FLAGS_LAST
} GUsbContextFlags;
Expand All @@ -50,13 +51,14 @@ g_usb_context_error_quark(void);

GUsbContext *
g_usb_context_new(GError **error);
GUsbContext *
g_usb_context_new_full(GUsbContextFlags flags, GCancellable *cancellable, GError **error);

void
g_usb_context_set_flags(GUsbContext *self, GUsbContextFlags flags);
GUsbContextFlags
g_usb_context_get_flags(GUsbContext *self);

G_DEPRECATED
GUsbSource *
g_usb_context_get_source(GUsbContext *self, GMainContext *main_ctx);
GMainContext *
Expand Down
9 changes: 9 additions & 0 deletions gusb/gusb-device.c
Original file line number Diff line number Diff line change
Expand Up @@ -2164,6 +2164,9 @@ g_usb_device_control_transfer_async(GUsbDevice *self,
req,
NULL);
}

/* setup with the default mainloop */
g_usb_context_get_source(priv->context, NULL);
}

/**
Expand Down Expand Up @@ -2340,6 +2343,9 @@ g_usb_device_bulk_transfer_async(GUsbDevice *self,
req,
NULL);
}

/* setup with the default mainloop */
g_usb_context_get_source(priv->context, NULL);
}

/**
Expand Down Expand Up @@ -2516,6 +2522,9 @@ g_usb_device_interrupt_transfer_async(GUsbDevice *self,
req,
NULL);
}

/* setup with the default mainloop */
g_usb_context_get_source(priv->context, NULL);
}

/**
Expand Down
20 changes: 20 additions & 0 deletions gusb/gusb-source-private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
*
* Copyright (C) 2010 Richard Hughes <[email protected]>
* Copyright (C) 2011 Debarshi Ray <[email protected]>
*
* SPDX-License-Identifier: LGPL-2.1+
*/

#pragma once

#include <glib.h>

G_BEGIN_DECLS

GUsbSource *
_g_usb_source_new(GMainContext *main_ctx, GUsbContext *context);
void
_g_usb_source_destroy(GUsbSource *source);

G_END_DECLS
Loading