Commit 7cc9621d authored by David Goulet's avatar David Goulet 🔆
Browse files

sched: Add Schedulers torrc option



This option is a list of possible scheduler type tor can use ordered by
priority. Its default value is "KIST,KISTLite,Vanilla" which means that KIST
will be used first and if unavailable will fallback to KISTLite and so on.
Signed-off-by: David Goulet's avatarDavid Goulet <dgoulet@torproject.org>
parent 6ff8c86a
......@@ -491,6 +491,7 @@ static config_var_t option_vars_[] = {
OBSOLETE("SchedulerMaxFlushCells__"),
V(KISTSchedRunInterval, MSEC_INTERVAL, "0 msec"),
V(KISTSockBufSizeFactor, DOUBLE, "1.0"),
V(Schedulers, CSV, "KIST,KISTLite,Vanilla"),
V(ShutdownWaitLength, INTERVAL, "30 seconds"),
OBSOLETE("SocksListenAddress"),
V(SocksPolicy, LINELIST, NULL),
......@@ -907,6 +908,10 @@ or_options_free(or_options_t *options)
rs, routerset_free(rs));
smartlist_free(options->NodeFamilySets);
}
if (options->SchedulerTypes_) {
SMARTLIST_FOREACH(options->SchedulerTypes_, int *, i, tor_free(i));
smartlist_free(options->SchedulerTypes_);
}
tor_free(options->BridgePassword_AuthDigest_);
tor_free(options->command_arg);
tor_free(options->master_key_fname);
......@@ -2888,6 +2893,61 @@ warn_about_relative_paths(or_options_t *options)
}
}
/* Validate options related to the scheduler. From the Schedulers list, the
* SchedulerTypes_ list is created with int values so once we select the
* scheduler, which can happen anytime at runtime, we don't have to parse
* strings and thus be quick.
*
* Return 0 on success else -1 and msg is set with an error message. */
static int
options_validate_scheduler(or_options_t *options, char **msg)
{
tor_assert(options);
tor_assert(msg);
if (!options->Schedulers || smartlist_len(options->Schedulers) == 0) {
REJECT("Empty Schedulers list. Either remove the option so the defaults "
"can be used or set at least one value.");
}
/* Ok, we do have scheduler types, validate them. */
options->SchedulerTypes_ = smartlist_new();
SMARTLIST_FOREACH_BEGIN(options->Schedulers, const char *, type) {
int *sched_type;
if (!strcasecmp("KISTLite", type)) {
sched_type = tor_malloc_zero(sizeof(int));
*sched_type = SCHEDULER_KIST_LITE;
smartlist_add(options->SchedulerTypes_, sched_type);
} else if (!strcasecmp("KIST", type)) {
sched_type = tor_malloc_zero(sizeof(int));
*sched_type = SCHEDULER_KIST;
smartlist_add(options->SchedulerTypes_, sched_type);
} else if (!strcasecmp("Vanilla", type)) {
sched_type = tor_malloc_zero(sizeof(int));
*sched_type = SCHEDULER_VANILLA;
smartlist_add(options->SchedulerTypes_, sched_type);
} else {
tor_asprintf(msg, "Unknown type %s in option Schedulers. "
"Possible values are KIST, KISTLite and Vanilla.",
escaped(type));
return -1;
}
} SMARTLIST_FOREACH_END(type);
if (options->KISTSockBufSizeFactor < 0) {
REJECT("KISTSockBufSizeFactor must be at least 0");
}
/* Don't need to validate that the Interval is less than anything because
* zero is valid and all negative values are valid. */
if (options->KISTSchedRunInterval > KIST_SCHED_RUN_INTERVAL_MAX) {
tor_asprintf(msg, "KISTSchedRunInterval must not be more than %d (ms)",
KIST_SCHED_RUN_INTERVAL_MAX);
return -1;
}
return 0;
}
/* Validate options related to single onion services.
* Modifies some options that are incompatible with single onion services.
* On failure returns -1, and sets *msg to an error string.
......@@ -3112,19 +3172,6 @@ options_validate(or_options_t *old_options, or_options_t *options,
routerset_union(options->ExcludeExitNodesUnion_,options->ExcludeNodes);
}
if (options->KISTSockBufSizeFactor < 0) {
REJECT("KISTSockBufSizeFactor must be at least 0");
}
/* Don't need to validate that the Interval is less than anything because
* zero is valid and all negative values are valid. */
if (options->KISTSchedRunInterval > KIST_SCHED_RUN_INTERVAL_MAX) {
char *buf = tor_calloc(80, sizeof(char));
tor_snprintf(buf, 80, "KISTSchedRunInterval must not be more than %d (ms)",
KIST_SCHED_RUN_INTERVAL_MAX);
*msg = buf;
return -1;
}
if (options->NodeFamilies) {
options->NodeFamilySets = smartlist_new();
for (cl = options->NodeFamilies; cl; cl = cl->next) {
......@@ -4241,6 +4288,10 @@ options_validate(or_options_t *old_options, or_options_t *options,
REJECT("BridgeRelay is 1, ORPort is not set. This is an invalid "
"combination.");
if (options_validate_scheduler(options, msg) < 0) {
return -1;
}
return 0;
}
......
......@@ -4616,6 +4616,12 @@ typedef struct {
/** A multiplier for the KIST per-socket limit calculation. */
double KISTSockBufSizeFactor;
/** The list of scheduler type string ordered by priority that is first one
* has to be tried first. Default: KIST,KISTLite,Vanilla */
smartlist_t *Schedulers;
/* An ordered list of scheduler_types mapped from Schedulers. */
smartlist_t *SchedulerTypes_;
} or_options_t;
/** Persistent state for an onion router, as saved to disk. */
......
......@@ -274,6 +274,48 @@ scheduler_compare_channels, (const void *c1_v, const void *c2_v))
* Functions that can be accessed from anywhere in Tor.
*****************************************************************************/
/* Using the global options, select the scheduler we should be using. */
static void
select_scheduler(void)
{
const char *chosen_sched_type = NULL;
/* This list is ordered that is first entry has the first priority. Thus, as
* soon as we find a scheduler type that we can use, we use it and stop. */
SMARTLIST_FOREACH_BEGIN(get_options()->SchedulerTypes_, int *, type) {
switch (*type) {
case SCHEDULER_VANILLA:
the_scheduler = get_vanilla_scheduler();
chosen_sched_type = "Vanilla";
goto end;
case SCHEDULER_KIST:
if (!scheduler_can_use_kist()) {
log_warn(LD_SCHED, "Scheduler KIST can't be used. Consider removing "
"it from Schedulers or if you have a tor built "
"with KIST support, you should make sure "
"KISTSchedRunInterval is a non zero value");
continue;
}
the_scheduler = get_kist_scheduler();
chosen_sched_type = "KIST";
scheduler_kist_set_full_mode();
goto end;
case SCHEDULER_KIST_LITE:
chosen_sched_type = "KISTLite";
the_scheduler = get_kist_scheduler();
scheduler_kist_set_lite_mode();
goto end;
default:
/* Our option validation should have caught this. */
tor_assert_unreached();
}
} SMARTLIST_FOREACH_END(type);
end:
log_notice(LD_CONFIG, "Scheduler type %s has been enabled.",
chosen_sched_type);
}
/*
* Little helper function called from a few different places. It changes the
* scheduler implementation, if necessary. And if it did, it then tells the
......@@ -282,17 +324,10 @@ scheduler_compare_channels, (const void *c1_v, const void *c2_v))
static void
set_scheduler(void)
{
int have_kist = 0;
/* Switch, if needed */
scheduler_t *old_scheduler = the_scheduler;
if (scheduler_should_use_kist()) {
the_scheduler = get_kist_scheduler();
have_kist = 1;
} else {
the_scheduler = get_vanilla_scheduler();
}
tor_assert(the_scheduler);
/* From the options, select the scheduler type to set. */
select_scheduler();
if (old_scheduler != the_scheduler) {
/* Allow the old scheduler to clean up, if needed. */
......@@ -306,8 +341,6 @@ set_scheduler(void)
if (the_scheduler->init) {
the_scheduler->init();
}
log_notice(LD_CONFIG, "Using the %s scheduler.",
have_kist ? "KIST" : "vanilla");
}
}
......
......@@ -82,6 +82,15 @@ typedef struct scheduler_s {
void (*on_new_options)(void);
} scheduler_t;
/** Scheduler type, we build an ordered list with those values from the
* parsed strings in Schedulers. The reason to do such a thing is so we can
* quickly and without parsing strings select the scheduler at anytime. */
typedef enum {
SCHEDULER_VANILLA = 1,
SCHEDULER_KIST = 2,
SCHEDULER_KIST_LITE = 3,
} scheduler_types_t;
/*****************************************************************************
* Globally visible scheduler variables/values
*
......@@ -95,7 +104,6 @@ typedef struct scheduler_s {
/* Maximum interval that KIST runs (in ms). */
#define KIST_SCHED_RUN_INTERVAL_MAX 100
/*****************************************************************************
* Globally visible scheduler functions
*
......@@ -169,7 +177,9 @@ MOCK_DECL(int, channel_should_write_to_kernel,
MOCK_DECL(void, channel_write_to_kernel, (channel_t *chan));
MOCK_DECL(void, update_socket_info_impl, (socket_table_ent_t *ent));
int scheduler_should_use_kist(void);
int scheduler_can_use_kist(void);
void scheduler_kist_set_full_mode(void);
void scheduler_kist_set_lite_mode(void);
scheduler_t *get_kist_scheduler(void);
int32_t kist_scheduler_run_interval(const networkstatus_t *ns);
......
......@@ -26,12 +26,18 @@
* Data structures and supporting functions
*****************************************************************************/
#ifdef HAVE_KIST_SUPPORT
/* Indicate if KIST lite mode is on or off. We can disable it at runtime.
* Important to have because of the KISTLite -> KIST possible transition. */
static unsigned int kist_lite_mode = 0;
/* Indicate if we don't have the kernel support. This can happen if the kernel
* changed and it doesn't recognized the values passed to the syscalls needed
* by KIST. In that case, fallback to the naive approach. */
#ifdef HAVE_KIST_SUPPORT
static unsigned int kist_no_kernel_support = 0;
#endif /* HAVE_KIST_SUPPORT */
#else
static unsigned int kist_no_kernel_support = 1;
static unsigned int kist_lite_mode = 1;
#endif
/* Socket_table hash table stuff. The socket_table keeps track of per-socket
* limit information imposed by kist and used by kist. */
......@@ -193,7 +199,7 @@ update_socket_info_impl, (socket_table_ent_t *ent))
struct tcp_info tcp;
socklen_t tcp_info_len = sizeof(tcp);
if (kist_no_kernel_support) {
if (kist_no_kernel_support || kist_lite_mode) {
goto fallback;
}
......@@ -686,15 +692,40 @@ kist_scheduler_run_interval(const networkstatus_t *ns)
return run_interval;
}
/* Set KISTLite mode that is KIST without kernel support. */
void
scheduler_kist_set_lite_mode(void)
{
kist_lite_mode = 1;
log_info(LD_SCHED,
"Setting KIST scheduler without kernel support (KISTLite mode)");
}
/* Set KIST mode that is KIST with kernel support. */
void
scheduler_kist_set_full_mode(void)
{
kist_lite_mode = 0;
log_info(LD_SCHED,
"Setting KIST scheduler with kernel support (KIST mode)");
}
#ifdef HAVE_KIST_SUPPORT
/* Return true iff the scheduler subsystem should use KIST. */
int
scheduler_should_use_kist(void)
scheduler_can_use_kist(void)
{
if (kist_no_kernel_support) {
/* We have no kernel support so we can't use KIST. */
return 0;
}
/* We do have the support, time to check if we can get the interval that the
* consensus can be disabling. */
int64_t run_interval = kist_scheduler_run_interval(NULL);
log_info(LD_SCHED, "Determined sched_run_interval should be %" PRId64 ". "
"Will%s use KIST.",
log_debug(LD_SCHED, "Determined KIST sched_run_interval should be "
"%" PRId64 ". Can%s use KIST.",
run_interval, (run_interval > 0 ? "" : " not"));
return run_interval > 0;
}
......@@ -702,7 +733,7 @@ scheduler_should_use_kist(void)
#else /* HAVE_KIST_SUPPORT */
int
scheduler_should_use_kist(void)
scheduler_can_use_kist(void)
{
return 0;
}
......
......@@ -887,7 +887,7 @@ test_scheduler_initfree(void *arg)
}
static void
test_scheduler_should_use_kist(void *arg)
test_scheduler_can_use_kist(void *arg)
{
(void)arg;
......@@ -897,7 +897,7 @@ test_scheduler_should_use_kist(void *arg)
/* Test force disabling of KIST */
clear_options();
mocked_options.KISTSchedRunInterval = -1;
res_should = scheduler_should_use_kist();
res_should = scheduler_can_use_kist();
res_freq = kist_scheduler_run_interval(NULL);
tt_int_op(res_should, ==, 0);
tt_int_op(res_freq, ==, -1);
......@@ -905,7 +905,7 @@ test_scheduler_should_use_kist(void *arg)
/* Test force enabling of KIST */
clear_options();
mocked_options.KISTSchedRunInterval = 1234;
res_should = scheduler_should_use_kist();
res_should = scheduler_can_use_kist();
res_freq = kist_scheduler_run_interval(NULL);
#ifdef HAVE_KIST_SUPPORT
tt_int_op(res_should, ==, 1);
......@@ -917,7 +917,7 @@ test_scheduler_should_use_kist(void *arg)
/* Test defer to consensus, but no consensus available */
clear_options();
mocked_options.KISTSchedRunInterval = 0;
res_should = scheduler_should_use_kist();
res_should = scheduler_can_use_kist();
res_freq = kist_scheduler_run_interval(NULL);
#ifdef HAVE_KIST_SUPPORT
tt_int_op(res_should, ==, 1);
......@@ -930,7 +930,7 @@ test_scheduler_should_use_kist(void *arg)
MOCK(networkstatus_get_param, mock_kist_networkstatus_get_param);
clear_options();
mocked_options.KISTSchedRunInterval = 0;
res_should = scheduler_should_use_kist();
res_should = scheduler_can_use_kist();
res_freq = kist_scheduler_run_interval(NULL);
#ifdef HAVE_KIST_SUPPORT
tt_int_op(res_should, ==, 1);
......@@ -944,7 +944,7 @@ test_scheduler_should_use_kist(void *arg)
MOCK(networkstatus_get_param, mock_vanilla_networkstatus_get_param);
clear_options();
mocked_options.KISTSchedRunInterval = 0;
res_should = scheduler_should_use_kist();
res_should = scheduler_can_use_kist();
res_freq = kist_scheduler_run_interval(NULL);
tt_int_op(res_should, ==, 0);
tt_int_op(res_freq, ==, -1);
......@@ -1023,7 +1023,7 @@ struct testcase_t scheduler_tests[] = {
{ "loop_vanilla", test_scheduler_loop_vanilla, TT_FORK, NULL, NULL },
{ "loop_kist", test_scheduler_loop_kist, TT_FORK, NULL, NULL },
{ "ns_changed", test_scheduler_ns_changed, TT_FORK, NULL, NULL},
{ "should_use_kist", test_scheduler_should_use_kist, TT_FORK, NULL, NULL },
{ "should_use_kist", test_scheduler_can_use_kist, TT_FORK, NULL, NULL },
END_OF_TESTCASES
};
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment