On Fri, Aug 18, 2017 at 03:05:31PM +0200, Alberto Garcia wrote:
On Fri 18 Aug 2017 05:10:17 AM CEST, Manos Pitsidianakis wrote:

  * If no ThrottleGroup is found with the given name a new one is
  * created.
  *
- * @name: the name of the ThrottleGroup
+ * This function edits throttle_groups and must be called under the global
+ * mutex.
+ *
+ * @name: the name of the ThrottleGroup, NULL means a new anonymous group will
+ *        be created.
  * @ret:  the ThrottleState member of the ThrottleGroup
  */
 ThrottleState *throttle_group_incref(const char *name)

If we're not going to have anonymous groups in the end this patch needs
changes (name == NULL is no longer allowed).

+/* This function edits throttle_groups and must be called under the global
+ * mutex */
+static void throttle_group_obj_complete(UserCreatable *obj, Error **errp)
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj), *iter;
+    ThrottleConfig *cfg = &tg->ts.cfg;
+
+    /* set group name to object id if it exists */
+    if (!tg->name && tg->parent_obj.parent) {
+        tg->name = object_get_canonical_path_component(OBJECT(obj));
+    }
+
+    if (tg->name) {
+        /* error if name is duplicate */
+        QTAILQ_FOREACH(iter, &throttle_groups, list) {
+            if (!g_strcmp0(tg->name, iter->name) && tg != iter) {

I'm just nitpicking here :) but if you change this and put 'tg != iter'
first you'll save one unnecessary string comparison.

Only in the case where tg == iter, otherwise you have one unnecessary pointer comparison for every other group.

+                error_setg(errp, "A group with this name already exists");
+                return;
+            }
+        }
+    }
+
+    /* unfix buckets to check validity */
+    throttle_get_config(&tg->ts, cfg);
+    if (!throttle_is_valid(cfg, errp)) {
+        return;
+    }
+    /* fix buckets again */
+    throttle_config(&tg->ts, tg->clock_type, cfg);

throttle_get_config(ts, cfg) makes a copy of the existing configuration,
but the cfg pointer that you are passing already points to the existing
configuration, so in practice you are doing

   *(ts->cfg) = *(ts->cfg);
   throttle_unfix_bucket(...);

and since you "unfixed" the configuration, then you need undo the whole
thing by setting the config again:

   *(ts->cfg) = *(ts->cfg);
   throttle_fix_bucket(...);

You should declare a local ThrottleConfig variable, copy the config
there, and run throttle_is_valid() on that copy. The final
throttle_config() call is unnecessary.

I figured I didn't have to make an extra copy but this looks bad in retrospect


Once the patches I sent yesterday are merged we'll be able to skip the
throttle_get_config() call and do throttle_is_valid(&tg->ts.cfg, errp)
directly.

+static void throttle_group_set(Object *obj, Visitor *v, const char * name,
+                               void *opaque, Error **errp)
+
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj);
+    ThrottleConfig cfg;
+    ThrottleParamInfo *info = opaque;
+    Error *local_err = NULL;
+    int64_t value;
+
+    /* If we have finished initialization, don't accept individual property
+     * changes through QOM. Throttle configuration limits must be set in one
+     * transaction, as certain combinations are invalid.
+     */
+    if (tg->is_initialized) {
+        error_setg(&local_err, "Property cannot be set after initialization");
+        goto ret;
+    }
+
+    visit_type_int64(v, name, &value, &local_err);
+    if (local_err) {
+        goto ret;
+    }
+    if (value < 0) {
+        error_setg(&local_err, "Property values cannot be negative");
+        goto ret;
+    }
+
+    cfg = tg->ts.cfg;
+    switch (info->data_type) {
+    case UINT64:
+        {
+            uint64_t *field = (void *)&cfg.buckets[info->type] + info->offset;
+            *field = value;
+        }
+        break;
+    case DOUBLE:
+        {
+            double *field = (void *)&cfg.buckets[info->type] + info->offset;
+            *field = value;
+        }
+        break;
+    case UNSIGNED:
+        {
+            if (value > UINT_MAX) {
+                error_setg(&local_err, "%s value must be in the"
+                                       "range [0, %u]", info->name, UINT_MAX);
+                goto ret;
+            }
+            unsigned *field = (void *)&cfg.buckets[info->type] + info->offset;
+            *field = value;
+        }
+    }
+
+    tg->ts.cfg = cfg;

