summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Drake <dan@reactivated.net>2010-07-29 12:18:45 +0100
committerDaniel Drake <dan@reactivated.net>2010-07-29 12:18:45 +0100
commit1867c7103b51e21432a11df8893050a4fe60ba42 (patch)
treeec331b4c8cea2c1e866e7d93d56d17d917c524c5
parent30b5fbb87c76dfc00041404d11003e2ed068384b (diff)
downloadlibusb-1867c7103b51e21432a11df8893050a4fe60ba42.tar.gz
Add reference counting to default contextpbr285
Michael Plante pointed out that if 2 users call libusb_init(NULL) within a process, we end up creating 2 default contexts, one of which is lost. Add reference counting so that the default context is reused and destroyed only after the last user.
-rw-r--r--libusb/core.c48
1 files changed, 38 insertions, 10 deletions
diff --git a/libusb/core.c b/libusb/core.c
index c39f33a..5c29eac 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -41,6 +41,7 @@ const struct usbi_os_backend * const usbi_backend = &windows_backend;
#endif
struct libusb_context *usbi_default_context = NULL;
+static int default_context_refcnt = 0;
static usbi_mutex_static_t default_context_lock = USBI_MUTEX_INITIALIZER;
/**
@@ -310,6 +311,14 @@ if (cfg != desired)
* is created the first time a process calls libusb_init() when no other
* context is alive. Contexts are destroyed during libusb_exit().
*
+ * The default context is reference-counted and can be shared. That means that
+ * if libusb_init(NULL) is called twice within the same process, the two
+ * users end up sharing the same context. The deinitialization and freeing of
+ * the default context will only happen when the last user calls libusb_exit()
+ * In other words, the default context is created and initialized when its
+ * reference count goes from 0 to 1, and is deinitialized and destroyed when
+ * its reference count goes from 1 to 0.
+ *
* You may be wondering why only a subset of libusb functions require a
* context pointer in their function definition. Internally, libusb stores
* context pointers in other objects (e.g. libusb_device instances) and hence
@@ -1476,9 +1485,15 @@ API_EXPORTED void LIBUSB_API libusb_set_debug(libusb_context *ctx, int level)
/** \ingroup lib
* Initialize libusb. This function must be called before calling any other
* libusb function.
+ *
+ * If you do not provide an output location for a context pointer, a default
+ * context will be created. If there was already a default context, it will
+ * be reused (and nothing will be initialized/reinitialized).
+ *
* \param context Optional output location for context pointer.
* Only valid on return code 0.
* \returns 0 on success, or a LIBUSB_ERROR code on failure
+ * \see contexts
*/
API_EXPORTED int LIBUSB_API libusb_init(libusb_context **context)
{
@@ -1488,13 +1503,17 @@ API_EXPORTED int LIBUSB_API libusb_init(libusb_context **context)
usbi_mutex_static_lock(&default_context_lock);
if (!context && usbi_default_context) {
+ r = 0;
+ usbi_dbg("reusing default context");
+ default_context_refcnt++;
usbi_mutex_static_unlock(&default_context_lock);
- return 0; /* using default; nothing to do. */
+ return 0;
}
+
ctx = malloc(sizeof(*ctx));
if (!ctx) {
- usbi_mutex_static_unlock(&default_context_lock);
- return LIBUSB_ERROR_NO_MEM;
+ r = LIBUSB_ERROR_NO_MEM;
+ goto err_unlock;
}
memset(ctx, 0, sizeof(*ctx));
#ifdef USBI_TIMERFD_AVAILABLE
@@ -1552,6 +1571,8 @@ err_destroy_mutex:
usbi_mutex_destroy(&ctx->usb_devs_lock);
err_free_ctx:
free(ctx);
+err_unlock:
+ usbi_mutex_static_unlock(&default_context_lock);
return r;
}
@@ -1565,6 +1586,20 @@ API_EXPORTED void LIBUSB_API libusb_exit(struct libusb_context *ctx)
USBI_GET_CONTEXT(ctx);
usbi_dbg("");
+ /* if working with default context, only actually do the deinitialization
+ * if we're the last user */
+ if (ctx == usbi_default_context) {
+ usbi_mutex_static_lock(&default_context_lock);
+ if (--default_context_refcnt > 0) {
+ usbi_dbg("not destroying default context");
+ usbi_mutex_static_unlock(&default_context_lock);
+ return;
+ }
+ usbi_dbg("destroying default context");
+ usbi_default_context = NULL;
+ usbi_mutex_static_unlock(&default_context_lock);
+ }
+
/* a little sanity check. doesn't bother with open_devs locking because
* unless there is an application bug, nobody will be accessing this. */
if (!list_empty(&ctx->open_devs))
@@ -1574,13 +1609,6 @@ API_EXPORTED void LIBUSB_API libusb_exit(struct libusb_context *ctx)
if (usbi_backend->exit)
usbi_backend->exit();
- usbi_mutex_static_lock(&default_context_lock);
- if (ctx == usbi_default_context) {
- usbi_dbg("freeing default context");
- usbi_default_context = NULL;
- }
- usbi_mutex_static_unlock(&default_context_lock);
-
usbi_mutex_destroy(&ctx->open_devs_lock);
usbi_mutex_destroy(&ctx->usb_devs_lock);
free(ctx);