curl-library
[PATCH] pipelining: Add CURLMOPT_PIPELINE_POLICY_FUNCTION
Date: Fri, 31 Oct 2014 21:01:57 +0100
Also adds CURLMOPT_PIPELINE_POLICY_DATA, CURL_SUPPORTS_PIPELINING,
CURL_BLACKLISTED, struct curl_pipeline_policy
and curl_pipeline_policy_callback
This allows the user to tweak the maximum number of connections
per host, the maximum number of requests in a pipeline and
to override blacklisting or implement whitelisting on a per
host basis (since bundles are only per host, not host:port - which
imho is a limitation). Finally they can cause libcurl to start
doing pipelining immediately instead after the first reply
is received.
The callback is called when a bundle is constructed and when libcurl
detects (for the first time) that a bundle can do http pipelining.
Without this the user can't specify anything of the policy unless the
bundle is destructed (which normally doesn't happen).
---
docs/libcurl/symbols-in-versions | 5 +++
include/curl/multi.h | 63 +++++++++++++++++++++++++++++
lib/bundles.c | 3 +-
lib/bundles.h | 6 ++-
lib/conncache.c | 19 ++++++++-
lib/http.c | 16 +++++---
lib/multi.c | 8 ++++
lib/multihandle.h | 10 +++--
lib/pipeline.c | 85 ++++++++++++++++++++++++++++++++++++++++
lib/pipeline.h | 10 +++++
lib/url.c | 19 ++++-----
packages/OS400/curl.inc.in | 7 ++++
12 files changed, 226 insertions(+), 25 deletions(-)
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index 8e4ca9c..715cc80 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -280,6 +280,8 @@ CURLMOPT_MAXCONNECTS 7.16.3
CURLMOPT_MAX_HOST_CONNECTIONS 7.30.0
CURLMOPT_MAX_PIPELINE_LENGTH 7.30.0
CURLMOPT_MAX_TOTAL_CONNECTIONS 7.30.0
+CURLMOPT_PIPELINE_POLICY_DATA 7.40.0
+CURLMOPT_PIPELINE_POLICY_FUNCTION 7.40.0
CURLMOPT_PIPELINING 7.16.0
CURLMOPT_PIPELINING_SERVER_BL 7.30.0
CURLMOPT_PIPELINING_SITE_BL 7.30.0
@@ -627,6 +629,8 @@ CURLVERSION_FOURTH 7.16.1
CURLVERSION_NOW 7.10
CURLVERSION_SECOND 7.11.1
CURLVERSION_THIRD 7.12.0
+CURL_BLACKLISTED 7.40.0
+CURL_CAN_PIPELINE 7.40.0
CURL_CHUNK_BGN_FUNC_FAIL 7.21.0
CURL_CHUNK_BGN_FUNC_OK 7.21.0
CURL_CHUNK_BGN_FUNC_SKIP 7.21.0
@@ -722,6 +726,7 @@ CURL_SSLVERSION_TLSv1 7.9.2
CURL_SSLVERSION_TLSv1_0 7.34.0
CURL_SSLVERSION_TLSv1_1 7.34.0
CURL_SSLVERSION_TLSv1_2 7.34.0
+CURL_SUPPORTS_PIPELINING 7.40.0
CURL_TIMECOND_IFMODSINCE 7.9.7
CURL_TIMECOND_IFUNMODSINCE 7.9.7
CURL_TIMECOND_LASTMOD 7.9.7
diff --git a/include/curl/multi.h b/include/curl/multi.h
index 3c4acb0..9da0869 100644
--- a/include/curl/multi.h
+++ b/include/curl/multi.h
@@ -289,6 +289,63 @@ CURL_EXTERN CURLMcode curl_multi_socket_action(CURLM *multi_handle,
CURL_EXTERN CURLMcode curl_multi_socket_all(CURLM *multi_handle,
int *running_handles);
+/*
+ * Name: curl_pipeline_policy
+ *
+ * Desc: Contains the per site pipeline policy variables. The values default
+ * to CURLMOPT_MAX_HOST_CONNECTIONS, CURLMOPT_MAX_PIPELINE_LENGTH,
+ * and whether or not the site appears in CURLMOPT_PIPELINING_SITE_BL
+ * respectively. Initially CURL_SUPPORTS_PIPELINING is not set.
+ * However, these values can be overwritten by the user during the
+ * curl_pipeline_policy_callback set with
+ * CURLMOPT_PIPELINE_POLICY_FUNCTION. If CURL_SUPPORTS_PIPELINING and
+ * CURL_BLACKLISTED are both set then the site is blacklisted from
+ * pipelining.
+ */
+
+#define CURL_SUPPORTS_PIPELINING 1
+#define CURL_BLACKLISTED 2
+
+struct curl_pipeline_policy {
+ size_t max_host_connections; /* the maximum number of simultaneous
+ connections to the site */
+ long max_pipeline_length; /* the maximum amount of requests in
+ a pipelined connection */
+ int flags; /* bit-mask for CURL_SUPPORTS_PIPELINING
+ and CURL_BLACKLISTED */
+};
+
+/* Return TRUE iff bundle supports pipelining and is not blacklisted */
+#define CURL_CAN_PIPELINE(bundle) ((bundle->policy.flags & \
+ (CURL_SUPPORTS_PIPELINING|CURL_BLACKLISTED)) == CURL_SUPPORTS_PIPELINING)
+
+/*
+ * Name: curl_pipeline_policy_callback
+ *
+ * Desc: Called by libcurl whenever the library creates a new bundle for
+ * some hostname:port combination, or when the policy was changed
+ * due to pipelining capability detection or addition/removal
+ * of site blacklisting. The callback is not called when the
+ * policy is changed due to server version blacklisting.
+ * If the user resets CURL_SUPPORTS_PIPELINING and does not set
+ * CURL_BLACKLISTED then most likely the callback will happen
+ * again when the reply is received for the next request on
+ * the connection. If that is not desirable then just set
+ * CURL_BLACKLISTED instead.
+ * Note that if a connection is blacklisted due to
+ * CURLMOPT_PIPELINING_SERVER_BL then this callback is not
+ * called. This is to avoid repetive calling of the callback
+ * in the case the user would reset CURL_BLACKLISTED;
+ * CURLMOPT_PIPELINING_SERVER_BL overrules and cannot be tuned
+ * with this callback.
+ */
+typedef void (*curl_pipeline_policy_callback)(
+ char const *hostname, /* connected hostname */
+ int port, /* connected port */
+ struct curl_pipeline_policy* policy, /* writable policy */
+ void *userp); /* private callback
+ pointer */
+
#ifndef CURL_ALLOW_OLD_MULTI_SOCKET
/* This macro below was added in 7.16.3 to push users who recompile to use
the new curl_multi_socket_action() instead of the old curl_multi_socket()
@@ -365,6 +422,12 @@ typedef enum {
/* maximum number of open connections in total */
CINIT(MAX_TOTAL_CONNECTIONS, LONG, 13),
+ /* This is the pipeline policy callback function pointer */
+ CINIT(PIPELINE_POLICY_FUNCTION, FUNCTIONPOINT, 14),
+
+ /* This is the argument passed to the pipeline policy callback */
+ CINIT(PIPELINE_POLICY_DATA, OBJECTPOINT, 15),
+
CURLMOPT_LASTENTRY /* the last unused */
} CURLMoption;
diff --git a/lib/bundles.c b/lib/bundles.c
index aadf026..e46b878 100644
--- a/lib/bundles.c
+++ b/lib/bundles.c
@@ -46,6 +46,7 @@ static void conn_llist_dtor(void *user, void *element)
}
CURLcode Curl_bundle_create(struct SessionHandle *data,
+ struct curl_pipeline_policy *policy,
struct connectbundle **cb_ptr)
{
(void)data;
@@ -55,7 +56,7 @@ CURLcode Curl_bundle_create(struct SessionHandle *data,
return CURLE_OUT_OF_MEMORY;
(*cb_ptr)->num_connections = 0;
- (*cb_ptr)->server_supports_pipelining = FALSE;
+ (*cb_ptr)->policy = *policy;
(*cb_ptr)->conn_list = Curl_llist_alloc((curl_llist_dtor) conn_llist_dtor);
if(!(*cb_ptr)->conn_list) {
diff --git a/lib/bundles.h b/lib/bundles.h
index 3816c40..e418c2c 100644
--- a/lib/bundles.h
+++ b/lib/bundles.h
@@ -22,14 +22,16 @@
*
***************************************************************************/
+#include <curl/multi.h>
+
struct connectbundle {
- bool server_supports_pipelining; /* TRUE if server supports pipelining,
- set after first response */
+ struct curl_pipeline_policy policy; /* connect policy */
size_t num_connections; /* Number of connections in the bundle */
struct curl_llist *conn_list; /* The connectdata members of the bundle */
};
CURLcode Curl_bundle_create(struct SessionHandle *data,
+ struct curl_pipeline_policy *policy,
struct connectbundle **cb_ptr);
void Curl_bundle_destroy(struct connectbundle *cb_ptr);
diff --git a/lib/conncache.c b/lib/conncache.c
index fcfb150..c068bfc 100644
--- a/lib/conncache.c
+++ b/lib/conncache.c
@@ -33,6 +33,7 @@
#include "rawstr.h"
#include "bundles.h"
#include "conncache.h"
+#include "pipeline.h"
#include "curl_memory.h"
/* The last #include file should be: */
@@ -130,7 +131,23 @@ CURLcode Curl_conncache_add_conn(struct conncache *connc,
bundle = Curl_conncache_find_bundle(data->state.conn_cache,
conn->host.name);
if(!bundle) {
- result = Curl_bundle_create(data, &new_bundle);
+ struct Curl_multi *multi = data->multi;
+ struct curl_pipeline_policy policy = { 0, 0, 0 };
+ if(multi) {
+ /* policy default values */
+ policy.max_host_connections = Curl_multi_max_host_connections(multi);
+ policy.max_pipeline_length = Curl_multi_max_pipeline_length(multi);
+ if(Curl_pipeline_site_blacklisted(data, conn)) {
+ policy.flags = CURL_BLACKLISTED;
+ }
+ /* allow user to tweak the policy */
+ if(multi->pipeline_policy_cb)
+ multi->pipeline_policy_cb(conn->host.name,
+ conn->remote_port,
+ &policy,
+ multi->pipeline_policy_userp);
+ }
+ result = Curl_bundle_create(data, &policy, &new_bundle);
if(result)
return result;
diff --git a/lib/http.c b/lib/http.c
index b50d00f..48b2aa3 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -3320,9 +3320,10 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
"pipelining supported\n"));
/* Activate pipelining if needed */
cb_ptr = conn->bundle;
- if(cb_ptr) {
- if(!Curl_pipeline_site_blacklisted(data, conn))
- cb_ptr->server_supports_pipelining = TRUE;
+ if(cb_ptr && !(cb_ptr->policy.flags &
+ (CURL_SUPPORTS_PIPELINING|CURL_BLACKLISTED))) {
+ Curl_pipeline_set_policy_flags(data->multi, conn,
+ CURL_SUPPORTS_PIPELINING);
}
}
@@ -3403,10 +3404,13 @@ CURLcode Curl_http_readwrite_headers(struct SessionHandle *data,
else if(checkprefix("Server:", k->p)) {
char *server_name = Curl_copy_header_value(k->p);
- /* Turn off pipelining if the server version is blacklisted */
- if(conn->bundle && conn->bundle->server_supports_pipelining) {
+ /* Check if the server version is blacklisted when
+ we're doing pipelining */
+ if(conn->bundle && CURL_CAN_PIPELINE(conn->bundle)) {
if(Curl_pipeline_server_blacklisted(data, server_name))
- conn->bundle->server_supports_pipelining = FALSE;
+ /* Do not call Curl_pipeline_set_policy_flags:
+ we do not want the policy callback to happen. */
+ conn->bundle->policy.flags |= CURL_BLACKLISTED;
}
Curl_safefree(server_name);
}
diff --git a/lib/multi.c b/lib/multi.c
index 7ea366c..6ac49c4 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -2381,6 +2381,8 @@ CURLMcode curl_multi_setopt(CURLM *multi_handle,
case CURLMOPT_PIPELINING_SITE_BL:
res = Curl_pipeline_set_site_blacklist(va_arg(param, char **),
&multi->pipelining_site_bl);
+ if(!res)
+ Curl_pipeline_update_blacklist_policy(multi);
break;
case CURLMOPT_PIPELINING_SERVER_BL:
res = Curl_pipeline_set_server_blacklist(va_arg(param, char **),
@@ -2389,6 +2391,12 @@ CURLMcode curl_multi_setopt(CURLM *multi_handle,
case CURLMOPT_MAX_TOTAL_CONNECTIONS:
multi->max_total_connections = va_arg(param, long);
break;
+ case CURLMOPT_PIPELINE_POLICY_FUNCTION:
+ multi->pipeline_policy_cb = va_arg(param, curl_pipeline_policy_callback);
+ break;
+ case CURLMOPT_PIPELINE_POLICY_DATA:
+ multi->pipeline_policy_userp = va_arg(param, void *);
+ break;
default:
res = CURLM_UNKNOWN_OPTION;
break;
diff --git a/lib/multihandle.h b/lib/multihandle.h
index 1a4b1d9..a673f96 100644
--- a/lib/multihandle.h
+++ b/lib/multihandle.h
@@ -107,14 +107,18 @@ struct Curl_multi {
long maxconnects; /* if >0, a fixed limit of the maximum number of entries
we're allowed to grow the connection cache to */
- long max_host_connections; /* if >0, a fixed limit of the maximum number
+ long max_host_connections; /* if >0, the default limit of the maximum number
of connections per host */
long max_total_connections; /* if >0, a fixed limit of the maximum number
of connections in total */
- long max_pipeline_length; /* if >0, maximum number of requests in a
- pipeline */
+ /* callback function and user data pointer for the pipeline policy API */
+ curl_pipeline_policy_callback pipeline_policy_cb;
+ void *pipeline_policy_userp;
+
+ long max_pipeline_length; /* if >0, the default maximum number of requests
+ in a pipeline */
long content_length_penalty_size; /* a connection with a
content-length bigger than
diff --git a/lib/pipeline.c b/lib/pipeline.c
index 2645fdb..37f13aa 100644
--- a/lib/pipeline.c
+++ b/lib/pipeline.c
@@ -33,6 +33,7 @@
#include "sendf.h"
#include "rawstr.h"
#include "bundles.h"
+#include "conncache.h"
#include "curl_memory.h"
/* The last #include file should be: */
@@ -248,6 +249,90 @@ CURLMcode Curl_pipeline_set_site_blacklist(char **sites,
return CURLM_OK;
}
+void Curl_pipeline_set_policy_flags(struct Curl_multi *multi,
+ struct connectdata *conn,
+ int flags)
+{
+ struct connectbundle *cb_ptr = conn->bundle;
+ bool changed = (cb_ptr->policy.flags & flags) != flags;
+ cb_ptr->policy.flags |= flags;
+ /* If the policy was changed, call the policy callback */
+ if(changed && multi && multi->pipeline_policy_cb)
+ multi->pipeline_policy_cb(conn->host.name,
+ conn->remote_port,
+ &cb_ptr->policy,
+ multi->pipeline_policy_userp);
+}
+
+void Curl_pipeline_reset_policy_flags(struct Curl_multi *multi,
+ struct connectdata *conn,
+ int flags)
+{
+ struct connectbundle *cb_ptr = conn->bundle;
+ bool changed = (cb_ptr->policy.flags & flags) != 0;
+ cb_ptr->policy.flags &= ~flags;
+ /* If the policy was changed, call the policy callback */
+ if(changed && multi && multi->pipeline_policy_cb)
+ multi->pipeline_policy_cb(conn->host.name,
+ conn->remote_port,
+ &cb_ptr->policy,
+ multi->pipeline_policy_userp);
+}
+
+void Curl_pipeline_update_blacklist_policy(struct Curl_multi *multi)
+{
+ struct curl_llist *blacklist = Curl_multi_pipelining_site_bl(multi);
+ struct conncache *connc = multi->conn_cache;
+ struct curl_hash_iterator iter;
+ struct curl_hash_element *he;
+
+ /* Run over all bundles */
+ Curl_hash_start_iterate(connc->hash, &iter);
+ he = Curl_hash_next_element(&iter);
+ while(he) {
+ struct connectbundle *bundle = he->ptr;
+ bool blacklisted = FALSE;
+ he = Curl_hash_next_element(&iter);
+
+ /* Run over all connections (conn) in this bundle, until we
+ run out of connection or find one that is blacklisted. */
+ struct curl_llist_element *curr_conn = bundle->conn_list->head;
+ while(!blacklisted && curr_conn) {
+ struct connectdata *conn = curr_conn->ptr;
+ curr_conn = curr_conn->next;
+
+ if(blacklist) {
+ /* Run over all sites (site) in the blacklist */
+ struct curl_llist_element *curr = blacklist->head;
+ while(!blacklisted && curr) {
+ struct site_blacklist_entry *site = curr->ptr;
+ curr = curr->next;
+
+ if(site->port == conn->remote_port &&
+ Curl_raw_equal(site->hostname, conn->host.name)) {
+ /* Connection conn is blacklisted. Set flag. */
+ Curl_pipeline_set_policy_flags(multi, conn, CURL_BLACKLISTED);
+ /* Read back the flag because the policy callback might have
+ reset it again. */
+ blacklisted = (bundle->policy.flags & CURL_BLACKLISTED) != 0;
+ }
+ }
+ }
+
+ if(!blacklisted) {
+ /* None of the connections in the bundle are blacklisted.
+ Reset the flag. */
+ Curl_pipeline_reset_policy_flags(multi, conn, CURL_BLACKLISTED);
+ /* Read back the flag because the policy callback might have
+ set it again. */
+ blacklisted = (bundle->policy.flags & CURL_BLACKLISTED) != 0;
+ }
+
+ } /* Next conn in the bundle, unless the last one was blacklisted. */
+
+ } /* Next bundle */
+}
+
bool Curl_pipeline_server_blacklisted(struct SessionHandle *handle,
char *server_name)
{
diff --git a/lib/pipeline.h b/lib/pipeline.h
index 96c4c33..374370c 100644
--- a/lib/pipeline.h
+++ b/lib/pipeline.h
@@ -35,6 +35,16 @@ bool Curl_pipeline_site_blacklisted(struct SessionHandle *handle,
CURLMcode Curl_pipeline_set_site_blacklist(char **sites,
struct curl_llist **list_ptr);
+void Curl_pipeline_set_policy_flags(struct Curl_multi *multi,
+ struct connectdata *conn,
+ int flags);
+
+void Curl_pipeline_reset_policy_flags(struct Curl_multi *multi,
+ struct connectdata *conn,
+ int flags);
+
+void Curl_pipeline_update_blacklist_policy(struct Curl_multi *multi);
+
bool Curl_pipeline_server_blacklisted(struct SessionHandle *handle,
char *server_name);
diff --git a/lib/url.c b/lib/url.c
index 5b19f89..bb7ceba 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -3022,25 +3022,20 @@ ConnectionExists(struct SessionHandle *data,
*force_reuse = FALSE;
- /* We can't pipe if the site is blacklisted */
- if(canPipeline && Curl_pipeline_site_blacklisted(data, needle)) {
- canPipeline = FALSE;
- }
-
/* Look up the bundle with all the connections to this
particular host */
bundle = Curl_conncache_find_bundle(data->state.conn_cache,
needle->host.name);
if(bundle) {
- size_t max_pipe_len = Curl_multi_max_pipeline_length(data->multi);
+ size_t max_pipe_len = bundle->policy.max_pipeline_length;
size_t best_pipe_len = max_pipe_len;
struct curl_llist_element *curr;
infof(data, "Found bundle for host %s: %p\n",
needle->host.name, (void *)bundle);
- /* We can't pipe if we don't know anything about the server */
- if(canPipeline && !bundle->server_supports_pipelining) {
+ /* Check if we are pipelining */
+ if(canPipeline && !CURL_CAN_PIPELINE(bundle)) {
infof(data, "Server doesn't support pipelining\n");
canPipeline = FALSE;
}
@@ -5203,7 +5198,6 @@ static CURLcode create_conn(struct SessionHandle *data,
bool prot_missing = FALSE;
bool no_connections_available = FALSE;
bool force_reuse = FALSE;
- size_t max_host_connections = Curl_multi_max_host_connections(data->multi);
size_t max_total_connections = Curl_multi_max_total_connections(data->multi);
*async = FALSE;
@@ -5546,7 +5540,8 @@ static CURLcode create_conn(struct SessionHandle *data,
infof(data, "Found connection %ld, with requests in the pipe (%zu)\n",
conn_temp->connection_id, pipelen);
- if(conn_temp->bundle->num_connections < max_host_connections &&
+ if(conn_temp->bundle->num_connections <
+ conn_temp->bundle->policy.max_host_connections &&
data->state.conn_cache->num_connections < max_total_connections) {
/* We want a new connection anyway */
reuse = FALSE;
@@ -5585,8 +5580,8 @@ static CURLcode create_conn(struct SessionHandle *data,
bundle = Curl_conncache_find_bundle(data->state.conn_cache,
conn->host.name);
- if(max_host_connections > 0 && bundle &&
- (bundle->num_connections >= max_host_connections)) {
+ if(bundle && bundle->policy.max_host_connections > 0 &&
+ (bundle->num_connections >= bundle->policy.max_host_connections)) {
struct connectdata *conn_candidate;
/* The bundle is full. Let's see if we can kill a connection. */
diff --git a/packages/OS400/curl.inc.in b/packages/OS400/curl.inc.in
index 2f6d86a..69e07db 100644
--- a/packages/OS400/curl.inc.in
+++ b/packages/OS400/curl.inc.in
@@ -1553,6 +1553,10 @@
d c 10012
d CURLMOPT_MAX_TOTAL_CONNECTIONS...
d c 00013
+ d CURLMOPT_PIPELINING_POLICY_FUNCTION...
+ d c 20014
+ d CURLMOPT_PIPELINING_POLICY_DATA...
+ d c 10015
*
* Public API enums for RTSP requests.
*
@@ -1771,6 +1775,9 @@
d curl_socket_callback...
d s * based(######ptr######) procptr
*
+ d curl_pipeline_policy_callback...
+ d s * based(######ptr######) procptr
+ *
d curl_opensocket_callback...
d s * based(######ptr######) procptr
*
--
2.1.3
--MP_/qdCN/8xXo/QHx0iFEnBmCg_
Content-Type: text/plain; charset="utf-8"
MIME-Version: 1.0
Content-Transfer-Encoding: base64
Content-Disposition: inline
LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t
LS0tLS0tLS0tLQpMaXN0IGFkbWluOiBodHRwOi8vY29vbC5oYXh4LnNlL2xpc3QvbGlzdGluZm8v
Y3VybC1saWJyYXJ5CkV0aXF1ZXR0ZTogIGh0dHA6Ly9jdXJsLmhheHguc2UvbWFpbC9ldGlxdWV0
dGUuaHRtbA==
--MP_/qdCN/8xXo/QHx0iFEnBmCg_--
Received on 2001-09-17