+/* Blocks until 'idl' successfully connects to the remote database and
+ * retrieves its contents. */
+void
+ovsdb_idl_get_initial_snapshot(struct ovsdb_idl *idl)
+{
+ while (1) {
+ ovsdb_idl_run(idl);
+ if (ovsdb_idl_has_ever_connected(idl)) {
+ return;
+ }
+ ovsdb_idl_wait(idl);
+ poll_block();
+ }
+}
+\f
+/* If 'lock_name' is nonnull, configures 'idl' to obtain the named lock from
+ * the database server and to avoid modifying the database when the lock cannot
+ * be acquired (that is, when another client has the same lock).
+ *
+ * If 'lock_name' is NULL, drops the locking requirement and releases the
+ * lock. */
+void
+ovsdb_idl_set_lock(struct ovsdb_idl *idl, const char *lock_name)
+{
+ ovs_assert(!idl->txn);
+ ovs_assert(hmap_is_empty(&idl->outstanding_txns));
+
+ if (idl->lock_name && (!lock_name || strcmp(lock_name, idl->lock_name))) {
+ /* Release previous lock. */
+ ovsdb_idl_send_unlock_request(idl);
+ free(idl->lock_name);
+ idl->lock_name = NULL;
+ idl->is_lock_contended = false;
+ }
+
+ if (lock_name && !idl->lock_name) {
+ /* Acquire new lock. */
+ idl->lock_name = xstrdup(lock_name);
+ ovsdb_idl_send_lock_request(idl);
+ }
+}
+
+/* Returns true if 'idl' is configured to obtain a lock and owns that lock.
+ *
+ * Locking and unlocking happens asynchronously from the database client's
+ * point of view, so the information is only useful for optimization (e.g. if
+ * the client doesn't have the lock then there's no point in trying to write to
+ * the database). */
+bool
+ovsdb_idl_has_lock(const struct ovsdb_idl *idl)
+{
+ return idl->has_lock;
+}
+
+/* Returns true if 'idl' is configured to obtain a lock but the database server
+ * has indicated that some other client already owns the requested lock. */
+bool
+ovsdb_idl_is_lock_contended(const struct ovsdb_idl *idl)
+{
+ return idl->is_lock_contended;
+}
+
+static void
+ovsdb_idl_update_has_lock(struct ovsdb_idl *idl, bool new_has_lock)
+{
+ if (new_has_lock && !idl->has_lock) {
+ if (idl->state == IDL_S_MONITORING ||
+ idl->state == IDL_S_MONITORING2) {
+ idl->change_seqno++;
+ } else {
+ /* We're setting up a session, so don't signal that the database
+ * changed. Finalizing the session will increment change_seqno
+ * anyhow. */
+ }
+ idl->is_lock_contended = false;
+ }
+ idl->has_lock = new_has_lock;
+}
+
+static void
+ovsdb_idl_send_lock_request__(struct ovsdb_idl *idl, const char *method,
+ struct json **idp)
+{
+ ovsdb_idl_update_has_lock(idl, false);
+
+ json_destroy(idl->lock_request_id);
+ idl->lock_request_id = NULL;
+
+ if (jsonrpc_session_is_connected(idl->session)) {
+ struct json *params;
+
+ params = json_array_create_1(json_string_create(idl->lock_name));
+ jsonrpc_session_send(idl->session,
+ jsonrpc_create_request(method, params, idp));
+ }
+}
+
+static void
+ovsdb_idl_send_lock_request(struct ovsdb_idl *idl)
+{
+ ovsdb_idl_send_lock_request__(idl, "lock", &idl->lock_request_id);
+}
+
+static void
+ovsdb_idl_send_unlock_request(struct ovsdb_idl *idl)
+{
+ ovsdb_idl_send_lock_request__(idl, "unlock", NULL);
+}
+
+static void
+ovsdb_idl_parse_lock_reply(struct ovsdb_idl *idl, const struct json *result)
+{
+ bool got_lock;
+
+ json_destroy(idl->lock_request_id);
+ idl->lock_request_id = NULL;
+
+ if (result->type == JSON_OBJECT) {
+ const struct json *locked;
+
+ locked = shash_find_data(json_object(result), "locked");
+ got_lock = locked && locked->type == JSON_TRUE;
+ } else {
+ got_lock = false;
+ }
+
+ ovsdb_idl_update_has_lock(idl, got_lock);
+ if (!got_lock) {
+ idl->is_lock_contended = true;
+ }
+}
+
+static void
+ovsdb_idl_parse_lock_notify(struct ovsdb_idl *idl,
+ const struct json *params,
+ bool new_has_lock)
+{
+ if (idl->lock_name
+ && params->type == JSON_ARRAY
+ && json_array(params)->n > 0
+ && json_array(params)->elems[0]->type == JSON_STRING) {
+ const char *lock_name = json_string(json_array(params)->elems[0]);
+
+ if (!strcmp(idl->lock_name, lock_name)) {
+ ovsdb_idl_update_has_lock(idl, new_has_lock);
+ if (!new_has_lock) {
+ idl->is_lock_contended = true;
+ }
+ }
+ }
+}
+
+void
+ovsdb_idl_loop_destroy(struct ovsdb_idl_loop *loop)
+{
+ if (loop) {
+ ovsdb_idl_destroy(loop->idl);
+ }
+}
+
+struct ovsdb_idl_txn *
+ovsdb_idl_loop_run(struct ovsdb_idl_loop *loop)
+{
+ ovsdb_idl_run(loop->idl);
+ loop->open_txn = (loop->committing_txn
+ || ovsdb_idl_get_seqno(loop->idl) == loop->skip_seqno
+ ? NULL
+ : ovsdb_idl_txn_create(loop->idl));
+ return loop->open_txn;
+}
+
+void
+ovsdb_idl_loop_commit_and_wait(struct ovsdb_idl_loop *loop)
+{
+ if (loop->open_txn) {
+ loop->committing_txn = loop->open_txn;
+ loop->open_txn = NULL;
+
+ loop->precommit_seqno = ovsdb_idl_get_seqno(loop->idl);
+ }
+
+ struct ovsdb_idl_txn *txn = loop->committing_txn;
+ if (txn) {
+ enum ovsdb_idl_txn_status status = ovsdb_idl_txn_commit(txn);
+ if (status != TXN_INCOMPLETE) {
+ switch (status) {
+ case TXN_TRY_AGAIN:
+ /* We want to re-evaluate the database when it's changed from
+ * the contents that it had when we started the commit. (That
+ * might have already happened.) */
+ loop->skip_seqno = loop->precommit_seqno;
+ if (ovsdb_idl_get_seqno(loop->idl) != loop->skip_seqno) {
+ poll_immediate_wake();
+ }
+ break;
+
+ case TXN_SUCCESS:
+ /* If the database has already changed since we started the
+ * commit, re-evaluate it immediately to avoid missing a change
+ * for a while. */
+ if (ovsdb_idl_get_seqno(loop->idl) != loop->precommit_seqno) {
+ poll_immediate_wake();
+ }
+ break;
+
+ case TXN_UNCHANGED:
+ case TXN_ABORTED:
+ case TXN_NOT_LOCKED:
+ case TXN_ERROR:
+ break;
+
+ case TXN_UNCOMMITTED:
+ case TXN_INCOMPLETE:
+ OVS_NOT_REACHED();
+
+ }
+ ovsdb_idl_txn_destroy(txn);
+ loop->committing_txn = NULL;
+ }
+ }
+
+ ovsdb_idl_wait(loop->idl);
+}