This commit implements the curl handle pool. By default it can grow
to up to 4 curl handles, shared between all NBD connections. You can
change this using the new connections=N parameter.
---
plugins/curl/nbdkit-curl-plugin.pod | 7 +++
plugins/curl/curldefs.h | 5 +-
plugins/curl/curl.c | 11 ++++
plugins/curl/pool.c | 97 ++++++++++++++++++++++-------
4 files changed, 98 insertions(+), 22 deletions(-)
diff --git a/plugins/curl/nbdkit-curl-plugin.pod b/plugins/curl/nbdkit-curl-plugin.pod
index a396de027..f4cd07068 100644
--- a/plugins/curl/nbdkit-curl-plugin.pod
+++ b/plugins/curl/nbdkit-curl-plugin.pod
@@ -52,6 +52,13 @@ that libcurl is compiled with.
Set CA certificates directory location for libcurl. See
L<CURLOPT_CAPATH(3)> for more information.
+=item B<connections=>N
+
+Open up to C<N> curl connections to the web server. The default is 4.
+Curl connections are shared between all NBD clients, so you may wish
+to increase this if you expect many simultaneous clients (or a single
+client using many multi-conn connections).
+
=item B<cookie=>COOKIE
=item B<cookie=+>FILENAME
diff --git a/plugins/curl/curldefs.h b/plugins/curl/curldefs.h
index 533c28c52..d3c616f0e 100644
--- a/plugins/curl/curldefs.h
+++ b/plugins/curl/curldefs.h
@@ -50,6 +50,7 @@ extern const char *url;
extern const char *cainfo;
extern const char *capath;
+extern unsigned connections;
extern char *cookie;
extern const char *cookiefile;
extern const char *cookiejar;
@@ -84,7 +85,6 @@ extern int curl_debug_verbose;
/* The per-connection handle. */
struct handle {
int readonly;
- struct curl_handle *ch;
};
/* The libcurl handle and some associated fields and buffers. */
@@ -92,6 +92,9 @@ struct curl_handle {
/* The underlying curl handle. */
CURL *c;
+ /* True if the handle is in use by a thread. */
+ bool in_use;
+
/* These fields are used/initialized when we create the handle. */
bool accept_range;
int64_t exportsize;
diff --git a/plugins/curl/curl.c b/plugins/curl/curl.c
index 105de29c6..4634c7006 100644
--- a/plugins/curl/curl.c
+++ b/plugins/curl/curl.c
@@ -57,6 +57,7 @@ const char *url = NULL; /* required */
const char *cainfo = NULL;
const char *capath = NULL;
+unsigned connections = 4;
char *cookie = NULL;
const char *cookiefile = NULL;
const char *cookiejar = NULL;
@@ -206,6 +207,16 @@ curl_config (const char *key, const char *value)
capath = value;
}
+ else if (strcmp (key, "connections") == 0) {
+ if (nbdkit_parse_unsigned ("connections", value,
+ &connections) == -1)
+ return -1;
+ if (connections == 0) {
+ nbdkit_error ("connections parameter must not be 0");
+ return -1;
+ }
+ }
+
else if (strcmp (key, "cookie") == 0) {
free (cookie);
if (nbdkit_read_password (value, &cookie) == -1)
diff --git a/plugins/curl/pool.c b/plugins/curl/pool.c
index 00a7d381b..de02fea56 100644
--- a/plugins/curl/pool.c
+++ b/plugins/curl/pool.c
@@ -55,6 +55,7 @@
#include "ascii-ctype.h"
#include "ascii-string.h"
#include "cleanup.h"
+#include "vector.h"
#include "curldefs.h"
@@ -66,30 +67,41 @@
} while (0)
static struct curl_handle *allocate_handle (void);
+static void free_handle (struct curl_handle *);
static int debug_cb (CURL *handle, curl_infotype type,
const char *data, size_t size, void *);
static size_t header_cb (void *ptr, size_t size, size_t nmemb, void *opaque);
static size_t write_cb (char *ptr, size_t size, size_t nmemb, void *opaque);
static size_t read_cb (void *ptr, size_t size, size_t nmemb, void *opaque);
-/* In the current implementation there is only one handle. This lock
- * prevents it from being used multiple times.
- */
+/* This lock protects access to the curl_handles vector below. */
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
-/* The single curl handle. NULL means not yet allocated. */
-static struct curl_handle *the_ch;
+/* List of curl handles. This is allocated dynamically as more
+ * handles are requested. Currently it does not shrink. It may grow
+ * up to 'connections' in length.
+ */
+DEFINE_VECTOR_TYPE(curl_handle_list, struct curl_handle *)
+static curl_handle_list curl_handles = empty_vector;
+
+/* The condition is used when the curl handles vector is full and
+ * we're waiting for a thread to put_handle.
+ */
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+static size_t in_use = 0, waiting = 0;
/* Close and free all handles in the pool. */
void
free_all_handles (void)
{
- if (the_ch) {
- curl_easy_cleanup (the_ch->c);
- if (the_ch->headers_copy)
- curl_slist_free_all (the_ch->headers_copy);
- free (the_ch);
- }
+ size_t i;
+
+ nbdkit_debug ("free_all_handles: number of curl handles allocated: %zu",
+ curl_handles.len);
+
+ for (i = 0; i < curl_handles.len; ++i)
+ free_handle (curl_handles.ptr[i]);
+ curl_handle_list_reset (&curl_handles);
}
/* Get a handle from the pool.
@@ -99,25 +111,59 @@ free_all_handles (void)
struct curl_handle *
get_handle (void)
{
- int r;
-
- r = pthread_mutex_lock (&lock);
- assert (r == 0);
- if (!the_ch) {
- the_ch = allocate_handle ();
- if (!the_ch) {
- pthread_mutex_unlock (&lock);
+ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
+ size_t i;
+ struct curl_handle *ch;
+
+ again:
+ /* Look for a handle which is not in_use. */
+ for (i = 0; i < curl_handles.len; ++i) {
+ ch = curl_handles.ptr[i];
+ if (!ch->in_use) {
+ ch->in_use = true;
+ in_use++;
+ return ch;
+ }
+ }
+
+ /* If more connections are allowed, then allocate a new handle. */
+ if (curl_handles.len < connections) {
+ ch = allocate_handle ();
+ if (ch == NULL)
+ return NULL;
+ if (curl_handle_list_append (&curl_handles, ch) == -1) {
+ free_handle (ch);
return NULL;
}
+ ch->in_use = true;
+ in_use++;
+ return ch;
}
- return the_ch;
+
+ /* Otherwise we have run out of connections so we must wait until
+ * another thread calls put_handle.
+ */
+ assert (in_use == connections);
+ waiting++;
+ while (in_use == connections)
+ pthread_cond_wait (&cond, &lock);
+ waiting--;
+
+ goto again;
}
/* Return the handle to the pool. */
void
put_handle (struct curl_handle *ch)
{
- pthread_mutex_unlock (&lock);
+ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
+
+ ch->in_use = false;
+ in_use--;
+
+ /* Signal the next thread which is waiting. */
+ if (waiting > 0)
+ pthread_cond_signal (&cond);
}
/* Allocate and initialize a new libcurl handle. */
@@ -484,3 +530,12 @@ read_cb (void *ptr, size_t size, size_t nmemb, void *opaque)
return realsize;
}
+
+static void
+free_handle (struct curl_handle *ch)
+{
+ curl_easy_cleanup (ch->c);
+ if (ch->headers_copy)
+ curl_slist_free_all (ch->headers_copy);
+ free (ch);
+}
--
2.39.0