Acked-by: Ethan Jackson <et...@nicira.com> Thanks looks good.
On Tue, Aug 27, 2013 at 5:16 PM, Ben Pfaff <b...@nicira.com> wrote: > We have a call chain like this: > > iface_configure_qos() calls > netdev_dump_queues(), which calls > netdev_linux_dump_queues(), which calls back through 'cb' to > qos_unixctl_show_cb(), which calls > netdev_delete_queue(), which calls > netdev_linux_delete_queue(). > > Both netdev_dump_queues() and netdev_linux_delete_queue() take the same > mutex in the same netdev, which deadlocks. > > This commit fixes the problem by getting rid of the callback. > > netdev_linux_dump_queue_stats() would benefit from the same treatment but > it's less urgent because I don't see any callbacks from that function that > call back into a netdev function. > > Bug #19319. > Reported-by: Scott Hendricks <shendri...@vmware.com> > Signed-off-by: Ben Pfaff <b...@nicira.com> > --- > lib/netdev-dummy.c | 4 ++- > lib/netdev-linux.c | 79 +++++++++++++++++++++++++++++++++------------ > lib/netdev-provider.h | 49 +++++++++++++++++++--------- > lib/netdev-vport.c | 4 ++- > lib/netdev.c | 86 > ++++++++++++++++++++++++++++++++++++++----------- > lib/netdev.h | 41 ++++++++++++++++++++--- > vswitchd/bridge.c | 62 +++++++++++++++-------------------- > 7 files changed, 226 insertions(+), 99 deletions(-) > > diff --git a/lib/netdev-dummy.c b/lib/netdev-dummy.c > index ac26564..16a6b0b 100644 > --- a/lib/netdev-dummy.c > +++ b/lib/netdev-dummy.c > @@ -708,7 +708,9 @@ static const struct netdev_class dummy_class = { > NULL, /* set_queue */ > NULL, /* delete_queue */ > NULL, /* get_queue_stats */ > - NULL, /* dump_queues */ > + NULL, /* queue_dump_start */ > + NULL, /* queue_dump_next */ > + NULL, /* queue_dump_done */ > NULL, /* dump_queue_stats */ > > NULL, /* get_in4 */ > diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c > index 80fa39b..26fb32d 100644 > --- a/lib/netdev-linux.c > +++ b/lib/netdev-linux.c > @@ -2100,35 +2100,35 @@ start_queue_dump(const struct netdev *netdev, struct > nl_dump *dump) > return true; > } > > +struct netdev_linux_queue_state { > + unsigned int *queues; > + size_t cur_queue; > + size_t n_queues; > +}; > + > static int > -netdev_linux_dump_queues(const struct netdev *netdev_, > - netdev_dump_queues_cb *cb, void *aux) > +netdev_linux_queue_dump_start(const struct netdev *netdev_, void **statep) > { > - struct netdev_linux *netdev = netdev_linux_cast(netdev_); > + const struct netdev_linux *netdev = netdev_linux_cast(netdev_); > int error; > > ovs_mutex_lock(&netdev->mutex); > error = tc_query_qdisc(netdev_); > if (!error) { > if (netdev->tc->ops->class_get) { > - struct tc_queue *queue, *next_queue; > - struct smap details; > - > - smap_init(&details); > - HMAP_FOR_EACH_SAFE (queue, next_queue, hmap_node, > - &netdev->tc->queues) { > - int retval; > - > - smap_clear(&details); > - > - retval = netdev->tc->ops->class_get(netdev_, queue, > &details); > - if (!retval) { > - (*cb)(queue->queue_id, &details, aux); > - } else { > - error = retval; > - } > + struct netdev_linux_queue_state *state; > + struct tc_queue *queue; > + size_t i; > + > + *statep = state = xmalloc(sizeof *state); > + state->n_queues = hmap_count(&netdev->tc->queues); > + state->cur_queue = 0; > + state->queues = xmalloc(state->n_queues * sizeof *state->queues); > + > + i = 0; > + HMAP_FOR_EACH (queue, hmap_node, &netdev->tc->queues) { > + state->queues[i++] = queue->queue_id; > } > - smap_destroy(&details); > } else { > error = EOPNOTSUPP; > } > @@ -2139,6 +2139,41 @@ netdev_linux_dump_queues(const struct netdev *netdev_, > } > > static int > +netdev_linux_queue_dump_next(const struct netdev *netdev_, void *state_, > + unsigned int *queue_idp, struct smap *details) > +{ > + const struct netdev_linux *netdev = netdev_linux_cast(netdev_); > + struct netdev_linux_queue_state *state = state_; > + int error = EOF; > + > + ovs_mutex_lock(&netdev->mutex); > + while (state->cur_queue < state->n_queues) { > + unsigned int queue_id = state->queues[state->cur_queue++]; > + struct tc_queue *queue = tc_find_queue(netdev_, queue_id); > + > + if (queue) { > + *queue_idp = queue_id; > + error = netdev->tc->ops->class_get(netdev_, queue, details); > + break; > + } > + } > + ovs_mutex_unlock(&netdev->mutex); > + > + return error; > +} > + > +static int > +netdev_linux_queue_dump_done(const struct netdev *netdev OVS_UNUSED, > + void *state_) > +{ > + struct netdev_linux_queue_state *state = state_; > + > + free(state->queues); > + free(state); > + return 0; > +} > + > +static int > netdev_linux_dump_queue_stats(const struct netdev *netdev_, > netdev_dump_queue_stats_cb *cb, void *aux) > { > @@ -2580,7 +2615,9 @@ netdev_linux_change_seq(const struct netdev *netdev_) > netdev_linux_set_queue, \ > netdev_linux_delete_queue, \ > netdev_linux_get_queue_stats, \ > - netdev_linux_dump_queues, \ > + netdev_linux_queue_dump_start, \ > + netdev_linux_queue_dump_next, \ > + netdev_linux_queue_dump_done, \ > netdev_linux_dump_queue_stats, \ > \ > netdev_linux_get_in4, \ > diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h > index 70a5188..9ab58fb 100644 > --- a/lib/netdev-provider.h > +++ b/lib/netdev-provider.h > @@ -478,22 +478,39 @@ struct netdev_class { > int (*get_queue_stats)(const struct netdev *netdev, unsigned int > queue_id, > struct netdev_queue_stats *stats); > > - /* Iterates over all of 'netdev''s queues, calling 'cb' with the queue's > - * ID, its configuration, and the 'aux' specified by the caller. The > order > - * of iteration is unspecified, but (when successful) each queue is > visited > - * exactly once. > - * > - * 'cb' will not modify or free the 'details' argument passed in. It may > - * delete or modify the queue passed in as its 'queue_id' argument. It > may > - * modify but will not delete any other queue within 'netdev'. If 'cb' > - * adds new queues, then ->dump_queues is allowed to visit some queues > - * twice or not at all. > - */ > - int (*dump_queues)(const struct netdev *netdev, > - void (*cb)(unsigned int queue_id, > - const struct smap *details, > - void *aux), > - void *aux); > + /* Attempts to begin dumping the queues in 'netdev'. On success, > returns 0 > + * and initializes '*statep' with any data needed for iteration. On > + * failure, returns a positive errno value. > + * > + * May be NULL if 'netdev' does not support QoS at all. */ > + int (*queue_dump_start)(const struct netdev *netdev, void **statep); > + > + /* Attempts to retrieve another queue from 'netdev' for 'state', which > was > + * initialized by a successful call to the 'queue_dump_start' function > for > + * 'netdev'. On success, stores a queue ID into '*queue_id' and fills > + * 'details' with the configuration of the queue with that ID. Returns > EOF > + * if the last queue has been dumped, or a positive errno value on error. > + * This function will not be called again once it returns nonzero once > for > + * a given iteration (but the 'queue_dump_done' function will be called > + * afterward). > + * > + * The caller initializes and clears 'details' before calling this > + * function. The caller takes ownership of the string key-values pairs > + * added to 'details'. > + * > + * The returned contents of 'details' should be documented as valid for > the > + * given 'type' in the "other_config" column in the "Queue" table in > + * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)). > + * > + * May be NULL if 'netdev' does not support QoS at all. */ > + int (*queue_dump_next)(const struct netdev *netdev, void *state, > + unsigned int *queue_id, struct smap *details); > + > + /* Releases resources from 'netdev' for 'state', which was initialized > by a > + * successful call to the 'queue_dump_start' function for 'netdev'. > + * > + * May be NULL if 'netdev' does not support QoS at all. */ > + int (*queue_dump_done)(const struct netdev *netdev, void *state); > > /* Iterates over all of 'netdev''s queues, calling 'cb' with the queue's > * ID, its statistics, and the 'aux' specified by the caller. The order > of > diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c > index 0f5dd7a..af50597 100644 > --- a/lib/netdev-vport.c > +++ b/lib/netdev-vport.c > @@ -727,7 +727,9 @@ get_stats(const struct netdev *netdev, struct > netdev_stats *stats) > NULL, /* set_queue */ \ > NULL, /* delete_queue */ \ > NULL, /* get_queue_stats */ \ > - NULL, /* dump_queues */ \ > + NULL, /* queue_dump_start */ \ > + NULL, /* queue_dump_next */ \ > + NULL, /* queue_dump_done */ \ > NULL, /* dump_queue_stats */ \ > \ > NULL, /* get_in4 */ \ > diff --git a/lib/netdev.c b/lib/netdev.c > index bf942a0..5ed6062 100644 > --- a/lib/netdev.c > +++ b/lib/netdev.c > @@ -1398,30 +1398,78 @@ netdev_get_queue_stats(const struct netdev *netdev, > unsigned int queue_id, > return retval; > } > > -/* Iterates over all of 'netdev''s queues, calling 'cb' with the queue's ID, > - * its configuration, and the 'aux' specified by the caller. The order of > - * iteration is unspecified, but (when successful) each queue is visited > - * exactly once. > +/* Initializes 'dump' to begin dumping the queues in a netdev. > + * > + * This function provides no status indication. An error status for the > entire > + * dump operation is provided when it is completed by calling > + * netdev_queue_dump_done(). > + */ > +void > +netdev_queue_dump_start(struct netdev_queue_dump *dump, > + const struct netdev *netdev) > +{ > + dump->netdev = netdev_ref(netdev); > + if (netdev->netdev_class->queue_dump_start) { > + dump->error = netdev->netdev_class->queue_dump_start(netdev, > + &dump->state); > + } else { > + dump->error = EOPNOTSUPP; > + } > +} > + > +/* Attempts to retrieve another queue from 'dump', which must have been > + * initialized with netdev_queue_dump_start(). On success, stores a new > queue > + * ID into '*queue_id', fills 'details' with configuration details for the > + * queue, and returns true. On failure, returns false. > * > - * Calling this function may be more efficient than calling > netdev_get_queue() > - * for every queue. > + * Queues are not necessarily dumped in increasing order of queue ID (or any > + * other predictable order). > * > - * 'cb' must not modify or free the 'details' argument passed in. It may > - * delete or modify the queue passed in as its 'queue_id' argument. It may > - * modify but must not delete any other queue within 'netdev'. 'cb' should > not > - * add new queues because this may cause some queues to be visited twice or > not > - * at all. > + * Failure might indicate an actual error or merely that the last queue has > + * been dumped. An error status for the entire dump operation is provided > when > + * it is completed by calling netdev_queue_dump_done(). > * > - * Returns 0 if successful, otherwise a positive errno value. On error, some > - * configured queues may not have been included in the iteration. */ > + * The returned contents of 'details' should be documented as valid for the > + * given 'type' in the "other_config" column in the "Queue" table in > + * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)). > + * > + * The caller must initialize 'details' (e.g. with smap_init()) before > calling > + * this function. This function will clear and replace its contents. The > + * caller must free 'details' when it is no longer needed (e.g. with > + * smap_destroy()). */ > +bool > +netdev_queue_dump_next(struct netdev_queue_dump *dump, > + unsigned int *queue_id, struct smap *details) > +{ > + const struct netdev *netdev = dump->netdev; > + > + if (dump->error) { > + return false; > + } > + > + dump->error = netdev->netdev_class->queue_dump_next(netdev, dump->state, > + queue_id, details); > + > + if (dump->error) { > + netdev->netdev_class->queue_dump_done(netdev, dump->state); > + return false; > + } > + return true; > +} > + > +/* Completes queue table dump operation 'dump', which must have been > + * initialized with netdev_queue_dump_start(). Returns 0 if the dump > operation > + * was error-free, otherwise a positive errno value describing the problem. > */ > int > -netdev_dump_queues(const struct netdev *netdev, > - netdev_dump_queues_cb *cb, void *aux) > +netdev_queue_dump_done(struct netdev_queue_dump *dump) > { > - const struct netdev_class *class = netdev->netdev_class; > - return (class->dump_queues > - ? class->dump_queues(netdev, cb, aux) > - : EOPNOTSUPP); > + const struct netdev *netdev = dump->netdev; > + if (!dump->error && netdev->netdev_class->queue_dump_done) { > + dump->error = netdev->netdev_class->queue_dump_done(netdev, > + dump->state); > + } > + netdev_close(dump->netdev); > + return dump->error == EOF ? 0 : dump->error; > } > > /* Iterates over all of 'netdev''s queues, calling 'cb' with the queue's ID, > diff --git a/lib/netdev.h b/lib/netdev.h > index 287f6cc..bafa50e 100644 > --- a/lib/netdev.h > +++ b/lib/netdev.h > @@ -47,7 +47,17 @@ extern "C" { > * These functions are conditionally thread-safe: they may be called > from > * different threads only on different netdev_rx objects. (The client > may > * create multiple netdev_rx objects for a single netdev and access each > - * of those from a different thread.) */ > + * of those from a different thread.) > + * > + * NETDEV_FOR_EACH_QUEUE > + * netdev_queue_dump_next() > + * netdev_queue_dump_done() > + * > + * These functions are conditionally thread-safe: they may be called > from > + * different threads only on different netdev_queue_dump objects. (The > + * client may create multiple netdev_queue_dump objects for a single > + * netdev and access each of those from a different thread.) > + */ > > struct netdev; > struct netdev_class; > @@ -271,10 +281,31 @@ int netdev_delete_queue(struct netdev *, unsigned int > queue_id); > int netdev_get_queue_stats(const struct netdev *, unsigned int queue_id, > struct netdev_queue_stats *); > > -typedef void netdev_dump_queues_cb(unsigned int queue_id, > - const struct smap *details, void *aux); > -int netdev_dump_queues(const struct netdev *, > - netdev_dump_queues_cb *, void *aux); > +struct netdev_queue_dump { > + struct netdev *netdev; > + int error; > + void *state; > +}; > +void netdev_queue_dump_start(struct netdev_queue_dump *, > + const struct netdev *); > +bool netdev_queue_dump_next(struct netdev_queue_dump *, > + unsigned int *queue_id, struct smap *details); > +int netdev_queue_dump_done(struct netdev_queue_dump *); > + > +/* Iterates through each queue in NETDEV, using DUMP as state. Fills > QUEUE_ID > + * and DETAILS with information about queues. The client must initialize and > + * destroy DETAILS. > + * > + * Arguments all have pointer type. > + * > + * If you break out of the loop, then you need to free the dump structure by > + * hand using netdev_queue_dump_done(). */ > +#define NETDEV_QUEUE_FOR_EACH(QUEUE_ID, DETAILS, DUMP, NETDEV) \ > + for (netdev_queue_dump_start(DUMP, NETDEV); \ > + (netdev_queue_dump_next(DUMP, QUEUE_ID, DETAILS) \ > + ? true \ > + : (netdev_queue_dump_done(DUMP), false)); \ > + ) > > typedef void netdev_dump_queue_stats_cb(unsigned int queue_id, > struct netdev_queue_stats *, > diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c > index a11e646..f993ba1 100644 > --- a/vswitchd/bridge.c > +++ b/vswitchd/bridge.c > @@ -2557,13 +2557,11 @@ struct qos_unixctl_show_cbdata { > }; > > static void > -qos_unixctl_show_cb(unsigned int queue_id, > - const struct smap *details, > - void *aux) > +qos_unixctl_show_queue(unsigned int queue_id, > + const struct smap *details, > + struct iface *iface, > + struct ds *ds) > { > - struct qos_unixctl_show_cbdata *data = aux; > - struct ds *ds = data->ds; > - struct iface *iface = data->iface; > struct netdev_queue_stats stats; > struct smap_node *node; > int error; > @@ -2607,8 +2605,6 @@ qos_unixctl_show(struct unixctl_conn *conn, int argc > OVS_UNUSED, > struct iface *iface; > const char *type; > struct smap_node *node; > - struct qos_unixctl_show_cbdata data; > - int error; > > iface = iface_find(argv[1]); > if (!iface) { > @@ -2619,20 +2615,22 @@ qos_unixctl_show(struct unixctl_conn *conn, int argc > OVS_UNUSED, > netdev_get_qos(iface->netdev, &type, &smap); > > if (*type != '\0') { > + struct netdev_queue_dump dump; > + struct smap details; > + unsigned int queue_id; > + > ds_put_format(&ds, "QoS: %s %s\n", iface->name, type); > > SMAP_FOR_EACH (node, &smap) { > ds_put_format(&ds, "%s: %s\n", node->key, node->value); > } > > - data.ds = &ds; > - data.iface = iface; > - error = netdev_dump_queues(iface->netdev, qos_unixctl_show_cb, > &data); > - > - if (error) { > - ds_put_format(&ds, "failed to dump queues: %s", > - ovs_strerror(error)); > + smap_init(&details); > + NETDEV_QUEUE_FOR_EACH (&queue_id, &details, &dump, iface->netdev) { > + qos_unixctl_show_queue(queue_id, &details, iface, &ds); > } > + smap_destroy(&details); > + > unixctl_command_reply(conn, ds_cstr(&ds)); > } else { > ds_put_format(&ds, "QoS not configured on %s\n", iface->name); > @@ -3605,11 +3603,6 @@ iface_clear_db_record(const struct ovsrec_interface > *if_cfg) > } > } > > -struct iface_delete_queues_cbdata { > - struct netdev *netdev; > - const struct ovsdb_datum *queues; > -}; > - > static bool > queue_ids_include(const struct ovsdb_datum *queues, int64_t target) > { > @@ -3620,17 +3613,6 @@ queue_ids_include(const struct ovsdb_datum *queues, > int64_t target) > } > > static void > -iface_delete_queues(unsigned int queue_id, > - const struct smap *details OVS_UNUSED, void *cbdata_) > -{ > - struct iface_delete_queues_cbdata *cbdata = cbdata_; > - > - if (!queue_ids_include(cbdata->queues, queue_id)) { > - netdev_delete_queue(cbdata->netdev, queue_id); > - } > -} > - > -static void > iface_configure_qos(struct iface *iface, const struct ovsrec_qos *qos) > { > struct ofpbuf queues_buf; > @@ -3640,7 +3622,10 @@ iface_configure_qos(struct iface *iface, const struct > ovsrec_qos *qos) > if (!qos || qos->type[0] == '\0' || qos->n_queues < 1) { > netdev_set_qos(iface->netdev, NULL, NULL); > } else { > - struct iface_delete_queues_cbdata cbdata; > + const struct ovsdb_datum *queues; > + struct netdev_queue_dump dump; > + unsigned int queue_id; > + struct smap details; > bool queue_zero; > size_t i; > > @@ -3648,10 +3633,15 @@ iface_configure_qos(struct iface *iface, const struct > ovsrec_qos *qos) > netdev_set_qos(iface->netdev, qos->type, &qos->other_config); > > /* Deconfigure queues that were deleted. */ > - cbdata.netdev = iface->netdev; > - cbdata.queues = ovsrec_qos_get_queues(qos, OVSDB_TYPE_INTEGER, > - OVSDB_TYPE_UUID); > - netdev_dump_queues(iface->netdev, iface_delete_queues, &cbdata); > + queues = ovsrec_qos_get_queues(qos, OVSDB_TYPE_INTEGER, > + OVSDB_TYPE_UUID); > + smap_init(&details); > + NETDEV_QUEUE_FOR_EACH (&queue_id, &details, &dump, iface->netdev) { > + if (!queue_ids_include(queues, queue_id)) { > + netdev_delete_queue(iface->netdev, queue_id); > + } > + } > + smap_destroy(&details); > > /* Configure queues for 'iface'. */ > queue_zero = false; > -- > 1.7.10.4 > > _______________________________________________ > dev mailing list > dev@openvswitch.org > http://openvswitch.org/mailman/listinfo/dev _______________________________________________ dev mailing list dev@openvswitch.org http://openvswitch.org/mailman/listinfo/dev