lib: Utilize smaps in the idl.
[sliver-openvswitch.git] / lib / ovsdb-idl.c
index 439a40a..3eca2fe 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (c) 2009, 2010, 2011 Nicira Networks.
+/* Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -94,9 +94,9 @@ struct ovsdb_idl_txn {
     unsigned int commit_seqno;
 
     /* Increments. */
-    char *inc_table;
-    char *inc_column;
-    struct json *inc_where;
+    const char *inc_table;
+    const char *inc_column;
+    struct uuid inc_row;
     unsigned int inc_index;
     int64_t inc_new_value;
 
@@ -270,32 +270,12 @@ ovsdb_idl_clear(struct ovsdb_idl *idl)
     }
 }
 
-/* Processes a batch of messages from the database server on 'idl'.  Returns
- * true if the database as seen through 'idl' changed, false if it did not
- * change.  The initial fetch of the entire contents of the remote database is
- * considered to be one kind of change.  If 'idl' has been configured to
- * acquire a database lock (with ovsdb_idl_set_lock()), then successfully
- * acquiring the lock is also considered to be a change.
- *
- * When this function returns false, the client may continue to use any data
- * structures it obtained from 'idl' in the past.  But when it returns true,
- * the client must not access any of these data structures again, because they
- * could have freed or reused for other purposes.
- *
- * This function can return occasional false positives, that is, report that
- * the database changed even though it didn't.  This happens if the connection
- * to the database drops and reconnects, which causes the database contents to
- * be reloaded even if they didn't change.  (It could also happen if the
- * database server sends out a "change" that reflects what we already thought
- * was in the database, but the database server is not supposed to do that.)
- *
- * As an alternative to checking the return value, the client may check for
- * changes in the value returned by ovsdb_idl_get_seqno().
- */
-bool
+/* Processes a batch of messages from the database server on 'idl'.  This may
+ * cause the IDL's contents to change.  The client may check for that with
+ * ovsdb_idl_get_seqno(). */
+void
 ovsdb_idl_run(struct ovsdb_idl *idl)
 {
-    unsigned int initial_change_seqno = idl->change_seqno;
     int i;
 
     assert(!idl->txn);
@@ -366,8 +346,6 @@ ovsdb_idl_run(struct ovsdb_idl *idl)
         }
         jsonrpc_msg_destroy(msg);
     }
