ovsdb: Implement a "lock" feature in the database protocol.
authorBen Pfaff <blp@nicira.com>
Tue, 26 Jul 2011 17:24:17 +0000 (10:24 -0700)
committerBen Pfaff <blp@nicira.com>
Tue, 26 Jul 2011 23:50:09 +0000 (16:50 -0700)
This provides clients a way to coordinate their access to the database.
This is a voluntary, not mandatory, locking protocols, that is, clients
are not prevented from modifying the database unless they cooperate with
the locking protocol.  It is also not related to any of the ACID properties
of database transactions.  It is strictly a way for clients to coordinate
among themselves.

The following commit will introduce one user.

13 files changed:
ovsdb/SPECS
ovsdb/execution.c
ovsdb/jsonrpc-server.c
ovsdb/jsonrpc-server.h
ovsdb/ovsdb-server.c
ovsdb/ovsdb-tool.c
ovsdb/ovsdb.h
ovsdb/server.c
ovsdb/server.h
ovsdb/trigger.c
tests/test-ovsdb.c
vswitchd/vswitch.ovsschema
vswitchd/vswitch.xml

index 951c0976e54d46265e1705d67d14bee812f2e67b..fdad2e37731eb60adb612bc56d138f653e17a3fa 100644 (file)
@@ -627,6 +627,107 @@ Cancels the ongoing table monitor request, identified by the
 ongoing "monitor" request.  No more "update" messages will be sent for
 this table monitor.
 
+lock operations
+...............
+
+Request object members:
+
+    "method": "lock", "steal", or "unlock"          required
+    "params": [<id>]                                required
+    "id": <nonnull-json-value>                      required
+
+Response object members:
+
+    "result": {"locked": <boolean>}     for "lock"
+    "result": {"locked": true}          for "steal"
+    "result": {}                        for "unlock"
+    "error": null
+    "id": same "id" as request
+
+Performs an operation on a "lock" object.  The database server
+supports an arbitrary number of locks, each of which is identified by
+a client-defined id (given in "params").  At any given time, each lock
+may have at most one owner.
+
+The locking operation depends on "method":
+
+    - "lock": The database will assign this client ownership of the
+      lock as soon as it becomes available.  When multiple clients
+      request the same lock, they will receive it in first-come, first
+      served order.
+
+    - "steal": The database immediately assigns this client ownership
+      of the lock.  If there is an existing owner, it loses ownership.
+
+    - "unlock": If the client owns the lock, releases it.  If the
+      client is waiting to obtain the lock, cancels the request and
+      stops waiting.
+
+      (Closing or otherwise disconnecting a database client connection
+      unlocks all of its locks.)
+
+For any given lock, the client must alternate "lock" or "steal"
+operations with "unlock" operations.  That is, if the previous
+operation on a lock was "lock" or "steal", it must be followed by an
+"unlock" operation, and vice versa.
+
+For a "lock" operation, the "locked" member in the response object is
+true if the lock has already been acquired, false if another client
+holds the lock and the client's request for it was queued.  In the
+latter case, the client will be notified later with a "locked" message
+when acquisition succeeds.
+
+These requests complete and send a response quickly, without waiting.
+The "locked" and "stolen" notifications (see below) report
+asynchronous changes to ownership.
+
+The scope of a lock is a database server, not a database hosted by
+that server.  A naming convention, such as "<db-name>:<lock-name>",
+can effectively limit the scope of a lock to a particular database.
+
+locked
+......
+
+Notification object members:
+
+    "method": "locked"
+    "params": [<id>]
+    "id": null
+
+Notifies the client that a "lock" operation that it previously
+requested has succeeded.  The client now owns the lock named in
+"params".
+
+The database server sends this notification after the reply to the
+corresponding "lock" request (but only if the "locked" member of the
+response was false), and before the reply to the client's subsequent
+"unlock" request.
+
+stolen
+......
+
+Notification object members:
+
+    "method": "locked"
+    "params": [<id>]
+    "id": null
+
+Notifies the client that owns a lock that another database client has
+stolen ownership of the lock.  The client no longer owns the lock
+named in "params".  The client must still issue an "unlock" request
+before performing any subsequent "lock" or "steal" operation on the
+lock.
+
+If the client originally obtained the lock through a "lock" request,
+then it will automatically regain the lock later after the client that
+stole it releases it.  (The database server will send the client a
+"locked" notification at that point to let it know.)
+
+If the client originally obtained the lock through a "steal" request,
+the database server won't automatically reassign it ownership of the
+lock when it later becomes available.  To regain ownership, the client
+must "unlock" and then "lock" or "steal" the lock again.
+
 echo
 ....
 
