diff --git a/include/haproxy/thread.h b/include/haproxy/thread.h index d6e7bc92a..7e0556f15 100644 --- a/include/haproxy/thread.h +++ b/include/haproxy/thread.h @@ -44,7 +44,8 @@ void ha_tkill(unsigned int thr, int sig); void ha_tkillall(int sig); void ha_thread_relax(void); int thread_map_to_groups(); -int thread_resolve_group_mask(uint igid, ulong imask, uint *ogid, ulong *omask, char **err); +int thread_resolve_group_mask(struct thread_set *ts, uint *ogid, ulong *omask, char **err); +int parse_thread_set(const char *arg, struct thread_set *ts, char **err); extern int thread_cpus_enabled_at_boot; diff --git a/include/haproxy/tinfo-t.h b/include/haproxy/tinfo-t.h index ddf031774..7bac2c5da 100644 --- a/include/haproxy/tinfo-t.h +++ b/include/haproxy/tinfo-t.h @@ -28,6 +28,21 @@ #include #include + +/* Threads sets are known either by a set of absolute thread numbers, or by a + * set of relative thread numbers within a group, for each group. The default + * is the absolute mode and corresponds to the case where no group is known + * (nbgrp == 0). The mode may only be changed when the set is empty (use + * thread_set_is_empty() for this). + */ +struct thread_set { + union { + ulong abs[(MAX_THREADS + LONGBITS - 1) / LONGBITS]; + ulong rel[MAX_TGROUPS]; + }; + uint nbgrp; /* number of non-empty groups in this set, 0 for abs */ +}; + /* tasklet classes */ enum { TL_URGENT = 0, /* urgent tasklets (I/O callbacks) */ diff --git a/include/haproxy/tinfo.h b/include/haproxy/tinfo.h index ee62bbae1..4bae4f2b3 100644 --- a/include/haproxy/tinfo.h +++ b/include/haproxy/tinfo.h @@ -24,6 +24,7 @@ #include #include +#include /* the structs are in thread.c */ extern struct tgroup_info ha_tgroup_info[MAX_TGROUPS]; @@ -38,4 +39,79 @@ extern THREAD_LOCAL struct tgroup_ctx *tg_ctx; /* ha_tgroup_ctx for the current extern struct thread_ctx ha_thread_ctx[MAX_THREADS]; extern THREAD_LOCAL struct thread_ctx *th_ctx; /* ha_thread_ctx for the current thread */ +/* returns the number of threads set in set . */ +static inline int thread_set_count(const struct thread_set *ts) +{ + int i, n; + + /* iterating over tgroups guarantees to visit all possible threads, the + * opposite is not true. + */ + for (i = n = 0; i < MAX_TGROUPS; i++) + n += my_popcountl(ts->rel[i]); + return n; +} + +/* returns zero if the thread set has at least one thread set, + * otherwise non-zero. + */ +static inline int thread_set_is_empty(const struct thread_set *ts) +{ + int i; + + /* iterating over tgroups guarantees to visit all possible threads, the + * opposite is not true. + */ + for (i = 0; i < MAX_TGROUPS; i++) + if (ts->rel[i]) + return 0; + return 1; +} + +/* returns the number starting at 1 of the first thread-group set in thread set + * , or zero if the set is empty or if thread numbers are only absolute. + */ +static inline int thread_set_first_group(const struct thread_set *ts) +{ + int i; + + if (ts->nbgrp) { + for (i = 0; i < MAX_TGROUPS; i++) + if (ts->rel[i]) + return i + 1; + } + return 0; +} + +/* returns the thread mask of the first assigned thread-group in the thread + * set for relative sets, the first thread mask at all in case of absolute + * sets, or zero if the set is empty. This is only used temporarily to ease the + * transition. + */ +static inline ulong thread_set_first_tmask(const struct thread_set *ts) +{ + int i; + + if (ts->nbgrp) { + for (i = 0; i < MAX_TGROUPS; i++) + if (ts->rel[i]) + return ts->rel[i]; + } + return ts->abs[0]; +} + +/* Pins the thread set to the specified thread mask on group 1 (use ~0UL for + * all threads). This is for compatibility with some rare legacy code. If a + * "thread" directive on a bind line is parsed, this one will be overwritten. + */ +static inline void thread_set_pin_grp1(struct thread_set *ts, ulong mask) +{ + int i; + + ts->nbgrp = 1; + ts->rel[0] = mask; + for (i = 1; i < MAX_TGROUPS; i++) + ts->rel[i] = 0; +} + #endif /* _HAPROXY_TINFO_H */ diff --git a/src/thread.c b/src/thread.c index 241377ec6..59e247ed1 100644 --- a/src/thread.c +++ b/src/thread.c @@ -1304,20 +1304,259 @@ int thread_resolve_group_mask(uint igid, ulong imask, uint *ogid, ulong *omask, while (imask) { new_mask |= imask & mask; - imask >>= ha_tgroup_info[igid - 1].count; + imask >>= ha_tgroup_info[g].count; } imask = new_mask; #else - memprintf(err, "'thread' directive only references threads not belonging to the group"); + memprintf(err, "'thread' directive only references threads not belonging to group %u", g+1); return -1; #endif } - *omask = mask & imask; - *ogid = igid; - return 0; + new_ts.rel[g] = imask & mask; + new_ts.nbgrp++; } } + + /* update the thread_set */ + if (!thread_set_first_group(&new_ts)) { + memprintf(err, "'thread' directive only references non-existing threads"); + return -1; + } + + *ts = new_ts; + + /* FIXME: for now we still also return the old format */ + *ogid = thread_set_first_group(ts); + *omask = thread_set_first_tmask(ts); + return 0; +} + +/* Parse a string representing a thread set in one of the following forms: + * + * - { "all" | "odd" | "even" | [ "-" ] }[,...] + * => these are (lists of) absolute thread numbers + * + * - "/" { "all" | "odd" | "even" | [ "-" ][,...] + * => these are (lists of) per-group relative thread numbers. All numbers + * must be lower than or equal to LONGBITS. When multiple list elements + * are provided, each of them must contain the thread group number. + * + * Minimum value for a thread or group number is always 1. Maximum value for an + * absolute thread number is MAX_THREADS, maximum value for a relative thread + * number is MAX_THREADS_PER_GROUP, an maximum value for a thread group is + * MAX_TGROUPS. "all", "even" and "odd" will be bound by MAX_THREADS and/or + * MAX_THREADS_PER_GROUP in any case. In ranges, a missing digit before "-" + * is implicitly 1, and a missing digit after "-" is implicitly the highest of + * its class. As such "-" is equivalent to "all", allowing to build strings + * such as "${MIN}-${MAX}" where both MIN and MAX are optional. + * + * It is not valid to mix absolute and relative numbers. As such: + * - all valid (all absolute threads) + * - 12-19,24-31 valid (abs threads 12 to 19 and 24 to 31) + * - 1/all valid (all 32 or 64 threads of group 1) + * - 1/1-4,1/8-10,2/1 valid + * - 1/1-4,8-10 invalid (mixes relatve "1/1-4" with absolute "8-10") + * - 1-4,8-10,2/1 invalid (mixes absolute "1-4,8-10" with relative "2/1") + * - 1/odd-4 invalid (mixes range with boundary) + * + * The target thread set is *completed* with supported threads, which means + * that it's the caller's responsibility for pre-initializing it. If the target + * thread set is NULL, it's not updated and the function only verifies that the + * input parses. + * + * On success, it returns 0. otherwise it returns non-zero with an error + * message in . + */ +int parse_thread_set(const char *arg, struct thread_set *ts, char **err) +{ + const char *set; + const char *sep; + int v, min, max, tg; + int is_rel; + + /* search for the first delimiter (',', '-' or '/') to decide whether + * we're facing an absolute or relative form. The relative form always + * starts with a number followed by a slash. + */ + for (sep = arg; isdigit((uchar)*sep); sep++) + ; + + is_rel = (/*sep > arg &&*/ *sep == '/'); /* relative form */ + + /* from there we have to cut the thread spec around commas */ + + set = arg; + tg = 0; + while (*set) { + /* note: we can't use strtol() here because "-3" would parse as + * (-3) while we want to stop before the "-", so we find the + * separator ourselves and rely on atoi() whose value we may + * ignore depending where the separator is. + */ + for (sep = set; isdigit((uchar)*sep); sep++) + ; + + if (sep != set && *sep && *sep != '/' && *sep != '-' && *sep != ',') { + memprintf(err, "invalid character '%c' in thread set specification: '%s'.", *sep, set); + return -1; + } + + v = (sep != set) ? atoi(set) : 0; + + /* Now we know that the string is made of an optional series of digits + * optionally followed by one of the delimiters above, or that it + * starts with a different character. + */ + + /* first, let's search for the thread group (digits before '/') */ + + if (tg || !is_rel) { + /* thread group already specified or not expected if absolute spec */ + if (*sep == '/') { + if (tg) + memprintf(err, "redundant thread group specification '%s' for group %d", set, tg); + else + memprintf(err, "group-relative thread specification '%s' is not permitted after a absolute thread range.", set); + return -1; + } + } else { + /* this is a group-relative spec, first field is the group number */ + if (sep == set && *sep == '/') { + memprintf(err, "thread group number expected before '%s'.", set); + return -1; + } + + if (*sep != '/') { + memprintf(err, "absolute thread specification '%s' is not permitted after a group-relative thread range.", set); + return -1; + } + + if (v < 1 || v > MAX_TGROUPS) { + memprintf(err, "invalid thread group number '%d', permitted range is 1..%d in '%s'.", v, MAX_TGROUPS, set); + return -1; + } + + tg = v; + + /* skip group number and go on with set,sep,v as if + * there was no group number. + */ + set = sep + 1; + continue; + } + + /* Now 'set' starts at the min thread number, whose value is in v if any, + * and preset the max to it, unless the range is filled at once via "all" + * (stored as 1:0), "odd" (stored as) 1:-1, or "even" (stored as 1:-2). + * 'sep' points to the next non-digit which may be set itself e.g. for + * "all" etc or "-xx". + */ + + if (!*set) { + /* empty set sets no restriction */ + min = 1; + max = is_rel ? MAX_THREADS_PER_GROUP : MAX_THREADS; + } + else { + if (sep != set && *sep && *sep != '-' && *sep != ',') { + // Only delimitors are permitted around digits. + memprintf(err, "invalid character '%c' in thread set specification: '%s'.", *sep, set); + return -1; + } + + /* for non-digits, find next delim */ + for (; *sep && *sep != '-' && *sep != ','; sep++) + ; + + min = max = 1; + if (sep != set) { + /* non-empty first thread */ + if (isteq(ist2(set, sep-set), ist("all"))) + max = 0; + else if (isteq(ist2(set, sep-set), ist("odd"))) + max = -1; + else if (isteq(ist2(set, sep-set), ist("even"))) + max = -2; + else if (v) + min = max = v; + else + max = min = 0; // throw an error below + } + + if (min < 1 || min > MAX_THREADS || (is_rel && min > MAX_THREADS_PER_GROUP)) { + memprintf(err, "invalid first thread number '%s', permitted range is 1..%d, or 'all', 'odd', 'even'.", + set, is_rel ? MAX_THREADS_PER_GROUP : MAX_THREADS); + return -1; + } + + /* is this a range ? */ + if (*sep == '-') { + if (min != max) { + memprintf(err, "extraneous range after 'all', 'odd' or 'even': '%s'.", set); + return -1; + } + + /* this is a seemingly valid range, there may be another number */ + for (set = ++sep; isdigit((uchar)*sep); sep++) + ; + v = atoi(set); + + if (sep == set) { // no digit: to the max + max = is_rel ? MAX_THREADS_PER_GROUP : MAX_THREADS; + if (*sep && *sep != ',') + max = 0; // throw an error below + } else + max = v; + + if (max < 1 || max > MAX_THREADS || (is_rel && max > MAX_THREADS_PER_GROUP)) { + memprintf(err, "invalid last thread number '%s', permitted range is 1..%d.", + set, is_rel ? MAX_THREADS_PER_GROUP : MAX_THREADS); + return -1; + } + } + + /* here sep points to the first non-digit after the thread spec, + * must be a valid delimiter. + */ + if (*sep && *sep != ',') { + memprintf(err, "invalid character '%c' after thread set specification: '%s'.", *sep, set); + return -1; + } + } + + /* store values */ + if (ts) { + if (is_rel) { + /* group-relative thread numbers */ + if (!ts->rel[tg - 1]) + ts->nbgrp++; + + if (max >= min) { + for (v = min; v <= max; v++) + ts->rel[tg - 1] |= 1UL << v; + } else { + memset(&ts->rel[tg - 1], + (max == 0) ? 0xff /* all */ : (max == -1) ? 0x55 /* odd */: 0xaa /* even */, + sizeof(ts->rel[tg - 1])); + } + } else { + /* absolute thread numbers */ + if (max >= min) { + for (v = min; v <= max; v++) + ts->abs[v / LONGBITS] |= 1UL << (v % LONGBITS); + } else { + memset(&ts->abs, + (max == 0) ? 0xff /* all */ : (max == -1) ? 0x55 /* odd */: 0xaa /* even */, + sizeof(ts->abs)); + } + } + } + + set = *sep ? sep + 1 : sep; + tg = 0; + } + return 0; } /* Parse the "nbthread" global directive, which takes an integer argument that