-
-    return initial_change_seqno != idl->change_seqno;
 }
 
 /* Arranges for poll_block() to wake up when ovsdb_idl_run() has something to
@@ -379,8 +357,23 @@ ovsdb_idl_wait(struct ovsdb_idl *idl)
     jsonrpc_session_recv_wait(idl->session);
 }
 
-/* Returns a number that represents the state of 'idl'.  When 'idl' is updated
- * (by ovsdb_idl_run()), the return value changes. */
+/* Returns a "sequence number" that represents the state of 'idl'.  When
+ * ovsdb_idl_run() changes the database, the sequence number changes.  The
+ * initial fetch of the entire contents of the remote database is considered to
+ * be one kind of change.  Successfully acquiring a lock, if one has been
+ * configured with ovsdb_idl_set_lock(), is also considered to be a change.
+ *
+ * As long as the sequence number does not change, the client may continue to
+ * use any data structures it obtains from 'idl'.  But when it changes, the
+ * client must not access any of these data structures again, because they
+ * could have freed or reused for other purposes.
+ *
+ * The sequence number can occasionally change even if the database does not.
+ * This happens if the connection to the database drops and reconnects, which
+ * causes the database contents to be reloaded even if they didn't change.  (It
+ * could also happen if the database server sends out a "change" that reflects
+ * what the IDL already thought was in the database.  The database server is
+ * not supposed to do that, but bugs could in theory cause it to do so.) */
 unsigned int
 ovsdb_idl_get_seqno(const struct ovsdb_idl *idl)
 {
@@ -913,6 +906,7 @@ static struct ovsdb_idl_row *
 ovsdb_idl_row_create__(const struct ovsdb_idl_table_class *class)
 {
     struct ovsdb_idl_row *row = xzalloc(class->allocation_size);
+    class->row_init(row);
     list_init(&row->src_arcs);
     list_init(&row->dst_arcs);
     hmap_node_nullify(&row->txn_node);
@@ -1014,6 +1008,7 @@ ovsdb_idl_table_from_class(const struct ovsdb_idl *idl,
     return &idl->tables[table_class - idl->class->tables];
 }
 
+/* Called by ovsdb-idlc generated code. */
 struct ovsdb_idl_row *
 ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src,
                       struct ovsdb_idl_table_class *dst_table_class,
@@ -1058,6 +1053,8 @@ ovsdb_idl_get_row_arc(struct ovsdb_idl_row *src,
     }
 }
 
+/* Searches 'tc''s table in 'idl' for a row with UUID 'uuid'.  Returns a
+ * pointer to the row if there is one, otherwise a null pointer.  */
 const struct ovsdb_idl_row *
 ovsdb_idl_get_row_for_uuid(const struct ovsdb_idl *idl,
                            const struct ovsdb_idl_table_class *tc,
@@ -1080,6 +1077,12 @@ next_real_row(struct ovsdb_idl_table *table, struct hmap_node *node)
     return NULL;
 }
 
+/* Returns a row in 'table_class''s table in 'idl', or a null pointer if that
+ * table is empty.
+ *
+ * Database tables are internally maintained as hash tables, so adding or
+ * removing rows while traversing the same table can cause some rows to be
+ * visited twice or not at apply. */
 const struct ovsdb_idl_row *
 ovsdb_idl_first_row(const struct ovsdb_idl *idl,
                     const struct ovsdb_idl_table_class *table_class)
@@ -1089,6 +1092,8 @@ ovsdb_idl_first_row(const struct ovsdb_idl *idl,
     return next_real_row(table, hmap_first(&table->rows));
 }
 
+/* Returns a row following 'row' within its table, or a null pointer if 'row'
+ * is the last row in its table. */
 const struct ovsdb_idl_row *
 ovsdb_idl_next_row(const struct ovsdb_idl_row *row)
 {
@@ -1112,8 +1117,13 @@ const struct ovsdb_datum *
 ovsdb_idl_read(const struct ovsdb_idl_row *row,
                const struct ovsdb_idl_column *column)
 {
-    const struct ovsdb_idl_table_class *class = row->table->class;
-    size_t column_idx = column - class->columns;
+    const struct ovsdb_idl_table_class *class;
+    size_t column_idx;
+
+    assert(!ovsdb_idl_row_is_synthetic(row));
+
+    class = row->table->class;
+    column_idx = column - class->columns;
 
     assert(row->new != NULL);
     assert(column_idx < class->n_columns);
@@ -1160,6 +1170,11 @@ ovsdb_idl_row_is_synthetic(const struct ovsdb_idl_row *row)
 static void ovsdb_idl_txn_complete(struct ovsdb_idl_txn *txn,
                                    enum ovsdb_idl_txn_status);
 
+/* Returns a string representation of 'status'.  The caller must not modify or
+ * free the returned string.
+ *
+ * The return value is probably useful only for debug log messages and unit
+ * tests. */
 const char *
 ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status status)
 {
@@ -1174,10 +1189,8 @@ ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status status)
         return "aborted";
     case TXN_SUCCESS:
         return "success";
-    case TXN_AGAIN_WAIT:
-        return "wait then try again";
-    case TXN_AGAIN_NOW:
-        return "try again now";
+    case TXN_TRY_AGAIN:
+        return "try again";
     case TXN_NOT_LOCKED:
         return "not locked";
     case TXN_ERROR:
@@ -1186,6 +1199,9 @@ ovsdb_idl_txn_status_to_string(enum ovsdb_idl_txn_status status)
     return "<unknown>";
 }
 
+/* Starts a new transaction on 'idl'.  A given ovsdb_idl may only have a single
+ * active transaction at a time.  See the large comment in ovsdb-idl.h for
+ * general information on transactions. */
 struct ovsdb_idl_txn *
 ovsdb_idl_txn_create(struct ovsdb_idl *idl)
 {
@@ -1204,7 +1220,6 @@ ovsdb_idl_txn_create(struct ovsdb_idl *idl)
 
     txn->inc_table = NULL;
     txn->inc_column = NULL;
-    txn->inc_where = NULL;
 
     hmap_init(&txn->inserted_rows);
 
@@ -1229,22 +1244,50 @@ ovsdb_idl_txn_add_comment(struct ovsdb_idl_txn *txn, const char *s, ...)
     va_end(args);
 }
 
+/* Marks 'txn' as a transaction that will not actually modify the database.  In
+ * almost every way, the transaction is treated like other transactions.  It
+ * must be committed or aborted like other transactions, it will be sent to the
+ * database server like other transactions, and so on.  The only difference is
+ * that the operations sent to the database server will include, as the last
+ * step, an "abort" operation, so that any changes made by the transaction will
+ * not actually take effect. */
 void
 ovsdb_idl_txn_set_dry_run(struct ovsdb_idl_txn *txn)
 {
     txn->dry_run = true;
 }
 
+/* Causes 'txn', when committed, to increment the value of 'column' within
+ * 'row' by 1.  'column' must have an integer type.  After 'txn' commits
+ * successfully, the client may retrieve the final (incremented) value of
+ * 'column' with ovsdb_idl_txn_get_increment_new_value().
+ *
+ * The client could accomplish something similar with ovsdb_idl_read(),
+ * ovsdb_idl_txn_verify() and ovsdb_idl_txn_write(), or with ovsdb-idlc
+ * generated wrappers for these functions.  However, ovsdb_idl_txn_increment()
+ * will never (by itself) fail because of a verify error.
+ *
+ * The intended use is for incrementing the "next_cfg" column in the
+ * Open_vSwitch table. */
 void
-ovsdb_idl_txn_increment(struct ovsdb_idl_txn *txn, const char *table,
-                        const char *column, const struct json *where)
+ovsdb_idl_txn_increment(struct ovsdb_idl_txn *txn,
+                        const struct ovsdb_idl_row *row,
+                        const struct ovsdb_idl_column *column)
 {
     assert(!txn->inc_table);
-    txn->inc_table = xstrdup(table);
-    txn->inc_column = xstrdup(column);
-    txn->inc_where = where ? json_clone(where) : json_array_create_empty();
+    assert(column->type.key.type == OVSDB_TYPE_INTEGER);
+    assert(column->type.value.type == OVSDB_TYPE_VOID);
+
+    txn->inc_table = row->table->class->name;
+    txn->inc_column = column->name;
+    txn->inc_row = row->uuid;
 }
 
+/* Destroys 'txn' and frees all associated memory.  If ovsdb_idl_txn_commit()
+ * has been called for 'txn' but the commit is still incomplete (that is, the
+ * last call returned TXN_INCOMPLETE) then the transaction may or may not still
+ * end up committing at the database server, but the client will not be able to
+ * get any further status information back. */
 void
 ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *txn)
 {
@@ -1257,9 +1300,6 @@ ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *txn)
     ovsdb_idl_txn_abort(txn);
     ds_destroy(&txn->comment);
     free(txn->error);