@@ -1192,3 +1293,26 @@ Semantics:
     Provides information to a database administrator on the purpose of
     a transaction.  The OVSDB server, for example, adds comments in
     transactions that modify the database to the database journal.
+
+assert
+......
+
+Request object members:
+
+    "op": "assert"                     required
+    "lock": <string>                   required
+
+Result object members:
+
+    none
+
+Semantics:
+
+    If the client does not own the lock named <string>, aborts the
+    transaction.
+
+Errors:
+
+    "error": "not owner"
+
+        The client does not own the named lock.
index 416016fd9ef3ff89758d6c87bd51653f6e54f51a..9e3a8d07b5d04252531d5fe6196f5ff66ce88fab 100644 (file)
 #include "ovsdb.h"
 #include "query.h"
 #include "row.h"
+#include "server.h"
 #include "table.h"
 #include "timeval.h"
 #include "transaction.h"
 
 struct ovsdb_execution {
     struct ovsdb *db;
+    const struct ovsdb_session *session;
     struct ovsdb_txn *txn;
     struct ovsdb_symbol_table *symtab;
     bool durable;
@@ -57,6 +59,7 @@ static ovsdb_operation_executor ovsdb_execute_wait;
 static ovsdb_operation_executor ovsdb_execute_commit;
 static ovsdb_operation_executor ovsdb_execute_abort;
 static ovsdb_operation_executor ovsdb_execute_comment;
+static ovsdb_operation_executor ovsdb_execute_assert;
 
 static ovsdb_operation_executor *
 lookup_executor(const char *name)
@@ -76,6 +79,7 @@ lookup_executor(const char *name)
         { "commit", ovsdb_execute_commit },
         { "abort", ovsdb_execute_abort },
         { "comment", ovsdb_execute_comment },
+        { "assert", ovsdb_execute_assert },
     };
 
     size_t i;
@@ -90,7 +94,8 @@ lookup_executor(const char *name)
 }
 
 struct json *