There's a bit of black magic here :)

This offset business is indeed black magic! And university makes you think the world runs on ML and Haskell...

you have a user-defined enumeration
(UNSIGNED, DOUBLE, UINT64) to identify C types and I'm worried about
what happens if the data types of LeakyBucket change, will we be able to
detect the problem?

Out of the blue I can think of the following alternative:

  - There's 6 different buckets (we have BucketType listing them)

  - There's 3 values we can set in each bucket (max, avg,
    burst_length). For those we can have an internal enumeration
    (probably with one additional value for iops-size).

  - In the 'properties' array, for each property we know its category
    (and I mean: avg, max, burst-lenght, iops-size) and the bucket
    where they belong.

  - In throttle_group_set() the switch could be something like this:

    switch (info->category) {
       case THROTTLE_VALUE_AVG:
          cfg->buckets[info->bkt_type].avg = value;
          break;
       case THROTTLE_VALUE_MAX:
          cfg->buckets[info->bkt_type].max = value;
          break;
       case THROTTLE_VALUE_BURST_LENGTH:
          /* Code here to check that value <= UINT_MAX */
          cfg->buckets[info->bkt_type].iops_length = value;
          break;
       case THROTTLE_VALUE_IOPS_SIZE:
          cfg->op_size = value;
    }

I don't think that solves the type problem, since C uses coercion. For example if hypothetically a double changes to uint in the future, `value` will not be checked for overflow and the compiler would just convert it to uint implicitly. An academic solution would be to use a strongly typed language!

+static void throttle_group_get(Object *obj, Visitor *v, const char *name,
+                               void *opaque, Error **errp)

And the same approach here, of course.

+static void throttle_group_set_limits(Object *obj, Visitor *v,
+                                      const char *name, void *opaque,
+                                      Error **errp)
+
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj);
+    ThrottleConfig cfg;
+    ThrottleLimits *arg = NULL;
+    Error *local_err = NULL;
+
+    arg = g_new0(ThrottleLimits, 1);

Do you need to allocate ThrottleLimits in the heap?

I thought you actually do because visit_type_ThrottleLimits() frees arg on error:

   if (err && visit_is_input(v)) {
       qapi_free_ThrottleLimits(*obj);
       *obj = NULL;
   }

but I see now it doesn't actually call g_free and only sets the pointer to NULL. Is that supposed to prevent use of a stack pointer in the caller after error? I am a bit confused as to why. (This is the generated qapi-visit.c code but similar to other visitor functions)

I will change this back to

   ThrottleLimits arg = { 0 };
   ThrottleLimits *argp = &arg;
   visit_type_ThrottleLimits(v, name, &argp, &local_err);


+static void throttle_group_get_limits(Object *obj, Visitor *v,
+                                      const char *name, void *opaque,
+                                      Error **errp)
+{
+    ThrottleGroup *tg = THROTTLE_GROUP(obj);
+    ThrottleConfig cfg;
+    ThrottleLimits *arg = NULL;
+
+    qemu_mutex_lock(&tg->lock);
+    throttle_get_config(&tg->ts, &cfg);
+    qemu_mutex_unlock(&tg->lock);
+
+    arg = g_new0(ThrottleLimits, 1);
+    throttle_config_to_limits(&cfg, arg);
+
+    visit_type_ThrottleLimits(v, name, &arg, errp);
+    g_free(arg);

Same question here.

Berto

Attachment: signature.asc
Description: PGP signature

Reply via email to