-    free(txn->inc_table);
-    free(txn->inc_column);
-    json_destroy(txn->inc_where);
     HMAP_FOR_EACH_SAFE (insert, next, hmap_node, &txn->inserted_rows) {
         free(insert);
     }
@@ -1267,6 +1307,7 @@ ovsdb_idl_txn_destroy(struct ovsdb_idl_txn *txn)
     free(txn);
 }
 
+/* Causes poll_block() to wake up if 'txn' has completed committing. */
 void
 ovsdb_idl_txn_wait(const struct ovsdb_idl_txn *txn)
 {
@@ -1397,6 +1438,55 @@ ovsdb_idl_txn_disassemble(struct ovsdb_idl_txn *txn)
     hmap_init(&txn->txn_rows);
 }
 
+/* Attempts to commit 'txn'.  Returns the status of the commit operation, one
+ * of the following TXN_* constants:
+ *
+ *   TXN_INCOMPLETE:
+ *
+ *       The transaction is in progress, but not yet complete.  The caller
+ *       should call again later, after calling ovsdb_idl_run() to let the IDL
+ *       do OVSDB protocol processing.
+ *
+ *   TXN_UNCHANGED:
+ *
+ *       The transaction is complete.  (It didn't actually change the database,
+ *       so the IDL didn't send any request to the database server.)
+ *
+ *   TXN_ABORTED:
+ *
+ *       The caller previously called ovsdb_idl_txn_abort().
+ *
+ *   TXN_SUCCESS:
+ *
+ *       The transaction was successful.  The update made by the transaction
+ *       (and possibly other changes made by other database clients) should
+ *       already be visible in the IDL.
+ *
+ *   TXN_TRY_AGAIN:
+ *
+ *       The transaction failed for some transient reason, e.g. because a
+ *       "verify" operation reported an inconsistency or due to a network
+ *       problem.  The caller should wait for a change to the database, then
+ *       compose a new transaction, and commit the new transaction.
+ *
+ *       Use the return value of ovsdb_idl_get_seqno() to wait for a change in
+ *       the database.  It is important to use its return value *before* the
+ *       initial call to ovsdb_idl_txn_commit() as the baseline for this
+ *       purpose, because the change that one should wait for can happen after
+ *       the initial call but before the call that returns TXN_TRY_AGAIN, and
+ *       using some other baseline value in that situation could cause an
+ *       indefinite wait if the database rarely changes.
+ *
+ *   TXN_NOT_LOCKED:
+ *
+ *       The transaction failed because the IDL has been configured to require
+ *       a database lock (with ovsdb_idl_set_lock()) but didn't get it yet or
+ *       has already lost it.
+ *
+ * Committing a transaction rolls back all of the changes that it made to the
+ * IDL's copy of the database.  If the transaction commits successfully, then
+ * the database server will send an update and, thus, the IDL will be updated
+ * with the committed changes. */
 enum ovsdb_idl_txn_status
 ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
 {
@@ -1547,7 +1637,8 @@ ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
         json_object_put_string(op, "op", "mutate");
         json_object_put_string(op, "table", txn->inc_table);
         json_object_put(op, "where",
-                        substitute_uuids(json_clone(txn->inc_where), txn));
+                        substitute_uuids(where_uuid_equals(&txn->inc_row),
+                                         txn));
         json_object_put(op, "mutations",
                         json_array_create_1(
                             json_array_create_3(
@@ -1560,7 +1651,8 @@ ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
         json_object_put_string(op, "op", "select");
         json_object_put_string(op, "table", txn->inc_table);
         json_object_put(op, "where",
-                        substitute_uuids(json_clone(txn->inc_where), txn));
+                        substitute_uuids(where_uuid_equals(&txn->inc_row),
+                                         txn));
         json_object_put(op, "columns",
                         json_array_create_1(json_string_create(
                                                 txn->inc_column)));
@@ -1591,7 +1683,7 @@ ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
                     json_hash(txn->request_id, 0));
         txn->status = TXN_INCOMPLETE;
     } else {
-        txn->status = TXN_AGAIN_WAIT;
+        txn->status = TXN_TRY_AGAIN;
     }
 
     ovsdb_idl_txn_disassemble(txn);
