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 951c097..fdad2e3 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 416016f..9e3a8d0 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 11ec439..147dadc 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 1650448..78ddb82 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 1b8d0be..2d332fe 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 822eb6b..74dfa5a 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 834ff1a..0d15ef2 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 ad9454d..e3ba149 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 ce19b8d..a9285f7 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 b2eb011..1322a2f 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 e151dd8..1cdf69a 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 315affc..ca61a2c 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 5b216a5..e72401f 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>