-ovsdb_execute(struct ovsdb *db, const struct json *params,
+ovsdb_execute(struct ovsdb *db, const struct ovsdb_session *session,
+              const struct json *params,
               long long int elapsed_msec, long long int *timeout_msec)
 {
     struct ovsdb_execution x;
@@ -116,6 +121,7 @@ ovsdb_execute(struct ovsdb *db, const struct json *params,
     }
 
     x.db = db;
+    x.session = session;
     x.txn = ovsdb_txn_create(db);
     x.symtab = ovsdb_symbol_table_create();
     x.durable = false;
@@ -706,3 +712,28 @@ ovsdb_execute_comment(struct ovsdb_execution *x, struct ovsdb_parser *parser,
 
     return NULL;
 }
+
+static struct ovsdb_error *
+ovsdb_execute_assert(struct ovsdb_execution *x, struct ovsdb_parser *parser,
+                     struct json *result OVS_UNUSED)
+{
+    const struct json *lock_name;
+
+    lock_name = ovsdb_parser_member(parser, "lock", OP_STRING);
+    if (!lock_name) {
+        return NULL;
+    }
+
+    if (x->session) {
+        const struct ovsdb_lock_waiter *waiter;
+
+        waiter = ovsdb_session_get_lock_waiter(x->session,
+                                               json_string(lock_name));
+        if (waiter && ovsdb_lock_waiter_is_owner(waiter)) {
+            return NULL;
+        }
+    }
+
+    return ovsdb_error("not owner", "Asserted lock %s not held.",
+                       json_string(lock_name));
+}
index 11ec439d10063644f0da4a7a3d4eec044843a1e6..147dadc7edbd14d33389cd1e3ff11ba45da1eeab 100644 (file)
@@ -58,6 +58,8 @@ static void ovsdb_jsonrpc_session_set_all_options(
 static bool ovsdb_jsonrpc_session_get_status(
     const struct ovsdb_jsonrpc_remote *,
     struct ovsdb_jsonrpc_remote_status *);
+static void ovsdb_jsonrpc_session_unlock_all(struct ovsdb_jsonrpc_session *);
+static void ovsdb_jsonrpc_session_unlock__(struct ovsdb_lock_waiter *);
 
 /* Triggers. */
 static void ovsdb_jsonrpc_trigger_create(struct ovsdb_jsonrpc_session *,
@@ -220,6 +222,15 @@ ovsdb_jsonrpc_server_get_remote_status(
     return remote && ovsdb_jsonrpc_session_get_status(remote, status);
 }
 
+void
+ovsdb_jsonrpc_server_free_remote_status(
+    struct ovsdb_jsonrpc_remote_status *status)
+{
+    free(status->locks_held);
+    free(status->locks_waiting);
+    free(status->locks_lost);
+}
+
 /* Forces all of the JSON-RPC sessions managed by 'svr' to disconnect and
  * reconnect. */
 void
@@ -330,8 +341,10 @@ static void
 ovsdb_jsonrpc_session_close(struct ovsdb_jsonrpc_session *s)
 {
     ovsdb_jsonrpc_monitor_remove_all(s);
+    ovsdb_jsonrpc_session_unlock_all(s);
     jsonrpc_session_close(s->js);
     list_remove(&s->node);
+    ovsdb_session_destroy(&s->up);
     s->remote->server->n_sessions--;
     ovsdb_session_destroy(&s->up);
     free(s);
@@ -345,6 +358,7 @@ ovsdb_jsonrpc_session_run(struct ovsdb_jsonrpc_session *s)
         s->js_seqno = jsonrpc_session_get_seqno(s->js);
         ovsdb_jsonrpc_trigger_complete_all(s);
         ovsdb_jsonrpc_monitor_remove_all(s);
+        ovsdb_jsonrpc_session_unlock_all(s);
     }
 
     ovsdb_jsonrpc_trigger_complete_done(s);
@@ -453,7 +467,9 @@ ovsdb_jsonrpc_session_get_status(const struct ovsdb_jsonrpc_remote *remote,
 {
     const struct ovsdb_jsonrpc_session *s;
     const struct jsonrpc_session *js;
+    struct ovsdb_lock_waiter *waiter;
     struct reconnect_stats rstats;
+    struct ds locks_held, locks_waiting, locks_lost;
 
     if (list_is_empty(&remote->sessions)) {
         return false;
@@ -471,6 +487,24 @@ ovsdb_jsonrpc_session_get_status(const struct ovsdb_jsonrpc_remote *remote,
     status->sec_since_disconnect = rstats.msec_since_disconnect == UINT_MAX
         ? UINT_MAX : rstats.msec_since_disconnect / 1000;
 
+    ds_init(&locks_held);
+    ds_init(&locks_waiting);
+    ds_init(&locks_lost);
+    HMAP_FOR_EACH (waiter, session_node, &s->up.waiters) {
+        struct ds *string;
+
+        string = (ovsdb_lock_waiter_is_owner(waiter) ? &locks_held
+                  : waiter->mode == OVSDB_LOCK_WAIT ? &locks_waiting
+                  : &locks_lost);
+        if (string->length) {
+            ds_put_char(string, ' ');
+        }
+        ds_put_cstr(string, waiter->lock_name);
+    }
+    status->locks_held = ds_steal_cstr(&locks_held);
+    status->locks_waiting = ds_steal_cstr(&locks_waiting);
+    status->locks_lost = ds_steal_cstr(&locks_lost);
+
     status->n_connections = list_size(&remote->sessions);
 
     return true;
@@ -518,6 +552,144 @@ error:
     return reply;
 }
 
+static struct ovsdb_error *
+ovsdb_jsonrpc_session_parse_lock_name(const struct jsonrpc_msg *request,
+                                      const char **lock_namep)
+{
+    const struct json_array *params;
+
+    params = json_array(request->params);
+    if (params->n != 1 || params->elems[0]->type != JSON_STRING ||
+        !ovsdb_parser_is_id(json_string(params->elems[0]))) {
+        *lock_namep = NULL;
+        return ovsdb_syntax_error(request->params, NULL,
+                                  "%s request params must be <id>",
+                                  request->method);
+    }
+
+    *lock_namep = json_string(params->elems[0]);
+    return NULL;
+}
+
+static void
+ovsdb_jsonrpc_session_notify(struct ovsdb_session *session,
+                             const char *lock_name,
+                             const char *method)
+{
+    struct ovsdb_jsonrpc_session *s;
+    struct json *params;
+
+    s = CONTAINER_OF(session, struct ovsdb_jsonrpc_session, up);
+    params = json_array_create_1(json_string_create(lock_name));
+    jsonrpc_session_send(s->js, jsonrpc_create_notify(method, params));
+}
+
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_session_lock(struct ovsdb_jsonrpc_session *s,
+                           struct jsonrpc_msg *request,
+                           enum ovsdb_lock_mode mode)
+{
+    struct ovsdb_lock_waiter *waiter;
+    struct jsonrpc_msg *reply;
+    struct ovsdb_error *error;
+    struct ovsdb_session *victim;
+    const char *lock_name;
+    struct json *result;
+
+    error = ovsdb_jsonrpc_session_parse_lock_name(request, &lock_name);
+    if (error) {
+        goto error;
+    }
+
+    /* Report error if this session has issued a "lock" or "steal" without a
+     * matching "unlock" for this lock. */
+    waiter = ovsdb_session_get_lock_waiter(&s->up, lock_name);
+    if (waiter) {
+        error = ovsdb_syntax_error(
+            request->params, NULL,
+            "must issue \"unlock\" before new \"%s\"", request->method);
+        goto error;
+    }
+
+    /* Get the lock, add us as a waiter. */
+    waiter = ovsdb_server_lock(&s->remote->server->up, &s->up, lock_name, mode,
+                               &victim);
+    if (victim) {
+        ovsdb_jsonrpc_session_notify(victim, lock_name, "stolen");
+    }
+
+    result = json_object_create();
+    json_object_put(result, "locked",
+                    json_boolean_create(ovsdb_lock_waiter_is_owner(waiter)));
+
+    return jsonrpc_create_reply(result, request->id);
+
+error:
+    reply = jsonrpc_create_reply(ovsdb_error_to_json(error), request->id);
+    ovsdb_error_destroy(error);
+    return reply;
+}
+
+static void
+ovsdb_jsonrpc_session_unlock_all(struct ovsdb_jsonrpc_session *s)
+{
+    struct ovsdb_lock_waiter *waiter, *next;
+
+    HMAP_FOR_EACH_SAFE (waiter, next, session_node, &s->up.waiters) {
+        ovsdb_jsonrpc_session_unlock__(waiter);
+    }
+}
+
+static void
+ovsdb_jsonrpc_session_unlock__(struct ovsdb_lock_waiter *waiter)
+{
+    struct ovsdb_lock *lock = waiter->lock;
+
+    if (lock) {
+        struct ovsdb_session *new_owner = ovsdb_lock_waiter_remove(waiter);
+        if (new_owner) {
+            ovsdb_jsonrpc_session_notify(new_owner, lock->name, "locked");
+        } else {
+            /* ovsdb_server_lock() might have freed 'lock'. */
+        }
+    }
+
+    ovsdb_lock_waiter_destroy(waiter);
+}
+
+static struct jsonrpc_msg *
+ovsdb_jsonrpc_session_unlock(struct ovsdb_jsonrpc_session *s,
+                             struct jsonrpc_msg *request)
+{
+    struct ovsdb_lock_waiter *waiter;
+    struct jsonrpc_msg *reply;
+    struct ovsdb_error *error;
+    const char *lock_name;
+
+    error = ovsdb_jsonrpc_session_parse_lock_name(request, &lock_name);
+    if (error) {
+        goto error;
+    }
+
+    /* Report error if this session has not issued a "lock" or "steal" for this
+     * lock. */
+    waiter = ovsdb_session_get_lock_waiter(&s->up, lock_name);
+    if (!waiter) {
+        error = ovsdb_syntax_error(
+            request->params, NULL, "\"unlock\" without \"lock\" or \"steal\"");
+        goto error;
+    }
+
+    ovsdb_jsonrpc_session_unlock__(waiter);
+
+    return jsonrpc_create_reply(json_object_create(), request->id);
+
+error:
+    reply = jsonrpc_create_reply(ovsdb_error_to_json(error), request->id);
+    ovsdb_error_destroy(error);
+    return reply;
+}
+
 static struct jsonrpc_msg *
 execute_transaction(struct ovsdb_jsonrpc_session *s,
                     struct jsonrpc_msg *request)
@@ -560,6 +732,12 @@ ovsdb_jsonrpc_session_got_request(struct ovsdb_jsonrpc_session *s,
         reply = jsonrpc_create_reply(
             json_array_create_1(json_string_create(get_db_name(s))),
             request->id);
+    } else if (!strcmp(request->method, "lock")) {
+        reply = ovsdb_jsonrpc_session_lock(s, request, OVSDB_LOCK_WAIT);
+    } else if (!strcmp(request->method, "steal")) {
+        reply = ovsdb_jsonrpc_session_lock(s, request, OVSDB_LOCK_STEAL);
+    } else if (!strcmp(request->method, "unlock")) {
+        reply = ovsdb_jsonrpc_session_unlock(s, request);
     } else if (!strcmp(request->method, "echo")) {
         reply = jsonrpc_create_reply(json_clone(request->params), request->id);
     } else {
index 16504483d794b2ef9fb14d79cb65953162cf2e6b..78ddb82dcd3b8ab397e250adc77edf19ceaa51ae 100644 (file)
@@ -41,11 +41,16 @@ struct ovsdb_jsonrpc_remote_status {
     unsigned int sec_since_connect;
     unsigned int sec_since_disconnect;
     bool is_connected;
+    char *locks_held;
+    char *locks_waiting;
+    char *locks_lost;
     int n_connections;
 };
 bool ovsdb_jsonrpc_server_get_remote_status(
     const struct ovsdb_jsonrpc_server *, const char *target,
     struct ovsdb_jsonrpc_remote_status *);
+void ovsdb_jsonrpc_server_free_remote_status(
+    struct ovsdb_jsonrpc_remote_status *);
 
 void ovsdb_jsonrpc_server_reconnect(struct ovsdb_jsonrpc_server *);
 
index 1b8d0be09cd6eb71ab72eb6284869254767cf1f2..2d332fec0703a26a69dfec7535f633fa43be74e3 100644 (file)
@@ -15,8 +15,7 @@
 
 #include <config.h>
 
-#include "ovsdb.h"
-
+#include <assert.h>
 #include <errno.h>
 #include <getopt.h>
 #include <signal.h>
 #include "daemon.h"
 #include "dirs.h"
 #include "file.h"
+#include "hash.h"
 #include "json.h"
 #include "jsonrpc.h"
 #include "jsonrpc-server.h"
 #include "leak-checker.h"
 #include "list.h"
+#include "ovsdb.h"
 #include "ovsdb-data.h"
 #include "ovsdb-types.h"
 #include "ovsdb-error.h"
@@ -499,11 +500,25 @@ update_remote_row(const struct ovsdb_row *row, struct ovsdb_txn *txn,
         values[n++] =
             xstrdup(ovs_retval_to_string(status.last_error));
     }
+    if (status.locks_held && status.locks_held[0]) {
+        keys[n] = xstrdup("locks_held");
+        values[n++] = xstrdup(status.locks_held);
+    }
+    if (status.locks_waiting && status.locks_waiting[0]) {
+        keys[n] = xstrdup("locks_waiting");
+        values[n++] = xstrdup(status.locks_waiting);
+    }
+    if (status.locks_lost && status.locks_lost[0]) {
+        keys[n] = xstrdup("locks_lost");
+        values[n++] = xstrdup(status.locks_lost);
+    }
     if (status.n_connections > 1) {
         keys[n] = xstrdup("n_connections");
         values[n++] = xasprintf("%d", status.n_connections);
     }
     write_string_string_column(rw_row, "status", keys, values, n);
+
+    ovsdb_jsonrpc_server_free_remote_status(&status);
 }
 
 static void
index 822eb6b2edc68e6b5abcd9a0d63cf0531e27baba..74dfa5a5b99ffa84e5df63edccbe475ebc38fe42 100644 (file)
@@ -311,7 +311,7 @@ transact(bool read_only, const char *db_file_name, const char *transaction)
     check_ovsdb_error(ovsdb_file_open(db_file_name, read_only, &db, NULL));
 
     request = parse_json(transaction);
-    result = ovsdb_execute(db, request, 0, NULL);
+    result = ovsdb_execute(db, NULL, request, 0, NULL);
     json_destroy(request);
 
     print_and_free_json(result);
index 834ff1a994af37ba12757e64b74ad9f5d6ec0f0c..0d15ef2db2488c8dd8146b4bfb8f0621f8eb74ad 100644 (file)
@@ -23,6 +23,7 @@
 
 struct json;
 struct ovsdb_log;
+struct ovsdb_session;
 struct ovsdb_txn;
 struct uuid;
 
@@ -71,7 +72,8 @@ struct json *ovsdb_to_json(const struct ovsdb *);
 
 struct ovsdb_table *ovsdb_get_table(const struct ovsdb *, const char *);
 
-struct json *ovsdb_execute(struct ovsdb *, const struct json *params,
+struct json *ovsdb_execute(struct ovsdb *, const struct ovsdb_session *,
+                           const struct json *params,
                            long long int elapsed_msec,
                            long long int *timeout_msec);
 \f
index ad9454dfbaaca9c102c1539e127c2afe4441b4bf..e3ba1498ef3fa1988f5165ea5723dccf300b0619 100644 (file)
 
 #include "server.h"
 
+#include <assert.h>
+
+#include "hash.h"
+
 /* Initializes 'session' as a session that operates on 'db'. */
 void
 ovsdb_session_init(struct ovsdb_session *session, struct ovsdb *db)
 {
     session->db = db;
     list_init(&session->completions);
+    hmap_init(&session->waiters);
 }
 
 /* Destroys 'session'. */
 void
-ovsdb_session_destroy(struct ovsdb_session *session OVS_UNUSED)
+ovsdb_session_destroy(struct ovsdb_session *session)
+{
+    assert(hmap_is_empty(&session->waiters));
+    hmap_destroy(&session->waiters);
+}
+
+/* Searches 'session' for an ovsdb_lock_waiter named 'lock_name' and returns
+ * it if it finds one, otherwise NULL. */
+struct ovsdb_lock_waiter *
+ovsdb_session_get_lock_waiter(const struct ovsdb_session *session,
+                              const char *lock_name)
+{
+    struct ovsdb_lock_waiter *waiter;
+
+    HMAP_FOR_EACH_WITH_HASH (waiter, session_node, hash_string(lock_name, 0),
+                             &session->waiters) {
+        if (!strcmp(lock_name, waiter->lock_name)) {
+            return waiter;
+        }
+    }
+    return NULL;
+}
+
+/* Returns the waiter that owns 'lock'.
+ *
+ * A lock always has an owner, so this function will never return NULL. */
+struct ovsdb_lock_waiter *
+ovsdb_lock_get_owner(const struct ovsdb_lock *lock)
+{
+    return CONTAINER_OF(list_front(&lock->waiters),
+                        struct ovsdb_lock_waiter, lock_node);
+}
+
+/* Removes 'waiter' from its lock's list.  This means that, if 'waiter' was
+ * formerly the owner of its lock, then it no longer owns it.
+ *
+ * Returns the session that now owns 'waiter'.  This is NULL if 'waiter' was
+ * the lock's owner and no other sessions were waiting for the lock.  In this
+ * case, the lock has been destroyed, so the caller must be sure not to refer
+ * to it again.  A nonnull return value reflects a change in the lock's
+ * ownership if and only if 'waiter' formerly owned the lock. */
+struct ovsdb_session *
+ovsdb_lock_waiter_remove(struct ovsdb_lock_waiter *waiter)
+{
+    struct ovsdb_lock *lock = waiter->lock;
+
+    list_remove(&waiter->lock_node);
+    waiter->lock = NULL;
+
+    if (list_is_empty(&lock->waiters)) {
+        hmap_remove(&lock->server->locks, &lock->hmap_node);
+        free(lock->name);
+        free(lock);
+        return NULL;
+    }
+
+    return ovsdb_lock_get_owner(lock)->session;
+}
+
+/* Destroys 'waiter', which must have already been removed from its lock's
+ * waiting list with ovsdb_lock_waiter_remove().
+ *
+ * Removing and destroying locks are decoupled because a lock initially created
+ * by the "steal" request, that is later stolen by another client, remains in
+ * the database session until the database client sends an "unlock" request. */
+void
+ovsdb_lock_waiter_destroy(struct ovsdb_lock_waiter *waiter)
+{
+    assert(!waiter->lock);
+    hmap_remove(&waiter->session->waiters, &waiter->session_node);
+    free(waiter->lock_name);
+    free(waiter);
+}
+
+/* Returns true if 'waiter' owns its associated lock. */
+bool
+ovsdb_lock_waiter_is_owner(const struct ovsdb_lock_waiter *waiter)
 {
+    return waiter->lock && waiter == ovsdb_lock_get_owner(waiter->lock);
 }
 
 /* Initializes 'server' as a server that operates on 'db'. */
@@ -36,10 +118,80 @@ void
 ovsdb_server_init(struct ovsdb_server *server, struct ovsdb *db)
 {
     server->db = db;
+    hmap_init(&server->locks);
 }
 
 /* Destroys 'server'. */
 void
-ovsdb_server_destroy(struct ovsdb_server *server OVS_UNUSED)
+ovsdb_server_destroy(struct ovsdb_server *server)
+{
+    hmap_destroy(&server->locks);
+}
+
+static struct ovsdb_lock *
+ovsdb_server_create_lock__(struct ovsdb_server *server, const char *lock_name,
+                           uint32_t hash)
+{
+    struct ovsdb_lock *lock;
+
+    HMAP_FOR_EACH_WITH_HASH (lock, hmap_node, hash, &server->locks) {
+        if (!strcmp(lock->name, lock_name)) {
+            return lock;
+        }
+    }
+
+    lock = xzalloc(sizeof *lock);
+    lock->server = server;
+    lock->name = xstrdup(lock_name);
+    hmap_insert(&server->locks, &lock->hmap_node, hash);
+    list_init(&lock->waiters);
+
+    return lock;
+}
+
+/* Attempts to acquire the lock named 'lock_name' for 'session' within
+ * 'server'.  Returns the new lock waiter.
+ *
+ * If 'mode' is OVSDB_LOCK_STEAL, then the new lock waiter is always the owner
+ * of the lock.  '*victimp' receives the session of the previous owner or NULL
+ * if the lock was previously unowned.  (If the victim itself originally
+ * obtained the lock through a "steal" operation, then this function also
+ * removes the victim from the lock's waiting list.)
+ *
+ * If 'mode' is OVSDB_LOCK_WAIT, then the new lock waiter is the owner of the
+ * lock only if this lock had no existing owner.  '*victimp' is set to NULL. */
+struct ovsdb_lock_waiter *
+ovsdb_server_lock(struct ovsdb_server *server,
+                  struct ovsdb_session *session,
+                  const char *lock_name,
+                  enum ovsdb_lock_mode mode,
+                  struct ovsdb_session **victimp)
 {
+    uint32_t hash = hash_string(lock_name, 0);
+    struct ovsdb_lock_waiter *waiter, *victim;
+    struct ovsdb_lock *lock;
+
+    lock = ovsdb_server_create_lock__(server, lock_name, hash);
+    victim = (mode == OVSDB_LOCK_STEAL && !list_is_empty(&lock->waiters)
+              ? ovsdb_lock_get_owner(lock)
+              : NULL);
+
+    waiter = xmalloc(sizeof *waiter);
+    waiter->mode = mode;
+    waiter->lock_name = xstrdup(lock_name);
+    waiter->lock = lock;
+    if (mode == OVSDB_LOCK_STEAL) {
+        list_push_front(&lock->waiters, &waiter->lock_node);
+    } else {
+        list_push_back(&lock->waiters, &waiter->lock_node);
+    }
+    waiter->session = session;
+    hmap_insert(&waiter->session->waiters, &waiter->session_node, hash);
+
+    if (victim && victim->mode == OVSDB_LOCK_STEAL) {
+        ovsdb_lock_waiter_remove(victim);
+    }
+    *victimp = victim ? victim->session : NULL;
+
+    return waiter;
 }
index ce19b8d09e431f7593f0c6c9f4336153845ee5a9..a9285f79ee08e6b25be9d6bb0f8ebf2149639b74 100644 (file)
 struct ovsdb_session {
     struct ovsdb *db;
     struct list completions;    /* Completed triggers. */
+    struct hmap waiters;        /* "ovsdb_lock_waiter *"s by lock name. */
 };
 
 void ovsdb_session_init(struct ovsdb_session *, struct ovsdb *);
 void ovsdb_session_destroy(struct ovsdb_session *);
 
+struct ovsdb_lock_waiter *ovsdb_session_get_lock_waiter(
+    const struct ovsdb_session *, const char *lock_name);
+
+/* A database lock.
+ *
+ * A lock always has one or more "lock waiters" kept on a list.  The waiter at
+ * the head of the list owns the lock. */
+struct ovsdb_lock {
+    struct ovsdb_server *server; /* The containing server. */
+    char *name;                  /* Unique name. */
+    struct hmap_node hmap_node;  /* In ovsdb_server's "locks" hmap. */
+    struct list waiters;         /* Contains "struct ovsdb_lock_waiter"s. */
+};
+
+struct ovsdb_lock_waiter *ovsdb_lock_get_owner(const struct ovsdb_lock *);
+
+/* How to obtain a lock. */
+enum ovsdb_lock_mode {
+    OVSDB_LOCK_WAIT,            /* By waiting for it to become available. */
+    OVSDB_LOCK_STEAL            /* By stealing it from the owner. */
+};
+
+/* A session's request for a database lock. */
+struct ovsdb_lock_waiter {
+    enum ovsdb_lock_mode mode;
+    char *lock_name;
+
+    struct ovsdb_lock *lock;    /* The lock being waited for. */
+    struct list lock_node;      /* In ->lock->waiters's list. */
+
+    struct ovsdb_session *session;
+    struct hmap_node session_node; /* In ->session->locks's hmap. */
+};
+
+struct ovsdb_session *ovsdb_lock_waiter_remove(struct ovsdb_lock_waiter *);
+void ovsdb_lock_waiter_destroy(struct ovsdb_lock_waiter *);
+bool ovsdb_lock_waiter_is_owner(const struct ovsdb_lock_waiter *);
+
 /* Abstract representation of an OVSDB server not tied to any particular
  * network protocol.  Protocol implementations (e.g. jsonrpc-server.c) embed
  * this in a larger data structure.  */
 struct ovsdb_server {
     struct ovsdb *db;
+    struct hmap locks;     /* Contains "struct ovsdb_lock"s indexed by name. */
 };
 
 void ovsdb_server_init(struct ovsdb_server *, struct ovsdb *);
 void ovsdb_server_destroy(struct ovsdb_server *);
 
+struct ovsdb_lock_waiter *ovsdb_server_lock(struct ovsdb_server *,
+                                            struct ovsdb_session *,
+                                            const char *lock_name,
+                                            enum ovsdb_lock_mode,
+                                            struct ovsdb_session **victimp);
+
 #endif /* ovsdb/server.h */
index b2eb0116fff49ed6bc9d30a5c3f19d28218fc998..1322a2fd4ef9ec12a3992426d373d0fc606d2598 100644 (file)
@@ -110,8 +110,8 @@ ovsdb_trigger_wait(struct ovsdb *db, long long int now)
 static bool
 ovsdb_trigger_try(struct ovsdb_trigger *t, long long int now)
 {
-    t->result = ovsdb_execute(t->session->db, t->request,
-                              now - t->created, &t->timeout_msec);
+    t->result = ovsdb_execute(t->session->db, t->session,
+                              t->request, now - t->created, &t->timeout_msec);
     if (t->result) {
         ovsdb_trigger_complete(t);
         return true;
index e151dd8bcad848063b39b5da1f1e76c5d3f9c0f9..1cdf69ae88667cf9702cbfc8e96a50e3af83738d 100644 (file)
@@ -1258,7 +1258,7 @@ do_execute(int argc OVS_UNUSED, char *argv[])
         char *s;
 
         params = parse_json(argv[i]);
-        result = ovsdb_execute(db, params, 0, NULL);
+        result = ovsdb_execute(db, NULL, params, 0, NULL);
         s = json_to_string(result, JSSF_SORT);
         printf("%s\n", s);
         free(s);
index 315affcb84837fa052d7565e5f77e0f4ab532c43..ca61a2cd66cb994bbcc4435a9327b057ad4acd84 100644 (file)
@@ -1,6 +1,6 @@
 {"name": "Open_vSwitch",
- "version": "5.1.0",
- "cksum": "154459795 14545",
+ "version": "5.2.0",
+ "cksum": "434778864 14545",
  "tables": {
    "Open_vSwitch": {
      "columns": {
index 5b216a556b7732fe7607d484e5e13658b52a27a5..e72401fdcc4c2636700c572e07f97cb69c3c222b 100644 (file)
             database (in seconds). Value is empty if manager has never
             disconnected.</dd>
         </dl>
+        <dl>
+          <dt><code>locks_held</code></dt>
+          <dt><code>locks_waiting</code></dt>
+          <dt><code>locks_lost</code></dt>
+          <dd>
+            Space-separated lists of the names of OVSDB locks that the
+            connection holds, is currently waiting to acquire, or has had
+            stolen by another OVSDB client, respectively.  Key-value pairs for
+            lists that would be empty are omitted.
+          </dd>
+        </dl>
         <dl>
           <dt><code>n_connections</code></dt>
           <dd>