@@ -1600,7 +1692,10 @@ ovsdb_idl_txn_commit(struct ovsdb_idl_txn *txn)
 
 /* Attempts to commit 'txn', blocking until the commit either succeeds or
  * fails.  Returns the final commit status, which may be any TXN_* value other
- * than TXN_INCOMPLETE. */
+ * than TXN_INCOMPLETE.
+ *
+ * This function calls ovsdb_idl_run() on 'txn''s IDL, so it may cause the
+ * return value of ovsdb_idl_get_seqno() to change. */
 enum ovsdb_idl_txn_status
 ovsdb_idl_txn_commit_block(struct ovsdb_idl_txn *txn)
 {
@@ -1616,6 +1711,9 @@ ovsdb_idl_txn_commit_block(struct ovsdb_idl_txn *txn)
     return status;
 }
 
+/* Returns the final (incremented) value of the column in 'txn' that was set to
+ * be incremented by ovsdb_idl_txn_increment().  'txn' must have committed
+ * successfully. */
 int64_t
 ovsdb_idl_txn_get_increment_new_value(const struct ovsdb_idl_txn *txn)
 {
@@ -1623,6 +1721,12 @@ ovsdb_idl_txn_get_increment_new_value(const struct ovsdb_idl_txn *txn)
     return txn->inc_new_value;
 }
 
