This is an automated email from the ASF dual-hosted git repository.

chug pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-dispatch.git


The following commit(s) were added to refs/heads/main by this push:
     new 269bfd4  DISPATCH-2194: Fix CONNECTOR - ENTITY_CACHE lock inversion
269bfd4 is described below

commit 269bfd46f3ac43ef6d1199f8cb271d117fd1ed9a
Author: Chuck Rolke <c...@apache.org>
AuthorDate: Fri Jul 9 16:26:59 2021 -0400

    DISPATCH-2194: Fix CONNECTOR - ENTITY_CACHE lock inversion
    
    The two locks are taken in both orders.
    
     * entity_cache first, connector second is routinely used by management
       entity updates in a pattern shared by all entities.
    
     * connector first, entity_cache second is used only when a connector
       creates or deletes an associated connection. The allocation and
       disposal of the connection causes an implicit entity_cache lock.
    
    The fix is to avoid the connector - entity_cache lock order by allocating
    the connection before taking the connector lock and by freeing the
    connection after releasing the connector lock.
    
    This patch also corrects an improper free call in a failure path.
---
 include/qpid/dispatch/server.h | 29 +++++++++++++++++++
 src/connection_manager.c       |  9 ++++--
 src/server.c                   | 66 +++++++++++++++++++++++++++++++++++-------
 3 files changed, 90 insertions(+), 14 deletions(-)

