cfq-iosched: Don't update group weights when on service tree

Version 3 is updated to apply to for-2.6.39/core.

For version 2, I took Vivek's advice and made sure we update the group
weight from cfq_group_service_tree_add().

If a weight was updated while a group is on the service tree, the
calculation for the total weight of the service tree can be adjusted
improperly, which either leads to bad service tree weights, or
potentially crashes (if total_weight becomes 0).

This patch defers updates to the weight until a group is off the service
tree.

Signed-off-by: Justin TerAvest <teravest@google.com>
Acked-by: Vivek Goyal <vgoyal@redhat.com>
Signed-off-by: Jens Axboe <jaxboe@fusionio.com>

authored by Justin TerAvest and committed by Jens Axboe 8184f93e 95f28604

+42 -13
+42 -13
block/cfq-iosched.c
··· 178 /* group service_tree key */ 179 u64 vdisktime; 180 unsigned int weight; 181 182 /* number of cfqq currently on this group */ 183 int nr_cfqq; ··· 855 } 856 857 static void 858 - cfq_group_service_tree_add(struct cfq_data *cfqd, struct cfq_group *cfqg) 859 { 860 struct cfq_rb_root *st = &cfqd->grp_service_tree; 861 struct cfq_group *__cfqg; ··· 896 cfqg->vdisktime = __cfqg->vdisktime + CFQ_IDLE_DELAY; 897 } else 898 cfqg->vdisktime = st->min_vdisktime; 899 - 900 - __cfq_group_service_tree_add(st, cfqg); 901 - st->total_weight += cfqg->weight; 902 } 903 904 static void 905 - cfq_group_service_tree_del(struct cfq_data *cfqd, struct cfq_group *cfqg) 906 { 907 struct cfq_rb_root *st = &cfqd->grp_service_tree; 908 ··· 920 return; 921 922 cfq_log_cfqg(cfqd, cfqg, "del_from_rr group"); 923 - st->total_weight -= cfqg->weight; 924 - if (!RB_EMPTY_NODE(&cfqg->rb_node)) 925 - cfq_rb_erase(&cfqg->rb_node, st); 926 cfqg->saved_workload_slice = 0; 927 cfq_blkiocg_update_dequeue_stats(&cfqg->blkg, 1); 928 } ··· 974 charge = cfqq->allocated_slice; 975 976 /* Can't update vdisktime while group is on service tree */ 977 - cfq_rb_erase(&cfqg->rb_node, st); 978 cfqg->vdisktime += cfq_scale_slice(charge, cfqg); 979 - __cfq_group_service_tree_add(st, cfqg); 980 981 /* This group is being expired. Save the context */ 982 if (time_after(cfqd->workload_expires, jiffies)) { ··· 1009 void cfq_update_blkio_group_weight(void *key, struct blkio_group *blkg, 1010 unsigned int weight) 1011 { 1012 - cfqg_of_blkg(blkg)->weight = weight; 1013 } 1014 1015 static struct cfq_group * ··· 1284 service_tree->count++; 1285 if ((add_front || !new_cfqq) && !group_changed) 1286 return; 1287 - cfq_group_service_tree_add(cfqd, cfqq->cfqg); 1288 } 1289 1290 static struct cfq_queue * ··· 1397 cfqq->p_root = NULL; 1398 } 1399 1400 - cfq_group_service_tree_del(cfqd, cfqq->cfqg); 1401 BUG_ON(!cfqd->busy_queues); 1402 cfqd->busy_queues--; 1403 if (cfq_cfqq_sync(cfqq))
··· 178 /* group service_tree key */ 179 u64 vdisktime; 180 unsigned int weight; 181 + unsigned int new_weight; 182 + bool needs_update; 183 184 /* number of cfqq currently on this group */ 185 int nr_cfqq; ··· 853 } 854 855 static void 856 + cfq_update_group_weight(struct cfq_group *cfqg) 857 + { 858 + BUG_ON(!RB_EMPTY_NODE(&cfqg->rb_node)); 859 + if (cfqg->needs_update) { 860 + cfqg->weight = cfqg->new_weight; 861 + cfqg->needs_update = false; 862 + } 863 + } 864 + 865 + static void 866 + cfq_group_service_tree_add(struct cfq_rb_root *st, struct cfq_group *cfqg) 867 + { 868 + BUG_ON(!RB_EMPTY_NODE(&cfqg->rb_node)); 869 + 870 + cfq_update_group_weight(cfqg); 871 + __cfq_group_service_tree_add(st, cfqg); 872 + st->total_weight += cfqg->weight; 873 + } 874 + 875 + static void 876 + cfq_group_notify_queue_add(struct cfq_data *cfqd, struct cfq_group *cfqg) 877 { 878 struct cfq_rb_root *st = &cfqd->grp_service_tree; 879 struct cfq_group *__cfqg; ··· 874 cfqg->vdisktime = __cfqg->vdisktime + CFQ_IDLE_DELAY; 875 } else 876 cfqg->vdisktime = st->min_vdisktime; 877 + cfq_group_service_tree_add(st, cfqg); 878 } 879 880 static void 881 + cfq_group_service_tree_del(struct cfq_rb_root *st, struct cfq_group *cfqg) 882 + { 883 + st->total_weight -= cfqg->weight; 884 + if (!RB_EMPTY_NODE(&cfqg->rb_node)) 885 + cfq_rb_erase(&cfqg->rb_node, st); 886 + } 887 + 888 + static void 889 + cfq_group_notify_queue_del(struct cfq_data *cfqd, struct cfq_group *cfqg) 890 { 891 struct cfq_rb_root *st = &cfqd->grp_service_tree; 892 ··· 892 return; 893 894 cfq_log_cfqg(cfqd, cfqg, "del_from_rr group"); 895 + cfq_group_service_tree_del(st, cfqg); 896 cfqg->saved_workload_slice = 0; 897 cfq_blkiocg_update_dequeue_stats(&cfqg->blkg, 1); 898 } ··· 948 charge = cfqq->allocated_slice; 949 950 /* Can't update vdisktime while group is on service tree */ 951 + cfq_group_service_tree_del(st, cfqg); 952 cfqg->vdisktime += cfq_scale_slice(charge, cfqg); 953 + /* If a new weight was requested, update now, off tree */ 954 + cfq_group_service_tree_add(st, cfqg); 955 956 /* This group is being expired. Save the context */ 957 if (time_after(cfqd->workload_expires, jiffies)) { ··· 982 void cfq_update_blkio_group_weight(void *key, struct blkio_group *blkg, 983 unsigned int weight) 984 { 985 + struct cfq_group *cfqg = cfqg_of_blkg(blkg); 986 + cfqg->new_weight = weight; 987 + cfqg->needs_update = true; 988 } 989 990 static struct cfq_group * ··· 1255 service_tree->count++; 1256 if ((add_front || !new_cfqq) && !group_changed) 1257 return; 1258 + cfq_group_notify_queue_add(cfqd, cfqq->cfqg); 1259 } 1260 1261 static struct cfq_queue * ··· 1368 cfqq->p_root = NULL; 1369 } 1370 1371 + cfq_group_notify_queue_del(cfqd, cfqq->cfqg); 1372 BUG_ON(!cfqd->busy_queues); 1373 cfqd->busy_queues--; 1374 if (cfq_cfqq_sync(cfqq))