+/* Aborts 'txn' without sending it to the database server.  This is effective
+ * only if ovsdb_idl_txn_commit() has not yet been called for 'txn'.
+ * Otherwise, it has no effect.
+ *
+ * Aborting a transaction doesn't free its memory.  Use
+ * ovsdb_idl_txn_destroy() to do that. */
 void
 ovsdb_idl_txn_abort(struct ovsdb_idl_txn *txn)
 {
@@ -1632,6 +1736,14 @@ ovsdb_idl_txn_abort(struct ovsdb_idl_txn *txn)
     }
 }
 
+/* Returns a string that reports the error status for 'txn'.  The caller must
+ * not modify or free the returned string.  A call to ovsdb_idl_txn_destroy()
+ * for 'txn' may free the returned string.
+ *
+ * The return value is ordinarily one of the strings that
+ * ovsdb_idl_txn_status_to_string() would return, but if the transaction failed
+ * due to an error reported by the database server, the return value is that
+ * error. */
 const char *
 ovsdb_idl_txn_get_error(const struct ovsdb_idl_txn *txn)
 {
@@ -1713,6 +1825,7 @@ ovsdb_idl_txn_write(const struct ovsdb_idl_row *row_,
     size_t column_idx;
 
     if (ovsdb_idl_row_is_synthetic(row)) {
+        ovsdb_datum_destroy(datum, &column->type);
         return;
     }
 
@@ -1896,7 +2009,7 @@ ovsdb_idl_txn_abort_all(struct ovsdb_idl *idl)
     struct ovsdb_idl_txn *txn;
 
     HMAP_FOR_EACH (txn, hmap_node, &idl->outstanding_txns) {
-        ovsdb_idl_txn_complete(txn, TXN_AGAIN_WAIT);
+        ovsdb_idl_txn_complete(txn, TXN_TRY_AGAIN);
     }
 }
 
@@ -2097,9 +2210,7 @@ ovsdb_idl_txn_process_reply(struct ovsdb_idl *idl,
 
         status = (hard_errors ? TXN_ERROR
                   : lock_errors ? TXN_NOT_LOCKED
-                  : soft_errors ? (txn->commit_seqno == idl->change_seqno
-                                   ? TXN_AGAIN_WAIT
-                                   : TXN_AGAIN_NOW)
+                  : soft_errors ? TXN_TRY_AGAIN
                   : TXN_SUCCESS);
     }
 
@@ -2107,6 +2218,8 @@ ovsdb_idl_txn_process_reply(struct ovsdb_idl *idl,
     return true;
 }
 
+/* Returns the transaction currently active for 'row''s IDL.  A transaction
+ * must currently be active. */
 struct ovsdb_idl_txn *
 ovsdb_idl_txn_get(const struct ovsdb_idl_row *row)
 {
@@ -2115,6 +2228,7 @@ ovsdb_idl_txn_get(const struct ovsdb_idl_row *row)
     return txn;
 }
 
+/* Returns the IDL on which 'txn' acts. */
 struct ovsdb_idl *
 ovsdb_idl_txn_get_idl (struct ovsdb_idl_txn *txn)
 {