diff --git a/include/qpid/dispatch/server.h b/include/qpid/dispatch/server.h
index faf8366..7f71912 100644
--- a/include/qpid/dispatch/server.h
+++ b/include/qpid/dispatch/server.h
@@ -569,6 +569,35 @@ void qd_connection_invoke_deferred(qd_connection_t *conn, 
qd_deferred_t call, vo
 
 
 /**
+ * Schedule a call to be invoked on a thread that has ownership of this 
connection
+ * when it will be safe for the callback to perform operations related to this 
connection.
+ * A qd_deferred_call_t object has been allocated before hand to avoid taking
+ * the ENTITY_CACHE lock.
+ *
+ * @param conn Connection object
+ * @param call The function to be invoked on the connection's thread
+ * @param context The context to be passed back in the callback
+ * @param dct Pointer to preallocated qd_deferred_call_t object
+ */
+void qd_connection_invoke_deferred_impl(qd_connection_t *conn, qd_deferred_t 
call, void *context, void *dct);
+
+
+/**
+ * Allocate a qd_deferred_call_t object
+ */
+void *qd_connection_new_qd_deferred_call_t();
+
+
+/**
+ * Deallocate a qd_deferred_call_t object
+ *
+ * @param dct Pointer to preallocated qd_deferred_call_t object
+ */
+void qd_connection_free_qd_deferred_call_t(void *dct);
+
+
+
+/**
  * Listen for incoming connections, return true if listening succeeded.
  */
 bool qd_listener_listen(qd_listener_t *l);
diff --git a/src/connection_manager.c b/src/connection_manager.c
index 6ef14cd..c77999b 100644
--- a/src/connection_manager.c
+++ b/src/connection_manager.c
@@ -1081,16 +1081,19 @@ void 
qd_connection_manager_delete_connector(qd_dispatch_t *qd, void *impl)
         // cannot free the timer while holding ct->lock since the
         // timer callback may be running during the call to qd_timer_free
         qd_timer_t *timer = 0;
+        void *dct = qd_connection_new_qd_deferred_call_t();
         sys_mutex_lock(ct->lock);
         timer = ct->timer;
         ct->timer = 0;
         ct->state = CXTR_STATE_DELETED;
         qd_connection_t *conn = ct->qd_conn;
         if (conn && conn->pn_conn) {
-            qd_connection_invoke_deferred(conn, deferred_close, conn->pn_conn);
+            qd_connection_invoke_deferred_impl(conn, deferred_close, 
conn->pn_conn, dct);
+            sys_mutex_unlock(ct->lock);
+        } else {
+            sys_mutex_unlock(ct->lock);
+            qd_connection_free_qd_deferred_call_t(dct);
         }
-        sys_mutex_unlock(ct->lock);
-
         qd_timer_free(timer);
         DEQ_REMOVE(qd->connection_manager->connectors, ct);
         qd_connector_decref(ct);
diff --git a/src/server.c b/src/server.c
index 4910f37..24e73ee 100644
--- a/src/server.c
+++ b/src/server.c
@@ -561,11 +561,13 @@ static void connection_wake(qd_connection_t *ctx) {
     if (ctx->pn_conn) pn_connection_wake(ctx->pn_conn);
 }
 
-/* Construct a new qd_connection. Thread safe. */
-qd_connection_t *qd_server_connection(qd_server_t *server, qd_server_config_t 
*config)
+/* Construct a new qd_connection. Thread safe.
+ * Does not allocate any managed objects and therefore
+ * does not take ENTITY_CACHE lock.
+ */
+qd_connection_t *qd_server_connection_impl(qd_server_t *server, 
qd_server_config_t *config, qd_connection_t *ctx)
 {
-    qd_connection_t *ctx = new_qd_connection_t();
-    if (!ctx) return NULL;
+    assert(ctx);
     ZERO(ctx);
     ctx->pn_conn       = pn_connection();
     ctx->deferred_call_lock = sys_mutex();
@@ -574,7 +576,7 @@ qd_connection_t *qd_server_connection(qd_server_t *server, 
qd_server_config_t *c
         if (ctx->pn_conn) pn_connection_free(ctx->pn_conn);
         if (ctx->deferred_call_lock) sys_mutex_free(ctx->deferred_call_lock);
         free(ctx->role);
-        free(ctx);
+        free_qd_connection_t(ctx);
         return NULL;
     }
     ctx->server = server;
@@ -591,6 +593,16 @@ qd_connection_t *qd_server_connection(qd_server_t *server, 
qd_server_config_t *c
     return ctx;
 }
 
+/* Construct a new qd_connection. Thread safe.
+ * Allocates a qd_connection_t object and therefore
+ * takes ENTITY_CACHE lock.
+ */
+qd_connection_t *qd_server_connection(qd_server_t *server, qd_server_config_t 
*config)
+{
+    qd_connection_t *ctx = new_qd_connection_t();
+    if (!ctx) return NULL;
+    return qd_server_connection_impl(server, config, ctx);
+}
 
 static void on_accept(pn_event_t *e, qd_listener_t *listener)
 {
@@ -1156,11 +1168,10 @@ static qd_failover_item_t 
*qd_connector_get_conn_info(qd_connector_t *ct) {
 
 
 /* Timer callback to try/retry connection open */
-static void try_open_lh(qd_connector_t *connector)
+static void try_open_lh(qd_connector_t *connector, qd_connection_t *connection)
 {
-    // Allocate a new connection. No other thread will touch this
     // connection until pn_proactor_connect is called below
-    qd_connection_t *qd_conn = qd_server_connection(connector->server, 
&connector->config);
+    qd_connection_t *qd_conn = qd_server_connection_impl(connector->server, 
&connector->config, connection);
     if (!qd_conn) {                 /* Try again later */
         qd_log(connector->server->log_source, QD_LOG_CRITICAL, "Allocation 
failure connecting to %s",
                connector->config.host_port);
@@ -1323,12 +1334,24 @@ static void try_open_cb(void *context)
 {
     qd_connector_t *ct = (qd_connector_t*) context;
 
-    sys_mutex_lock(ct->lock);   /* TODO aconway 2017-05-09: this lock looks 
too big */
+    // Allocate connection before taking connector lock to avoid
+    // CONNECTOR - ENTITY_CACHE lock inversion deadlock window.
+    qd_connection_t *ctx = new_qd_connection_t();
+    if (!ctx) {
+        qd_log(ct->server->log_source, QD_LOG_CRITICAL, "Allocation failure 
connecting to %s",
+               ct->config.host_port);
+        ct->delay = 10000;
+        ct->state = CXTR_STATE_CONNECTING;
+        qd_timer_schedule(ct->timer, ct->delay);
+        return;
+    }
+
+    sys_mutex_lock(ct->lock);
 
     if (ct->state == CXTR_STATE_CONNECTING || ct->state == CXTR_STATE_INIT) {
         // else deleted or failed - on failed wait until after connection is 
freed
         // and state is set to CXTR_STATE_CONNECTING (timer is rescheduled 
then)
-        try_open_lh(ct);
+        try_open_lh(ct, ctx);
     }
 
     sys_mutex_unlock(ct->lock);
@@ -1584,7 +1607,16 @@ void qd_connection_invoke_deferred(qd_connection_t 
*conn, qd_deferred_t call, vo
     if (!conn)
         return;
 
-    qd_deferred_call_t *dc = new_qd_deferred_call_t();
+    qd_connection_invoke_deferred_impl(conn, call, context, 
new_qd_deferred_call_t());
+}
+
+
+void qd_connection_invoke_deferred_impl(qd_connection_t *conn, qd_deferred_t 
call, void *context, void *dct)
+{
+    if (!conn)
+        return;
+
+    qd_deferred_call_t *dc = (qd_deferred_call_t*)dct;
     DEQ_ITEM_INIT(dc);
     dc->call    = call;
     dc->context = context;
@@ -1599,6 +1631,18 @@ void qd_connection_invoke_deferred(qd_connection_t 
*conn, qd_deferred_t call, vo
 }
 
 
+void *qd_connection_new_qd_deferred_call_t()
+{
+    return new_qd_deferred_call_t();
+}
+
+
+void qd_connection_free_qd_deferred_call_t(void *dct)
+{
+    free_qd_deferred_call_t((qd_deferred_call_t *)dct);
+}
+
+
 qd_listener_t *qd_server_listener(qd_server_t *server)
 {
     qd_listener_t *li = new_qd_listener_t();

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org
For additional commands, e-mail: commits-h...@qpid.apache.org

Reply via email to