From d438cf1ec9d5de08b8a8fffd7c38b66134fd337c Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Sun, 25 Aug 2013 08:45:07 -0700 Subject: [PATCH 01/79] Implement scheduler mechanism to track lists of channels wanting cells or writes; doesn't actually drive the cell flow from it yet --- src/common/torlog.h | 4 +- src/or/Makefile.nmake | 1 + src/or/channel.c | 19 ++ src/or/channeltls.c | 5 + src/or/connection_or.c | 37 ++++ src/or/connection_or.h | 1 + src/or/include.am | 2 + src/or/main.c | 10 + src/or/relay.c | 5 + src/or/scheduler.c | 411 +++++++++++++++++++++++++++++++++++++++++ src/or/scheduler.h | 31 ++++ 11 files changed, 525 insertions(+), 1 deletion(-) create mode 100644 src/or/scheduler.c create mode 100644 src/or/scheduler.h diff --git a/src/common/torlog.h b/src/common/torlog.h index 34e39b4b94..2ae8aa9075 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -97,8 +97,10 @@ #define LD_HEARTBEAT (1u<<20) /** Abstract channel_t code */ #define LD_CHANNEL (1u<<21) +/** Scheduler */ +#define LD_SCHED (1u<<22) /** Number of logging domains in the code. */ -#define N_LOGGING_DOMAINS 22 +#define N_LOGGING_DOMAINS 23 /** This log message is not safe to send to a callback-based logger * immediately. Used as a flag, not a log domain. */ diff --git a/src/or/Makefile.nmake b/src/or/Makefile.nmake index 523bf3306b..2ac98cd372 100644 --- a/src/or/Makefile.nmake +++ b/src/or/Makefile.nmake @@ -63,6 +63,7 @@ LIBTOR_OBJECTS = \ routerlist.obj \ routerparse.obj \ routerset.obj \ + scheduler.obj \ statefile.obj \ status.obj \ transports.obj diff --git a/src/or/channel.c b/src/or/channel.c index c8c92633b1..da2493a758 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -29,6 +29,7 @@ #include "rephist.h" #include "router.h" #include "routerlist.h" +#include "scheduler.h" /* Cell queue structure */ @@ -788,6 +789,9 @@ channel_free(channel_t *chan) "Freeing channel " U64_FORMAT " at %p", U64_PRINTF_ARG(chan->global_identifier), chan); + /* Get this one out of the scheduler */ + scheduler_release_channel(chan); + /* * Get rid of cmux policy before we do anything, so cmux policies don't * see channels in weird half-freed states. @@ -863,6 +867,9 @@ channel_force_free(channel_t *chan) "Force-freeing channel " U64_FORMAT " at %p", U64_PRINTF_ARG(chan->global_identifier), chan); + /* Get this one out of the scheduler */ + scheduler_release_channel(chan); + /* * Get rid of cmux policy before we do anything, so cmux policies don't * see channels in weird half-freed states. @@ -1941,6 +1948,18 @@ channel_change_state(channel_t *chan, channel_state_t to_state) } } + /* + * If we're going to a closed/closing state, we don't need scheduling any + * more; in CHANNEL_STATE_MAINT we can't accept writes. + */ + if (to_state == CHANNEL_STATE_CLOSING || + to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) { + scheduler_release_channel(chan); + } else if (to_state == CHANNEL_STATE_MAINT) { + scheduler_channel_doesnt_want_writes(chan); + } + /* Tell circuits if we opened and stuff */ if (to_state == CHANNEL_STATE_OPEN) { channel_do_open_actions(chan); diff --git a/src/or/channeltls.c b/src/or/channeltls.c index 245e33583b..af1aab36d3 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -25,6 +25,7 @@ #include "relay.h" #include "router.h" #include "routerlist.h" +#include "scheduler.h" /** How many CELL_PADDING cells have we received, ever? */ uint64_t stats_n_padding_cells_processed = 0; @@ -867,6 +868,10 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, * CHANNEL_STATE_MAINT on this. */ channel_change_state(base_chan, CHANNEL_STATE_OPEN); + /* We might have just become writeable; check and tell the scheduler */ + if (connection_or_num_cells_writeable(conn) > 0) { + scheduler_channel_wants_writes(base_chan); + } } else { /* * Not open, so from CHANNEL_STATE_OPEN we go to CHANNEL_STATE_MAINT, diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 7fcc5b24d6..9074c0ac5f 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -38,6 +38,8 @@ #include "router.h" #include "routerlist.h" #include "ext_orport.h" +#include "scheduler.h" + #ifdef USE_BUFFEREVENTS #include #endif @@ -595,6 +597,17 @@ connection_or_flushed_some(or_connection_t *conn) * high water mark. */ datalen = connection_get_outbuf_len(TO_CONN(conn)); if (datalen < OR_CONN_LOWWATER) { + /* Let the scheduler know */ + scheduler_channel_wants_writes(TLS_CHAN_TO_BASE(conn->chan)); + + /* + * TODO this will be done from the scheduler, so it will + * need a generic way to ask how many cells a channel can + * accept and if it still wants writes or not to know how + * to account for it in the case that it runs out of cells + * to send first. + */ + while ((conn->chan) && channel_tls_more_to_flush(conn->chan)) { /* Compute how many more cells we want at most */ n = CEIL_DIV(OR_CONN_HIGHWATER - datalen, cell_network_size); @@ -616,6 +629,30 @@ connection_or_flushed_some(or_connection_t *conn) return 0; } +/** This is for channeltls.c to ask how many cells we could accept if + * they were available. */ +ssize_t +connection_or_num_cells_writeable(or_connection_t *conn) +{ + size_t datalen, cell_network_size; + ssize_t n = 0; + + tor_assert(conn); + + /* + * If we're under the high water mark, we're potentially + * writeable; note this is different from the calculation above + * used to trigger when to start writing after we've stopped. + */ + datalen = connection_get_outbuf_len(TO_CONN(conn)); + if (datalen < OR_CONN_HIGHWATER) { + cell_network_size = get_cell_network_size(conn->wide_circ_ids); + n = CEIL_DIV(OR_CONN_HIGHWATER - datalen, cell_network_size); + } + + return n; +} + /** Connection conn has finished writing and has no bytes left on * its outbuf. * diff --git a/src/or/connection_or.h b/src/or/connection_or.h index 143540edd9..7b46d7d111 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -24,6 +24,7 @@ void connection_or_set_bad_connections(const char *digest, int force); void connection_or_block_renegotiation(or_connection_t *conn); int connection_or_reached_eof(or_connection_t *conn); int connection_or_process_inbuf(or_connection_t *conn); +ssize_t connection_or_num_cells_writeable(or_connection_t *conn); int connection_or_flushed_some(or_connection_t *conn); int connection_or_finished_flushing(or_connection_t *conn); int connection_or_finished_connecting(or_connection_t *conn); diff --git a/src/or/include.am b/src/or/include.am index 47bdd09901..b2b940614f 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -80,6 +80,7 @@ LIBTOR_A_SOURCES = \ src/or/routerlist.c \ src/or/routerparse.c \ src/or/routerset.c \ + src/or/scheduler.c \ src/or/statefile.c \ src/or/status.c \ $(evdns_source) \ @@ -185,6 +186,7 @@ ORHEADERS = \ src/or/routerlist.h \ src/or/routerset.h \ src/or/routerparse.h \ + src/or/scheduler.h \ src/or/statefile.h \ src/or/status.h diff --git a/src/or/main.c b/src/or/main.c index 094120fecf..1a2cfad255 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -52,6 +52,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "scheduler.h" #include "statefile.h" #include "status.h" #include "util_process.h" @@ -2455,6 +2456,14 @@ tor_init(int argc, char *argv[]) log_warn(LD_NET, "Problem initializing libevent RNG."); } + /* + * Initialize the scheduler - this has to come after + * options_init_from_torrc() sets up libevent - why yes, that seems + * completely sensible to hide the libevent setup in the option parsing + * code! + */ + scheduler_init(); + return 0; } @@ -2552,6 +2561,7 @@ tor_free_all(int postfork) channel_tls_free_all(); channel_free_all(); connection_free_all(); + scheduler_free_all(); buf_shrink_freelists(1); memarea_clear_freelist(); nodelist_free_all(); diff --git a/src/or/relay.c b/src/or/relay.c index d97c84fb07..2c16e4acca 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -39,6 +39,7 @@ #include "router.h" #include "routerlist.h" #include "routerparse.h" +#include "scheduler.h" static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, @@ -2868,6 +2869,10 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, log_debug(LD_GENERAL, "Made a circuit active."); } + /* New way: mark this as having waiting cells for the scheduler */ + scheduler_channel_has_waiting_cells(chan); + + /* TODO remove this once scheduler does it */ if (!channel_has_queued_writes(chan)) { /* There is no data at all waiting to be sent on the outbuf. Add a * cell, so that we can notice when it gets flushed, flushed_some can diff --git a/src/or/scheduler.c b/src/or/scheduler.c new file mode 100644 index 0000000000..451634a63f --- /dev/null +++ b/src/or/scheduler.c @@ -0,0 +1,411 @@ +/* * Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file scheduler.c + * \brief Relay scheduling system + **/ + +#include "or.h" +#include "channel.h" +#include "compat_libevent.h" +#include "scheduler.h" + +#ifdef HAVE_EVENT2_EVENT_H +#include +#else +#include +#endif + +/* + * Write scheduling works by keeping track of lists of channels that can + * accept cells, and have cells to write. From the scheduler's perspective, + * a channel can be in four possible states: + * + * 1.) Not open for writes, no cells to send + * - Not much to do here, and the channel will appear in neither list. + * - Transitions from: + * - Open for writes/has cells by simultaneously draining all circuit + * queues and filling the output buffer. + * - Transitions to: + * - Not open for writes/has cells by arrival of cells on an attached + * circuit (this would be driven from append_cell_to_circuit_queue()) + * - Open for writes/no cells by a channel type specific path; + * driven from connection_or_flushed_some() for channel_tls_t. + * + * 2.) Open for writes, no cells to send + * - Not much here either; this will be the state an idle but open channel + * can be expected to settle in. + * - Transitions from: + * - Not open for writes/no cells by flushing some of the output + * buffer. + * - Open for writes/has cells by the scheduler moving cells from + * circuit queues to channel output queue, but not having enough + * to fill the output queue. + * - Transitions to: + * - Open for writes/has cells by arrival of new cells on an attached + * circuit, in append_cell_to_circuit_queue() + * + * 3.) Not open for writes, cells to send + * - This is the state of a busy circuit limited by output bandwidth; + * cells have piled up in the circuit queues waiting to be relayed. + * - Transitions from: + * - Not open for writes/no cells by arrival of cells on an attached + * circuit + * - Open for writes/has cells by filling an output buffer without + * draining all cells from attached circuits + * - Transitions to: + * - Opens for writes/has cells by draining some of the output buffer + * via the connection_or_flushed_some() path (for channel_tls_t). + * + * 4.) Open for writes, cells to send + * - This connection is ready to relay some cells and waiting for + * the scheduler to choose it + * - Transitions from: + * - Not open for writes/has cells by the connection_or_flushed_some() + * path + * - Open for writes/no cells by the append_cell_to_circuit_queue() + * path + * - Transitions to: + * - Not open for writes/no cells by draining all circuit queues and + * simultaneously filling the output buffer. + * - Not open for writes/has cells by writing enough cells to fill the + * output buffer + * - Open for writes/no cells by draining all attached circuit queues + * without also filling the output buffer + * + * Other event-driven parts of the code move channels between these scheduling + * states by calling scheduler functions; the scheduler only runs on open-for- + * writes/has-cells channels and is the only path for those to transition to + * other states. The scheduler_run() function gives us the opportunity to do + * scheduling work, and is called from other scheduler functions whenever a + * state transition occurs, and periodically from the main event loop. + */ + +/* Scheduler global data structures */ + +/* + * We keep lists of channels that either have cells queued, can accept + * writes, or both (states 2, 3 and 4 above) - no explicit list of state + * 1 channels is kept, so we don't have to worry about registering new + * channels here or anything. The scheduler will learn about them when + * it needs to. We can check how many channels in state 4 in O(1), so + * the test whether we have anything to do in scheduler_run() is fast + * and there's no harm in calling it opportunistically whenever we get + * the chance. + * + * Note that it takes time O(n) to search for a channel in these smartlists + * or move one; I don't think the number of channels on a relay will be large + * enough for this to be a severe problem, but this would benefit from using + * a doubly-linked list rather than smartlist_t, together with a hash map from + * channel identifiers to pointers to list entries, so we can perform those + * operations in O(log(n)). + */ + +/* List of channels that can write but have no cells (state 2 above) */ +static smartlist_t *channels_waiting_for_cells = NULL; + +/* List of channels with cells waiting to write (state 3 above) */ +static smartlist_t *channels_waiting_to_write = NULL; + +/* List of channels that can write and have cells (pending work) */ +static smartlist_t *channels_pending = NULL; + +/* + * This event runs the scheduler from its callback, and is manually + * activated whenever a channel enters open for writes/cells to send. + */ + +static struct event *run_sched_ev = NULL; +static struct timeval run_sched_tv; + +/* Scheduler static function declarations */ + +static void scheduler_evt_callback(evutil_socket_t fd, + short events, void *arg); +static int scheduler_more_work(void); +static void scheduler_retrigger(void); +static void scheduler_trigger(void); + +/* Scheduler function implementations */ + +/** Free everything and shut down the scheduling system */ + +void +scheduler_free_all(void) +{ + log_debug(LD_SCHED, "Shutting down scheduler"); + + if (run_sched_ev) { + event_del(run_sched_ev); + tor_event_free(run_sched_ev); + run_sched_ev = NULL; + } + + if (channels_waiting_for_cells) { + smartlist_free(channels_waiting_for_cells); + channels_waiting_for_cells = NULL; + } + + if (channels_waiting_to_write) { + smartlist_free(channels_waiting_to_write); + channels_waiting_to_write = NULL; + } + + if (channels_pending) { + smartlist_free(channels_pending); + channels_pending = NULL; + } +} + +/* + * Scheduler event callback; this should get triggered once per event loop + * if any scheduling work was created during the event loop. + */ + +static void +scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) +{ + log_debug(LD_SCHED, "Scheduler event callback called"); + + tor_assert(run_sched_ev); + + /* Run the scheduler */ + scheduler_run(); + + /* Do we have more work to do? */ + if (scheduler_more_work()) scheduler_retrigger(); +} + +/** Mark a channel as no longer ready to accept writes */ + +void +scheduler_channel_doesnt_want_writes(channel_t *chan) +{ + tor_assert(chan); + tor_assert(channels_waiting_for_cells); + tor_assert(channels_waiting_to_write); + tor_assert(channels_pending); + + /* If it's already in pending, we can put it in waiting_to_write */ + if (smartlist_contains(channels_pending, chan)) { + /* + * It's in channels_pending, so it shouldn't be in any of + * the other lists. It can't write any more, so it goes to + * channels_waiting_to_write. + */ + smartlist_remove(channels_pending, chan); + smartlist_add(channels_waiting_to_write, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p went from pending " + "to waiting_to_write", + U64_PRINTF_ARG(chan->global_identifier), chan); + } else { + /* + * It's not in pending, so it can't become waiting_to_write; it's + * either not in any of the lists (nothing to do) or it's already in + * waiting_for_cells (remove it, can't write any more). + */ + if (smartlist_contains(channels_waiting_for_cells, chan)) { + smartlist_remove(channels_waiting_for_cells, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p left waiting_for_cells", + U64_PRINTF_ARG(chan->global_identifier), chan); + } + } +} + +/** Mark a channel as having waiting cells */ + +void +scheduler_channel_has_waiting_cells(channel_t *chan) +{ + int became_pending = 0; + + tor_assert(chan); + tor_assert(channels_waiting_for_cells); + tor_assert(channels_waiting_to_write); + tor_assert(channels_pending); + + /* First, check if this one also writeable */ + if (smartlist_contains(channels_waiting_for_cells, chan)) { + /* + * It's in channels_waiting_for_cells, so it shouldn't be in any of + * the other lists. It has waiting cells now, so it goes to + * channels_pending. + */ + smartlist_remove(channels_waiting_for_cells, chan); + smartlist_add(channels_pending, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p went from waiting_for_cells " + "to pending", + U64_PRINTF_ARG(chan->global_identifier), chan); + became_pending = 1; + } else { + /* + * It's not in waiting_for_cells, so it can't become pending; it's + * either not in any of the lists (we add it to waiting_to_write) + * or it's already in waiting_to_write or pending (we do nothing) + */ + if (!(smartlist_contains(channels_waiting_to_write, chan) || + smartlist_contains(channels_pending, chan))) { + smartlist_add(channels_waiting_to_write, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p entered waiting_to_write", + U64_PRINTF_ARG(chan->global_identifier), chan); + } + } + + /* + * If we made a channel pending, we potentially have scheduling work + * to do. + */ + if (became_pending) scheduler_retrigger(); +} + +/** Set up the scheduling system */ + +void +scheduler_init(void) +{ + log_debug(LD_SCHED, "Initting scheduler"); + + tor_assert(!run_sched_ev); + run_sched_ev = tor_event_new(tor_libevent_get_base(), -1, + 0, scheduler_evt_callback, NULL); + + channels_waiting_for_cells = smartlist_new(); + channels_waiting_to_write = smartlist_new(); + channels_pending = smartlist_new(); +} + +/** Check if there's more scheduling work */ + +static int +scheduler_more_work(void) +{ + tor_assert(channels_pending); + + return (smartlist_len(channels_pending) > 0) ? 1 : 0; +} + +/** Retrigger the scheduler in a way safe to use from the callback */ + +static void +scheduler_retrigger(void) +{ + tor_assert(run_sched_ev); + + if (!evtimer_pending(run_sched_ev, NULL)) { + log_debug(LD_SCHED, "Retriggering scheduler event"); + + event_del(run_sched_ev); + evtimer_add(run_sched_ev, &run_sched_tv); + } +} + +/** Notify the scheduler of a channel being closed */ + +void +scheduler_release_channel(channel_t *chan) +{ + tor_assert(chan); + + tor_assert(channels_waiting_for_cells); + tor_assert(channels_waiting_to_write); + tor_assert(channels_pending); + + smartlist_remove(channels_waiting_for_cells, chan); + smartlist_remove(channels_waiting_to_write, chan); + smartlist_remove(channels_pending, chan); +} + +/** Run the scheduling algorithm if necessary */ + +void +scheduler_run(void) +{ + smartlist_t *tmp = NULL; + + log_debug(LD_SCHED, "We have a chance to run the scheduler"); + + /* + * TODO make this work properly + * + * For now, just empty the pending list and log that we saw stuff in it + */ + + tmp = channels_pending; + channels_pending = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(tmp, channel_t *, chan) { + log_debug(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p", + U64_PRINTF_ARG(chan->global_identifier), chan); + } SMARTLIST_FOREACH_END(chan); + + smartlist_free(tmp); +} + +/** Trigger the scheduling event so we run the scheduler later */ + +static void +scheduler_trigger(void) +{ + log_debug(LD_SCHED, "Triggering scheduler event"); + + tor_assert(run_sched_ev); + + run_sched_tv.tv_sec = 0; + run_sched_tv.tv_usec = 0; + + evtimer_add(run_sched_ev, &run_sched_tv); +} + +/** Mark a channel as ready to accept writes */ + +void +scheduler_channel_wants_writes(channel_t *chan) +{ + int became_pending = 0; + + tor_assert(chan); + tor_assert(channels_waiting_for_cells); + tor_assert(channels_waiting_to_write); + tor_assert(channels_pending); + + /* If it's already in waiting_to_write, we can put it in pending */ + if (smartlist_contains(channels_waiting_to_write, chan)) { + /* + * It's in channels_waiting_to_write, so it shouldn't be in any of + * the other lists. It can write now, so it goes to channels_pending. + */ + smartlist_remove(channels_waiting_to_write, chan); + smartlist_add(channels_pending, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p went from waiting_to_write " + "to pending", + U64_PRINTF_ARG(chan->global_identifier), chan); + became_pending = 1; + } else { + /* + * It's not in waiting_to_write, so it can't become pending; it's + * either not in any of the lists (we add it to waiting_for_cells) + * or it's already in waiting_for_cells or pending (we do nothing) + */ + if (!(smartlist_contains(channels_waiting_for_cells, chan) || + smartlist_contains(channels_pending, chan))) { + smartlist_add(channels_waiting_for_cells, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p entered waiting_for_cells", + U64_PRINTF_ARG(chan->global_identifier), chan); + } + } + + /* + * If we made a channel pending, we potentially have scheduling work + * to do. + */ + if (became_pending) scheduler_retrigger(); +} + diff --git a/src/or/scheduler.h b/src/or/scheduler.h new file mode 100644 index 0000000000..b25e36e902 --- /dev/null +++ b/src/or/scheduler.h @@ -0,0 +1,31 @@ +/* * Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file scheduler.h + * \brief Header file for scheduler.c + **/ + +#ifndef TOR_SCHEDULER_H +#define TOR_SCHEDULER_H + +#include "or.h" +#include "channel.h" + +/* Global-visibility scheduler functions */ + +/* Set up and shut down the scheduler from main.c */ +void scheduler_free_all(void); +void scheduler_init(void); +void scheduler_run(void); + +/* Mark channels as having cells or wanting/not wanting writes */ +void scheduler_channel_doesnt_want_writes(channel_t *chan); +void scheduler_channel_has_waiting_cells(channel_t *chan); +void scheduler_channel_wants_writes(channel_t *chan); + +/* Notify the scheduler of a channel being closed */ +void scheduler_release_channel(channel_t *chan); + +#endif /* !defined(TOR_SCHEDULER_H) */ + From 08bea13c35275ae9f1cc49b6ff095a2b33686741 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Oct 2013 13:14:49 -0400 Subject: [PATCH 02/79] Temporarily disable scheduler_trigger as unused --- src/or/scheduler.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 451634a63f..7d852606e6 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -125,7 +125,9 @@ static void scheduler_evt_callback(evutil_socket_t fd, short events, void *arg); static int scheduler_more_work(void); static void scheduler_retrigger(void); +#if 0 static void scheduler_trigger(void); +#endif /* Scheduler function implementations */ @@ -349,6 +351,7 @@ scheduler_run(void) /** Trigger the scheduling event so we run the scheduler later */ +#if 0 static void scheduler_trigger(void) { @@ -361,6 +364,7 @@ scheduler_trigger(void) evtimer_add(run_sched_ev, &run_sched_tv); } +#endif /** Mark a channel as ready to accept writes */ From fc13184e44e4dd99d065ceff3068b09d713e3758 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Oct 2013 13:15:21 -0400 Subject: [PATCH 03/79] Fix unused-arguments warnings --- src/or/scheduler.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 7d852606e6..d965f3b845 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -168,6 +168,9 @@ scheduler_free_all(void) static void scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) { + (void)fd; + (void)events; + (void)arg; log_debug(LD_SCHED, "Scheduler event callback called"); tor_assert(run_sched_ev); From 85ee5b3095f60052412a0bbb1ef0a4ccd5b7c97e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Oct 2013 13:17:11 -0400 Subject: [PATCH 04/79] Use event_active, not 0-length timeouts. It's idempotent, too. --- src/or/scheduler.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index d965f3b845..e2dcdb5d32 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -117,7 +117,6 @@ static smartlist_t *channels_pending = NULL; */ static struct event *run_sched_ev = NULL; -static struct timeval run_sched_tv; /* Scheduler static function declarations */ @@ -300,13 +299,7 @@ static void scheduler_retrigger(void) { tor_assert(run_sched_ev); - - if (!evtimer_pending(run_sched_ev, NULL)) { - log_debug(LD_SCHED, "Retriggering scheduler event"); - - event_del(run_sched_ev); - evtimer_add(run_sched_ev, &run_sched_tv); - } + event_active(run_sched_ev, EV_TIMEOUT, 1); } /** Notify the scheduler of a channel being closed */ @@ -362,10 +355,7 @@ scheduler_trigger(void) tor_assert(run_sched_ev); - run_sched_tv.tv_sec = 0; - run_sched_tv.tv_usec = 0; - - evtimer_add(run_sched_ev, &run_sched_tv); + event_add(run_sched_ev, EV_TIMEOUT, 1); } #endif From 472b62bfe4edb2f5e332c997be2ec69bdf590660 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 14 Oct 2013 13:33:36 -0400 Subject: [PATCH 05/79] Uglify scheduler init logic to avoid crash on startup. Otherwise, when we authority try to do a self-test because of init-keys, if that self-test can't be launched for whatever reason and so we close the channel immediately, we crash. Yes, this a silly way for initialization to work. --- src/or/config.c | 9 +++++++++ src/or/main.c | 8 -------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index 16acec791c..4ae9fadfeb 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -43,6 +43,7 @@ #include "util.h" #include "routerlist.h" #include "routerset.h" +#include "scheduler.h" #include "statefile.h" #include "transports.h" #include "ext_orport.h" @@ -1040,6 +1041,14 @@ options_act_reversible(const or_options_t *old_options, char **msg) if (running_tor && !libevent_initialized) { init_libevent(options); libevent_initialized = 1; + + /* + * Initialize the scheduler - this has to come after + * options_init_from_torrc() sets up libevent - why yes, that seems + * completely sensible to hide the libevent setup in the option parsing + * code! It also needs to happen before init_keys(), so it needs to + * happen here too. How yucky. */ + scheduler_init(); } /* Adjust the port configuration so we can launch listeners. */ diff --git a/src/or/main.c b/src/or/main.c index 1a2cfad255..7b38f45b22 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -2456,14 +2456,6 @@ tor_init(int argc, char *argv[]) log_warn(LD_NET, "Problem initializing libevent RNG."); } - /* - * Initialize the scheduler - this has to come after - * options_init_from_torrc() sets up libevent - why yes, that seems - * completely sensible to hide the libevent setup in the option parsing - * code! - */ - scheduler_init(); - return 0; } From 2efbab2aaf98d8f8f0df504efd4fdd0fac77d354 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 29 Oct 2013 02:13:53 -0700 Subject: [PATCH 06/79] Provide generic mechanism for scheduler to query writeable cells on a channel --- src/or/channel.c | 34 ++++++++++++++++++++++++++++++++++ src/or/channel.h | 7 ++++++- src/or/channeltls.c | 30 ++++++++++++++++++++++++++++++ src/or/connection_or.c | 8 -------- src/or/or.h | 12 ++++++++++++ src/or/scheduler.c | 16 +++++++++++++--- 6 files changed, 95 insertions(+), 12 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index da2493a758..e9451f1e9b 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -3826,6 +3826,40 @@ channel_mark_outgoing(channel_t *chan) chan->is_incoming = 0; } +/************************ + * Flow control queries * + ***********************/ + +/* + * Estimate the number of writeable cells + * + * Ask the lower layer for an estimate of how many cells it can accept, and + * then subtract the length of our outgoing_queue, if any, to produce an + * estimate of the number of cells this channel can accept for writes. + */ + +int +channel_num_cells_writeable(channel_t *chan) +{ + int result; + + tor_assert(chan); + tor_assert(chan->num_cells_writeable); + + if (chan->state == CHANNEL_STATE_OPEN) { + /* Query lower layer */ + result = chan->num_cells_writeable(chan); + /* Subtract cell queue length, if any */ + result -= chan_cell_queue_len(&chan->outgoing_queue); + if (result < 0) result = 0; + } else { + /* No cells are writeable in any other state */ + result = 0; + } + + return result; +} + /********************* * Timestamp updates * ********************/ diff --git a/src/or/channel.h b/src/or/channel.h index 148199235a..28b5ab0ccf 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -110,7 +110,9 @@ struct channel_s { int (*matches_extend_info)(channel_t *, extend_info_t *); /** Check if this channel matches a target address when extending */ int (*matches_target)(channel_t *, const tor_addr_t *); - /** Write a cell to an open channel */ + /* Ask the lower layer how many cells can be written */ + int (*num_cells_writeable)(channel_t *); + /* Write a cell to an open channel */ int (*write_cell)(channel_t *, cell_t *); /** Write a packed cell to an open channel */ int (*write_packed_cell)(channel_t *, packed_cell_t *); @@ -465,6 +467,9 @@ void channel_listener_dump_statistics(channel_listener_t *chan_l, void channel_listener_dump_transport_statistics(channel_listener_t *chan_l, int severity); +/* Flow control queries */ +int channel_num_cells_writeable(channel_t *chan); + /* Timestamp queries */ time_t channel_when_created(channel_t *chan); time_t channel_when_last_active(channel_t *chan); diff --git a/src/or/channeltls.c b/src/or/channeltls.c index af1aab36d3..b828b152b5 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -68,6 +68,7 @@ channel_tls_matches_extend_info_method(channel_t *chan, extend_info_t *extend_info); static int channel_tls_matches_target_method(channel_t *chan, const tor_addr_t *target); +static int channel_tls_num_cells_writeable_method(channel_t *chan); static int channel_tls_write_cell_method(channel_t *chan, cell_t *cell); static int channel_tls_write_packed_cell_method(channel_t *chan, @@ -124,6 +125,7 @@ channel_tls_common_init(channel_tls_t *tlschan) chan->is_canonical = channel_tls_is_canonical_method; chan->matches_extend_info = channel_tls_matches_extend_info_method; chan->matches_target = channel_tls_matches_target_method; + chan->num_cells_writeable = channel_tls_num_cells_writeable_method; chan->write_cell = channel_tls_write_cell_method; chan->write_packed_cell = channel_tls_write_packed_cell_method; chan->write_var_cell = channel_tls_write_var_cell_method; @@ -673,6 +675,34 @@ channel_tls_matches_target_method(channel_t *chan, return tor_addr_eq(&(tlschan->conn->real_addr), target); } +/** + * Tell the upper layer how many cells we can accept to write + * + * This implements the num_cells_writeable method for channel_tls_t; it + * returns an estimate of the number of cells we can accept with + * channel_tls_write_*_cell(). + */ + +static int +channel_tls_num_cells_writeable_method(channel_t *chan) +{ + size_t outbuf_len; + int n; + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + size_t cell_network_size; + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + cell_network_size = get_cell_network_size(tlschan->conn->wide_circ_ids); + outbuf_len = connection_get_outbuf_len(TO_CONN(tlschan->conn)); + /* Get the number of cells */ + n = CEIL_DIV(OR_CONN_HIGHWATER - outbuf_len, cell_network_size); + if (n < 0) n = 0; + + return n; +} + /** * Write a cell to a channel_tls_t * diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 9074c0ac5f..a8f9d41923 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -576,14 +576,6 @@ connection_or_process_inbuf(or_connection_t *conn) return ret; } -/** When adding cells to an OR connection's outbuf, keep adding until the - * outbuf is at least this long, or we run out of cells. */ -#define OR_CONN_HIGHWATER (32*1024) - -/** Add cells to an OR connection's outbuf whenever the outbuf's data length - * drops below this size. */ -#define OR_CONN_LOWWATER (16*1024) - /** Called whenever we have flushed some data on an or_conn: add more data * from active circuits. */ int diff --git a/src/or/or.h b/src/or/or.h index b2b0d5f7ab..bdcb29ea11 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1426,6 +1426,18 @@ typedef struct or_handshake_state_t { /** Length of Extended ORPort connection identifier. */ #define EXT_OR_CONN_ID_LEN DIGEST_LEN /* 20 */ +/* + * OR_CONN_HIGHWATER and OR_CONN_LOWWATER moved from connection_or.c so + * channeltls.c can see them too. + */ + +/** When adding cells to an OR connection's outbuf, keep adding until the + * outbuf is at least this long, or we run out of cells. */ +#define OR_CONN_HIGHWATER (32*1024) + +/** Add cells to an OR connection's outbuf whenever the outbuf's data length + * drops below this size. */ +#define OR_CONN_LOWWATER (16*1024) /** Subtype of connection_t for an "OR connection" -- that is, one that speaks * cells over TLS. */ diff --git a/src/or/scheduler.c b/src/or/scheduler.c index e2dcdb5d32..7023eaae0a 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -324,6 +324,7 @@ void scheduler_run(void) { smartlist_t *tmp = NULL; + int n_cells; log_debug(LD_SCHED, "We have a chance to run the scheduler"); @@ -337,9 +338,18 @@ scheduler_run(void) channels_pending = smartlist_new(); SMARTLIST_FOREACH_BEGIN(tmp, channel_t *, chan) { - log_debug(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p", - U64_PRINTF_ARG(chan->global_identifier), chan); + n_cells = channel_num_cells_writeable(chan); + if (n_cells > 0) { + log_debug(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "%d cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); + } else { + log_info(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "no cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan); + } } SMARTLIST_FOREACH_END(chan); smartlist_free(tmp); From b09f41424c7e0da63733c2fdda5332d63a7f4e0f Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 31 Oct 2013 19:37:57 -0700 Subject: [PATCH 07/79] Actually call channel_flush_some_cells() from the scheduler --- src/or/connection_or.c | 29 +---------------------------- src/or/scheduler.c | 16 ++++++++++------ 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/or/connection_or.c b/src/or/connection_or.c index a8f9d41923..6b276cc3a1 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -581,9 +581,7 @@ connection_or_process_inbuf(or_connection_t *conn) int connection_or_flushed_some(or_connection_t *conn) { - size_t datalen, temp; - ssize_t n, flushed; - size_t cell_network_size = get_cell_network_size(conn->wide_circ_ids); + size_t datalen; /* If we're under the low water mark, add cells until we're just over the * high water mark. */ @@ -591,31 +589,6 @@ connection_or_flushed_some(or_connection_t *conn) if (datalen < OR_CONN_LOWWATER) { /* Let the scheduler know */ scheduler_channel_wants_writes(TLS_CHAN_TO_BASE(conn->chan)); - - /* - * TODO this will be done from the scheduler, so it will - * need a generic way to ask how many cells a channel can - * accept and if it still wants writes or not to know how - * to account for it in the case that it runs out of cells - * to send first. - */ - - while ((conn->chan) && channel_tls_more_to_flush(conn->chan)) { - /* Compute how many more cells we want at most */ - n = CEIL_DIV(OR_CONN_HIGHWATER - datalen, cell_network_size); - /* Bail out if we don't want any more */ - if (n <= 0) break; - /* We're still here; try to flush some more cells */ - flushed = channel_tls_flush_some_cells(conn->chan, n); - /* Bail out if it says it didn't flush anything */ - if (flushed <= 0) break; - /* How much in the outbuf now? */ - temp = connection_get_outbuf_len(TO_CONN(conn)); - /* Bail out if we didn't actually increase the outbuf size */ - if (temp <= datalen) break; - /* Update datalen for the next iteration */ - datalen = temp; - } } return 0; diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 7023eaae0a..1c6a2fdecb 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -325,18 +325,15 @@ scheduler_run(void) { smartlist_t *tmp = NULL; int n_cells; + ssize_t flushed, flushed_this_time; log_debug(LD_SCHED, "We have a chance to run the scheduler"); - /* - * TODO make this work properly - * - * For now, just empty the pending list and log that we saw stuff in it - */ - tmp = channels_pending; channels_pending = smartlist_new(); + /* For now, just run the old scheduler on all the chans in the list */ + SMARTLIST_FOREACH_BEGIN(tmp, channel_t *, chan) { n_cells = channel_num_cells_writeable(chan); if (n_cells > 0) { @@ -344,6 +341,13 @@ scheduler_run(void) "Scheduler saw pending channel " U64_FORMAT " at %p with " "%d cells writeable", U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); + + flushed = 0; + while (flushed < n_cells) { + flushed_this_time = channel_flush_some_cells(chan, n_cells - flushed); + if (flushed_this_time <= 0) break; + flushed += flushed_this_time; + } } else { log_info(LD_SCHED, "Scheduler saw pending channel " U64_FORMAT " at %p with " From f0533d8d223c40e487bbddc46256856f80e96389 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 31 Oct 2013 20:59:06 -0700 Subject: [PATCH 08/79] Remove no-longer-used channel_tls_t functions --- src/or/channeltls.c | 52 --------------------------------------------- src/or/channeltls.h | 2 -- 2 files changed, 54 deletions(-) diff --git a/src/or/channeltls.c b/src/or/channeltls.c index b828b152b5..7df2d35d93 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -913,58 +913,6 @@ channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, } } -/** - * Flush cells from a channel_tls_t - * - * Try to flush up to about num_cells cells, and return how many we flushed. - */ - -ssize_t -channel_tls_flush_some_cells(channel_tls_t *chan, ssize_t num_cells) -{ - ssize_t flushed = 0; - - tor_assert(chan); - - if (flushed >= num_cells) goto done; - - /* - * If channel_tls_t ever buffers anything below the channel_t layer, flush - * that first here. - */ - - flushed += channel_flush_some_cells(TLS_CHAN_TO_BASE(chan), - num_cells - flushed); - - /* - * If channel_tls_t ever buffers anything below the channel_t layer, check - * how much we actually got and push it on down here. - */ - - done: - return flushed; -} - -/** - * Check if a channel_tls_t has anything to flush - * - * Return true if there is any more to flush on this channel (cells in queue - * or active circuits). - */ - -int -channel_tls_more_to_flush(channel_tls_t *chan) -{ - tor_assert(chan); - - /* - * If channel_tls_t ever buffers anything below channel_t, the - * check for that should go here first. - */ - - return channel_more_to_flush(TLS_CHAN_TO_BASE(chan)); -} - #ifdef KEEP_TIMING_STATS /** diff --git a/src/or/channeltls.h b/src/or/channeltls.h index c872a09d79..5eb6e126bc 100644 --- a/src/or/channeltls.h +++ b/src/or/channeltls.h @@ -40,8 +40,6 @@ channel_t * channel_tls_to_base(channel_tls_t *tlschan); channel_tls_t * channel_tls_from_base(channel_t *chan); /* Things for connection_or.c to call back into */ -ssize_t channel_tls_flush_some_cells(channel_tls_t *chan, ssize_t num_cells); -int channel_tls_more_to_flush(channel_tls_t *chan); void channel_tls_handle_cell(cell_t *cell, or_connection_t *conn); void channel_tls_handle_state_change_on_orconn(channel_tls_t *chan, or_connection_t *conn, From 5e0a6d54d0f1341eddc26a6fa6d349e22966c255 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Mon, 4 Nov 2013 19:17:05 -0800 Subject: [PATCH 09/79] Add global cell/byte counters and per channel byte counters to channel.c --- src/or/channel.c | 165 ++++++++++++++++++++++++++++++++++++++++++++++- src/or/channel.h | 4 +- 2 files changed, 164 insertions(+), 5 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index e9451f1e9b..0caebfb55c 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -77,6 +77,53 @@ static smartlist_t *finished_listeners = NULL; /* Counter for ID numbers */ static uint64_t n_channels_allocated = 0; +/* + * Channel global byte/cell counters, for statistics and for scheduler high + * /low-water marks. + */ + +/* + * Total number of cells ever given to any channel with the + * channel_write_*_cell() functions. + */ + +static uint64_t n_channel_cells_queued = 0; + +/* + * Total number of cells ever passed to a channel lower layer with the + * write_*_cell() methods. + */ + +static uint64_t n_channel_cells_passed_to_lower_layer = 0; + +/* + * Current number of cells in all channel queues; should be + * n_channel_cells_queued - n_channel_cells_passed_to_lower_layer. + */ + +static uint64_t n_channel_cells_in_queues = 0; + +/* + * Total number of bytes for all cells ever queued to a channel and + * counted in n_channel_cells_queued. + */ + +static uint64_t n_channel_bytes_queued = 0; + +/* + * Total number of bytes for all cells ever passed to a channel lower layer + * and counted in n_channel_cells_passed_to_lower_layer. + */ + +static uint64_t n_channel_bytes_passed_to_lower_layer = 0; + +/* + * Current number of bytes in all channel queues; should be + * n_channel_bytes_queued - n_channel_bytes_passed_to_lower_layer. + */ + +static uint64_t n_channel_bytes_in_queues = 0; + /* Digest->channel map * * Similar to the one used in connection_or.c, this maps from the identity @@ -124,6 +171,8 @@ cell_queue_entry_new_var(var_cell_t *var_cell); static int is_destroy_cell(channel_t *chan, const cell_queue_entry_t *q, circid_t *circid_out); +static void channel_assert_counter_consistency(void); + /* Functions to maintain the digest map */ static void channel_add_to_digest_map(channel_t *chan); static void channel_remove_from_digest_map(channel_t *chan); @@ -141,6 +190,8 @@ channel_free_list(smartlist_t *channels, int mark_for_close); static void channel_listener_free_list(smartlist_t *channels, int mark_for_close); static void channel_listener_force_free(channel_listener_t *chan_l); +static size_t channel_get_cell_queue_entry_size(channel_t *chan, + cell_queue_entry_t *q); static void channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q); @@ -1672,6 +1723,34 @@ cell_queue_entry_new_var(var_cell_t *var_cell) return q; } +/** + * Ask how big the cell contained in a cell_queue_entry_t is + */ + +static size_t +channel_get_cell_queue_entry_size(channel_t *chan, cell_queue_entry_t *q) +{ + tor_assert(chan); + tor_assert(q); + + switch (q->type) { + case CELL_QUEUE_FIXED: + return get_cell_network_size(chan->wide_circ_ids); + break; + case CELL_QUEUE_VAR: + tor_assert(q->u.var.var_cell); + return get_var_cell_header_size(chan->wide_circ_ids) + + q->u.var.var_cell->payload_len; + break; + case CELL_QUEUE_PACKED: + return get_cell_network_size(chan->wide_circ_ids); + break; + default: + tor_assert(1); + return 0; + } +} + /** * Write to a channel based on a cell_queue_entry_t * @@ -1684,6 +1763,7 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) { int result = 0, sent = 0; cell_queue_entry_t *tmp = NULL; + size_t cell_bytes; tor_assert(chan); tor_assert(q); @@ -1700,6 +1780,9 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) } } + /* For statistical purposes, figure out how big this cell is */ + cell_bytes = channel_get_cell_queue_entry_size(chan, q); + /* Can we send it right out? If so, try */ if (TOR_SIMPLEQ_EMPTY(&chan->outgoing_queue) && chan->state == CHANNEL_STATE_OPEN) { @@ -1733,6 +1816,13 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) channel_timestamp_drained(chan); /* Update the counter */ ++(chan->n_cells_xmitted); + chan->n_bytes_xmitted += cell_bytes; + /* Update global counters */ + ++n_channel_cells_queued; + ++n_channel_cells_passed_to_lower_layer; + n_channel_bytes_queued += cell_bytes; + n_channel_bytes_passed_to_lower_layer += cell_bytes; + channel_assert_counter_consistency(); } } @@ -1744,6 +1834,12 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) */ tmp = cell_queue_entry_dup(q); TOR_SIMPLEQ_INSERT_TAIL(&chan->outgoing_queue, tmp, next); + /* Update global counters */ + ++n_channel_cells_queued; + ++n_channel_cells_in_queues; + n_channel_bytes_queued += cell_bytes; + n_channel_bytes_in_queues += cell_bytes; + channel_assert_counter_consistency(); /* Try to process the queue? */ if (chan->state == CHANNEL_STATE_OPEN) channel_flush_cells(chan); } @@ -2136,6 +2232,7 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, unsigned int unlimited = 0; ssize_t flushed = 0; cell_queue_entry_t *q = NULL; + size_t cell_size; tor_assert(chan); tor_assert(chan->write_cell); @@ -2151,6 +2248,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, NULL != (q = TOR_SIMPLEQ_FIRST(&chan->outgoing_queue))) { if (1) { + /* Figure out how big it is for statistical purposes */ + cell_size = channel_get_cell_queue_entry_size(chan, q); /* * Okay, we have a good queue entry, try to give it to the lower * layer. @@ -2163,6 +2262,7 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, ++flushed; channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); + chan->n_bytes_xmitted += cell_size; cell_queue_entry_free(q, 1); q = NULL; } @@ -2186,6 +2286,7 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, ++flushed; channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); + chan->n_bytes_xmitted += cell_size; cell_queue_entry_free(q, 1); q = NULL; } @@ -2209,6 +2310,7 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, ++flushed; channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); + chan->n_bytes_xmitted += cell_size; cell_queue_entry_free(q, 1); q = NULL; } @@ -2237,7 +2339,18 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, } /* if q got NULLed out, we used it and should remove the queue entry */ - if (!q) TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next); + if (!q) { + TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next); + /* + * ...and we handed a cell off to the lower layer, so we should + * update the counters. + */ + ++n_channel_cells_passed_to_lower_layer; + --n_channel_cells_in_queues; + n_channel_bytes_passed_to_lower_layer += cell_size; + n_channel_bytes_in_queues -= cell_size; + channel_assert_counter_consistency(); + } /* No cell removed from list, so we can't go on any further */ else break; } @@ -2560,8 +2673,9 @@ channel_queue_cell(channel_t *chan, cell_t *cell) /* Timestamp for receiving */ channel_timestamp_recv(chan); - /* Update the counter */ + /* Update the counters */ ++(chan->n_cells_recved); + chan->n_bytes_recved += get_cell_network_size(chan->wide_circ_ids); /* If we don't need to queue we can just call cell_handler */ if (!need_to_queue) { @@ -2615,6 +2729,8 @@ channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell) /* Update the counter */ ++(chan->n_cells_recved); + chan->n_bytes_recved += get_var_cell_header_size(chan->wide_circ_ids) + + var_cell->payload_len; /* If we don't need to queue we can just call cell_handler */ if (!need_to_queue) { @@ -2664,6 +2780,19 @@ packed_cell_is_destroy(channel_t *chan, return 0; } +/** + * Assert that the global channel stats counters are internally consistent + */ + +static void +channel_assert_counter_consistency(void) +{ + tor_assert(n_channel_cells_queued == + (n_channel_cells_in_queues + n_channel_cells_passed_to_lower_layer)); + tor_assert(n_channel_bytes_queued == + (n_channel_bytes_in_queues + n_channel_bytes_passed_to_lower_layer)); +} + /** DOCDOC */ static int is_destroy_cell(channel_t *chan, @@ -2745,6 +2874,19 @@ void channel_dumpstats(int severity) { if (all_channels && smartlist_len(all_channels) > 0) { + tor_log(severity, LD_GENERAL, + "Channels have queued " U64_FORMAT " bytes in " U64_FORMAT " cells, " + "and handed " U64_FORMAT " bytes in " U64_FORMAT " cells to the lower" + " layer.", + U64_PRINTF_ARG(n_channel_bytes_queued), + U64_PRINTF_ARG(n_channel_cells_queued), + U64_PRINTF_ARG(n_channel_bytes_passed_to_lower_layer), + U64_PRINTF_ARG(n_channel_cells_passed_to_lower_layer)); + tor_log(severity, LD_GENERAL, + "There are currently " U64_FORMAT " bytes in " U64_FORMAT " cells " + "in channel queues.", + U64_PRINTF_ARG(n_channel_bytes_in_queues), + U64_PRINTF_ARG(n_channel_cells_in_queues)); tor_log(severity, LD_GENERAL, "Dumping statistics about %d channels:", smartlist_len(all_channels)); @@ -3388,12 +3530,22 @@ channel_dump_statistics(channel_t *chan, int severity) /* Describe counters and rates */ tor_log(severity, LD_GENERAL, " * Channel " U64_FORMAT " has received " - U64_FORMAT " cells and transmitted " U64_FORMAT, + U64_FORMAT " bytes in " U64_FORMAT " cells and transmitted " + U64_FORMAT " bytes in " U64_FORMAT " cells", U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(chan->n_bytes_recved), U64_PRINTF_ARG(chan->n_cells_recved), + U64_PRINTF_ARG(chan->n_bytes_xmitted), U64_PRINTF_ARG(chan->n_cells_xmitted)); if (now > chan->timestamp_created && chan->timestamp_created > 0) { + if (chan->n_bytes_recved > 0) { + avg = (double)(chan->n_bytes_recved) / age; + tor_log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has averaged %f " + "bytes received per second", + U64_PRINTF_ARG(chan->global_identifier), avg); + } if (chan->n_cells_recved > 0) { avg = (double)(chan->n_cells_recved) / age; if (avg >= 1.0) { @@ -3409,6 +3561,13 @@ channel_dump_statistics(channel_t *chan, int severity) U64_PRINTF_ARG(chan->global_identifier), interval); } } + if (chan->n_bytes_xmitted > 0) { + avg = (double)(chan->n_bytes_xmitted) / age; + tor_log(severity, LD_GENERAL, + " * Channel " U64_FORMAT " has averaged %f " + "bytes transmitted per second", + U64_PRINTF_ARG(chan->global_identifier), avg); + } if (chan->n_cells_xmitted > 0) { avg = (double)(chan->n_cells_xmitted) / age; if (avg >= 1.0) { diff --git a/src/or/channel.h b/src/or/channel.h index 28b5ab0ccf..f35cb27aa6 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -200,8 +200,8 @@ struct channel_s { uint64_t dirreq_id; /** Channel counters for cell channels */ - uint64_t n_cells_recved; - uint64_t n_cells_xmitted; + uint64_t n_cells_recved, n_bytes_recved; + uint64_t n_cells_xmitted, n_bytes_xmitted; }; struct channel_listener_s { From 7674308f622904bc5a4cff5e7cb4d0f22fbf84d7 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Mon, 4 Nov 2013 22:40:24 -0800 Subject: [PATCH 10/79] Make 'make check-spaces' not complain about function pointers returning size_t or double --- scripts/maint/checkSpace.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/maint/checkSpace.pl b/scripts/maint/checkSpace.pl index b529103367..c785d89567 100755 --- a/scripts/maint/checkSpace.pl +++ b/scripts/maint/checkSpace.pl @@ -128,7 +128,8 @@ for $fn (@ARGV) { if ($1 ne "if" and $1 ne "while" and $1 ne "for" and $1 ne "switch" and $1 ne "return" and $1 ne "int" and $1 ne "elsif" and $1 ne "WINAPI" and $2 ne "WINAPI" and - $1 ne "void" and $1 ne "__attribute__" and $1 ne "op") { + $1 ne "void" and $1 ne "__attribute__" and $1 ne "op" and + $1 ne "size_t" and $1 ne "double") { print " fn ():$fn:$.\n"; } } From 8852a1794cfa9eb5dae494f5d85242d8fd6955fc Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 5 Nov 2013 00:30:02 -0800 Subject: [PATCH 11/79] Track total queue size per channel, with overhead estimates, and global queue total --- src/or/channel.c | 126 +++++++++++++++++++++++++++++++++++++++++ src/or/channel.h | 16 ++++++ src/or/channeltls.c | 54 ++++++++++++++++++ src/or/connection.c | 2 + src/or/connection_or.c | 3 + src/or/or.h | 6 ++ 6 files changed, 207 insertions(+) diff --git a/src/or/channel.c b/src/or/channel.c index 0caebfb55c..f729a17be9 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -124,6 +124,13 @@ static uint64_t n_channel_bytes_passed_to_lower_layer = 0; static uint64_t n_channel_bytes_in_queues = 0; +/* + * Current total estimated queue size *including lower layer queues and + * transmit overhead* + */ + +static uint64_t estimated_total_queue_size = 0; + /* Digest->channel map * * Similar to the one used in connection_or.c, this maps from the identity @@ -1840,6 +1847,8 @@ channel_write_cell_queue_entry(channel_t *chan, cell_queue_entry_t *q) n_channel_bytes_queued += cell_bytes; n_channel_bytes_in_queues += cell_bytes; channel_assert_counter_consistency(); + /* Update channel queue size */ + chan->bytes_in_queue += cell_bytes; /* Try to process the queue? */ if (chan->state == CHANNEL_STATE_OPEN) channel_flush_cells(chan); } @@ -1878,6 +1887,9 @@ channel_write_cell(channel_t *chan, cell_t *cell) q.type = CELL_QUEUE_FIXED; q.u.fixed.cell = cell; channel_write_cell_queue_entry(chan, &q); + + /* Update the queue size estimate */ + channel_update_xmit_queue_size(chan); } /** @@ -1913,6 +1925,9 @@ channel_write_packed_cell(channel_t *chan, packed_cell_t *packed_cell) q.type = CELL_QUEUE_PACKED; q.u.packed.packed_cell = packed_cell; channel_write_cell_queue_entry(chan, &q); + + /* Update the queue size estimate */ + channel_update_xmit_queue_size(chan); } /** @@ -1949,6 +1964,9 @@ channel_write_var_cell(channel_t *chan, var_cell_t *var_cell) q.type = CELL_QUEUE_VAR; q.u.var.var_cell = var_cell; channel_write_cell_queue_entry(chan, &q); + + /* Update the queue size estimate */ + channel_update_xmit_queue_size(chan); } /** @@ -2056,6 +2074,29 @@ channel_change_state(channel_t *chan, channel_state_t to_state) scheduler_channel_doesnt_want_writes(chan); } + /* + * If we're closing, this channel no longer counts toward the global + * estimated queue size; if we're open, it now does. + */ + if ((to_state == CHANNEL_STATE_CLOSING || + to_state == CHANNEL_STATE_CLOSED || + to_state == CHANNEL_STATE_ERROR) && + (from_state == CHANNEL_STATE_OPEN || + from_state == CHANNEL_STATE_MAINT)) { + estimated_total_queue_size -= chan->bytes_in_queue; + } + + /* + * If we're opening, this channel now does count toward the global + * estimated queue size. + */ + if ((to_state == CHANNEL_STATE_OPEN || + to_state == CHANNEL_STATE_MAINT) && + !(from_state == CHANNEL_STATE_OPEN || + from_state == CHANNEL_STATE_MAINT)) { + estimated_total_queue_size += chan->bytes_in_queue; + } + /* Tell circuits if we opened and stuff */ if (to_state == CHANNEL_STATE_OPEN) { channel_do_open_actions(chan); @@ -2350,6 +2391,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, n_channel_bytes_passed_to_lower_layer += cell_size; n_channel_bytes_in_queues -= cell_size; channel_assert_counter_consistency(); + /* Update the channel's queue size too */ + chan->bytes_in_queue -= cell_size; } /* No cell removed from list, so we can't go on any further */ else break; @@ -2362,6 +2405,9 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, channel_timestamp_drained(chan); } + /* Update the estimate queue size */ + channel_update_xmit_queue_size(chan); + return flushed; } @@ -4421,3 +4467,83 @@ channel_set_circid_type(channel_t *chan, } } +/** + * Update the estimated number of bytes queued to transmit for this channel, + * and notify the scheduler. The estimate includes both the channel queue and + * the queue size reported by the lower layer, and an overhead estimate + * optionally provided by the lower layer. + */ + +void +channel_update_xmit_queue_size(channel_t *chan) +{ + uint64_t queued, adj; + double overhead; + + tor_assert(chan); + tor_assert(chan->num_bytes_queued); + + /* + * First, get the number of bytes we have queued without factoring in + * lower-layer overhead. + */ + queued = chan->num_bytes_queued(chan) + chan->bytes_in_queue; + /* Next, adjust by the overhead factor, if any is available */ + if (chan->get_overhead_estimate) { + overhead = chan->get_overhead_estimate(chan); + if (overhead >= 1.0f) { + queued *= overhead; + } else { + /* Ignore silly overhead factors */ + log_notice(LD_CHANNEL, "Ignoring silly overhead factor %f", overhead); + } + } + + /* Now, compare to the previous estimate */ + if (queued > chan->bytes_queued_for_xmit) { + adj = queued - chan->bytes_queued_for_xmit; + log_debug(LD_CHANNEL, + "Increasing queue size for channel " U64_FORMAT " by " U64_FORMAT + " from " U64_FORMAT " to " U64_FORMAT, + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(adj), + U64_PRINTF_ARG(chan->bytes_queued_for_xmit), + U64_PRINTF_ARG(queued)); + /* Update the channel's estimate */ + chan->bytes_queued_for_xmit = queued; + + /* Update the global queue size estimate if appropriate */ + if (chan->state == CHANNEL_STATE_OPEN || + chan->state == CHANNEL_STATE_MAINT) { + estimated_total_queue_size += adj; + log_debug(LD_CHANNEL, + "Increasing global queue size by " U64_FORMAT " for channel " + U64_FORMAT ", new size is " U64_FORMAT, + U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(estimated_total_queue_size)); + } + } else if (queued < chan->bytes_queued_for_xmit) { + adj = chan->bytes_queued_for_xmit - queued; + log_debug(LD_CHANNEL, + "Decreasing queue size for channel " U64_FORMAT " by " U64_FORMAT + " from " U64_FORMAT " to " U64_FORMAT, + U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(adj), + U64_PRINTF_ARG(chan->bytes_queued_for_xmit), + U64_PRINTF_ARG(queued)); + /* Update the channel's estimate */ + chan->bytes_queued_for_xmit = queued; + + /* Update the global queue size estimate if appropriate */ + if (chan->state == CHANNEL_STATE_OPEN || + chan->state == CHANNEL_STATE_MAINT) { + estimated_total_queue_size -= adj; + log_debug(LD_CHANNEL, + "Decreasing global queue size by " U64_FORMAT " for channel " + U64_FORMAT ", new size is " U64_FORMAT, + U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), + U64_PRINTF_ARG(estimated_total_queue_size)); + } + } +} + diff --git a/src/or/channel.h b/src/or/channel.h index f35cb27aa6..388c729953 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -79,6 +79,11 @@ struct channel_s { /* Methods implemented by the lower layer */ /** + * Ask the lower layer for an estimate of the average overhead for + * transmissions on this channel. + */ + double (*get_overhead_estimate)(channel_t *); + /* * Ask the underlying transport what the remote endpoint address is, in * a tor_addr_t. This is optional and subclasses may leave this NULL. * If they implement it, they should write the address out to the @@ -110,6 +115,8 @@ struct channel_s { int (*matches_extend_info)(channel_t *, extend_info_t *); /** Check if this channel matches a target address when extending */ int (*matches_target)(channel_t *, const tor_addr_t *); + /* Ask the lower layer how many bytes it has queued but not yet sent */ + size_t (*num_bytes_queued)(channel_t *); /* Ask the lower layer how many cells can be written */ int (*num_cells_writeable)(channel_t *); /* Write a cell to an open channel */ @@ -202,6 +209,14 @@ struct channel_s { /** Channel counters for cell channels */ uint64_t n_cells_recved, n_bytes_recved; uint64_t n_cells_xmitted, n_bytes_xmitted; + + /** Our current contribution to the scheduler's total xmit queue */ + uint64_t bytes_queued_for_xmit; + + /** Number of bytes in this channel's cell queue; does not include + * lower-layer queueing. + */ + uint64_t bytes_in_queue; }; struct channel_listener_s { @@ -460,6 +475,7 @@ unsigned int channel_num_circuits(channel_t *chan); void channel_set_circid_type(channel_t *chan, crypto_pk_t *identity_rcvd, int consider_identity); void channel_timestamp_client(channel_t *chan); +void channel_update_xmit_queue_size(channel_t *chan); const char * channel_listener_describe_transport(channel_listener_t *chan_l); void channel_listener_dump_statistics(channel_listener_t *chan_l, diff --git a/src/or/channeltls.c b/src/or/channeltls.c index 7df2d35d93..a8222dea35 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -55,6 +55,7 @@ static void channel_tls_common_init(channel_tls_t *tlschan); static void channel_tls_close_method(channel_t *chan); static const char * channel_tls_describe_transport_method(channel_t *chan); static void channel_tls_free_method(channel_t *chan); +static double channel_tls_get_overhead_estimate_method(channel_t *chan); static int channel_tls_get_remote_addr_method(channel_t *chan, tor_addr_t *addr_out); static int @@ -69,6 +70,7 @@ channel_tls_matches_extend_info_method(channel_t *chan, static int channel_tls_matches_target_method(channel_t *chan, const tor_addr_t *target); static int channel_tls_num_cells_writeable_method(channel_t *chan); +static size_t channel_tls_num_bytes_queued_method(channel_t *chan); static int channel_tls_write_cell_method(channel_t *chan, cell_t *cell); static int channel_tls_write_packed_cell_method(channel_t *chan, @@ -118,6 +120,7 @@ channel_tls_common_init(channel_tls_t *tlschan) chan->close = channel_tls_close_method; chan->describe_transport = channel_tls_describe_transport_method; chan->free = channel_tls_free_method; + chan->get_overhead_estimate = channel_tls_get_overhead_estimate_method; chan->get_remote_addr = channel_tls_get_remote_addr_method; chan->get_remote_descr = channel_tls_get_remote_descr_method; chan->get_transport_name = channel_tls_get_transport_name_method; @@ -125,6 +128,7 @@ channel_tls_common_init(channel_tls_t *tlschan) chan->is_canonical = channel_tls_is_canonical_method; chan->matches_extend_info = channel_tls_matches_extend_info_method; chan->matches_target = channel_tls_matches_target_method; + chan->num_bytes_queued = channel_tls_num_bytes_queued_method; chan->num_cells_writeable = channel_tls_num_cells_writeable_method; chan->write_cell = channel_tls_write_cell_method; chan->write_packed_cell = channel_tls_write_packed_cell_method; @@ -437,6 +441,40 @@ channel_tls_free_method(channel_t *chan) } } +/** + * Get an estimate of the average TLS overhead for the upper layer + */ + +static double +channel_tls_get_overhead_estimate_method(channel_t *chan) +{ + double overhead = 1.0f; + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + /* Just return 1.0f if we don't have sensible data */ + if (tlschan->conn->bytes_xmitted > 0 && + tlschan->conn->bytes_xmitted_by_tls >= + tlschan->conn->bytes_xmitted) { + overhead = ((double)(tlschan->conn->bytes_xmitted_by_tls)) / + ((double)(tlschan->conn->bytes_xmitted)); + + /* + * Never estimate more than 2.0; otherwise we get silly large estimates + * at the very start of a new TLS connection. + */ + if (overhead > 2.0f) overhead = 2.0f; + } + + log_debug(LD_CHANNEL, + "Estimated overhead ratio for TLS chan " U64_FORMAT " is %f", + U64_PRINTF_ARG(chan->global_identifier), overhead); + + return overhead; +} + /** * Get the remote address of a channel_tls_t * @@ -675,6 +713,22 @@ channel_tls_matches_target_method(channel_t *chan, return tor_addr_eq(&(tlschan->conn->real_addr), target); } +/** + * Tell the upper layer how many bytes we have queued and not yet + * sent. + */ + +static size_t +channel_tls_num_bytes_queued_method(channel_t *chan) +{ + channel_tls_t *tlschan = BASE_CHAN_TO_TLS(chan); + + tor_assert(tlschan); + tor_assert(tlschan->conn); + + return connection_get_outbuf_len(TO_CONN(tlschan->conn)); +} + /** * Tell the upper layer how many cells we can accept to write * diff --git a/src/or/connection.c b/src/or/connection.c index 4a3bd2cf03..525f4b5e85 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -3839,6 +3839,8 @@ connection_handle_write_impl(connection_t *conn, int force) tor_tls_get_n_raw_bytes(or_conn->tls, &n_read, &n_written); log_debug(LD_GENERAL, "After TLS write of %d: %ld read, %ld written", result, (long)n_read, (long)n_written); + or_conn->bytes_xmitted += result; + or_conn->bytes_xmitted_by_tls += n_written; /* So we notice bytes were written even on error */ /* XXXX024 This cast is safe since we can never write INT_MAX bytes in a * single set of TLS operations. But it looks kinda ugly. If we refactor diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 6b276cc3a1..445335b43a 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -583,6 +583,9 @@ connection_or_flushed_some(or_connection_t *conn) { size_t datalen; + /* The channel will want to update its estimated queue size */ + channel_update_xmit_queue_size(TLS_CHAN_TO_BASE(conn->chan)); + /* If we're under the low water mark, add cells until we're just over the * high water mark. */ datalen = connection_get_outbuf_len(TO_CONN(conn)); diff --git a/src/or/or.h b/src/or/or.h index bdcb29ea11..320931d471 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1529,6 +1529,12 @@ typedef struct or_connection_t { /** Last emptied write token bucket in msec since midnight; only used if * TB_EMPTY events are enabled. */ uint32_t write_emptied_time; + + /* + * Count the number of bytes flushed out on this orconn, and the number of + * bytes TLS actually sent - used for overhead estimation for scheduling. + */ + uint64_t bytes_xmitted, bytes_xmitted_by_tls; } or_connection_t; /** Subtype of connection_t for an "edge connection" -- that is, an entry (ap) From 2fc3da3ff51f3fe7fa5338c2d6b52a06ed9c4f19 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Mon, 11 Nov 2013 21:50:16 -0800 Subject: [PATCH 12/79] Implement global queue size query in channel.c --- src/or/channel.c | 10 ++++++++++ src/or/channel.h | 1 + 2 files changed, 11 insertions(+) diff --git a/src/or/channel.c b/src/or/channel.c index f729a17be9..e2d102d2ac 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -4035,6 +4035,16 @@ channel_mark_outgoing(channel_t *chan) * Flow control queries * ***********************/ +/* + * Get the latest estimate for the total queue size of all open channels + */ + +uint64_t +channel_get_global_queue_estimate(void) +{ + return estimated_total_queue_size; +} + /* * Estimate the number of writeable cells * diff --git a/src/or/channel.h b/src/or/channel.h index 388c729953..18f7cfc01e 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -484,6 +484,7 @@ void channel_listener_dump_transport_statistics(channel_listener_t *chan_l, int severity); /* Flow control queries */ +uint64_t channel_get_global_queue_estimate(void); int channel_num_cells_writeable(channel_t *chan); /* Timestamp queries */ From f314d9509cae7acb9a692895c709e7771c6321f0 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 13 Nov 2013 16:50:26 -0800 Subject: [PATCH 13/79] Fix return values from channel_flush_some_cells() to correctly count cells directly written by channel_flush_from_first_active_circuit() --- src/or/channel.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index e2d102d2ac..dddd7ab1f1 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -175,6 +175,7 @@ static cell_queue_entry_t * cell_queue_entry_new_fixed(cell_t *cell); static cell_queue_entry_t * cell_queue_entry_new_var(var_cell_t *var_cell); +static int chan_cell_queue_len(const chan_cell_queue_t *queue); static int is_destroy_cell(channel_t *chan, const cell_queue_entry_t *q, circid_t *circid_out); @@ -2218,6 +2219,7 @@ channel_flush_some_cells(channel_t *chan, ssize_t num_cells) unsigned int unlimited = 0; ssize_t flushed = 0; int num_cells_from_circs, clamped_num_cells; + int q_len_before, q_len_after; tor_assert(chan); @@ -2243,14 +2245,44 @@ channel_flush_some_cells(channel_t *chan, ssize_t num_cells) clamped_num_cells = (int)(num_cells - flushed); } } + + /* + * Keep track of the change in queue size; we have to count cells + * channel_flush_from_first_active_circuit() writes out directly, + * but not double-count ones we might get later in + * channel_flush_some_cells_from_outgoing_queue() + */ + q_len_before = chan_cell_queue_len(&(chan->outgoing_queue)); + /* Try to get more cells from any active circuits */ num_cells_from_circs = channel_flush_from_first_active_circuit( chan, clamped_num_cells); - /* If it claims we got some, process the queue again */ + q_len_after = chan_cell_queue_len(&(chan->outgoing_queue)); + + /* + * If it claims we got some, adjust the flushed counter and consider + * processing the queue again + */ if (num_cells_from_circs > 0) { - flushed += channel_flush_some_cells_from_outgoing_queue(chan, - (unlimited ? -1 : num_cells - flushed)); + /* + * Adjust flushed by the number of cells counted in + * num_cells_from_circs that didn't go to the cell queue. + */ + + if (q_len_after > q_len_before) { + num_cells_from_circs -= (q_len_after - q_len_before); + if (num_cells_from_circs < 0) num_cells_from_circs = 0; + } + + flushed += num_cells_from_circs; + + /* Now process the queue if necessary */ + + if (q_len_after > q_len_before && num_cells < flushed) { + flushed += channel_flush_some_cells_from_outgoing_queue(chan, + (unlimited ? -1 : num_cells - flushed)); + } } } } From 4f567c8cc8cd68a8ca9bb93fc57d518d7eb55cf0 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 14 Nov 2013 04:45:16 -0800 Subject: [PATCH 14/79] Let the new scheduler handle writes --- src/or/relay.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/or/relay.c b/src/or/relay.c index 2c16e4acca..b653616c2c 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -2871,16 +2871,6 @@ append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, /* New way: mark this as having waiting cells for the scheduler */ scheduler_channel_has_waiting_cells(chan); - - /* TODO remove this once scheduler does it */ - if (!channel_has_queued_writes(chan)) { - /* There is no data at all waiting to be sent on the outbuf. Add a - * cell, so that we can notice when it gets flushed, flushed_some can - * get called, and we can start putting more data onto the buffer then. - */ - log_debug(LD_GENERAL, "Primed a buffer."); - channel_flush_from_first_active_circuit(chan, 1); - } } /** Append an encoded value of addr to payload_out, which must From 1275002a46dfb131f6db5c0fe28bc1828db327e2 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 14 Nov 2013 04:45:47 -0800 Subject: [PATCH 15/79] Schedule according to a queue size heuristic --- src/or/channel.c | 4 + src/or/scheduler.c | 190 +++++++++++++++++++++++++++++++++++++++------ src/or/scheduler.h | 3 + 3 files changed, 172 insertions(+), 25 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index dddd7ab1f1..7ed38945ba 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -4563,6 +4563,8 @@ channel_update_xmit_queue_size(channel_t *chan) U64_FORMAT ", new size is " U64_FORMAT, U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), U64_PRINTF_ARG(estimated_total_queue_size)); + /* Tell the scheduler we're increasing the queue size */ + scheduler_adjust_queue_size(chan, 1, adj); } } else if (queued < chan->bytes_queued_for_xmit) { adj = chan->bytes_queued_for_xmit - queued; @@ -4585,6 +4587,8 @@ channel_update_xmit_queue_size(channel_t *chan) U64_FORMAT ", new size is " U64_FORMAT, U64_PRINTF_ARG(adj), U64_PRINTF_ARG(chan->global_identifier), U64_PRINTF_ARG(estimated_total_queue_size)); + /* Tell the scheduler we're decreasing the queue size */ + scheduler_adjust_queue_size(chan, -1, adj); } } } diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 1c6a2fdecb..1baf3750d6 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -7,7 +7,10 @@ **/ #include "or.h" + +#define TOR_CHANNEL_INTERNAL_ /* For channel_flush_some_cells() */ #include "channel.h" + #include "compat_libevent.h" #include "scheduler.h" @@ -17,6 +20,9 @@ #include #endif +#define SCHED_Q_LOW_WATER 16384 +#define SCHED_Q_HIGH_WATER (2 * SCHED_Q_LOW_WATER) + /* * Write scheduling works by keeping track of lists of channels that can * accept cells, and have cells to write. From the scheduler's perspective, @@ -118,6 +124,19 @@ static smartlist_t *channels_pending = NULL; static struct event *run_sched_ev = NULL; +/* + * Queue heuristic; this is not the queue size, but an 'effective queuesize' + * that ages out contributions from stalled channels. + */ + +static uint64_t queue_heuristic = 0; + +/* + * Timestamp for last queue heuristic update + */ + +static time_t queue_heuristic_timestamp = 0; + /* Scheduler static function declarations */ static void scheduler_evt_callback(evutil_socket_t fd, @@ -127,6 +146,8 @@ static void scheduler_retrigger(void); #if 0 static void scheduler_trigger(void); #endif +static uint64_t scheduler_get_queue_heuristic(void); +static void scheduler_update_queue_heuristic(time_t now); /* Scheduler function implementations */ @@ -281,6 +302,8 @@ scheduler_init(void) channels_waiting_for_cells = smartlist_new(); channels_waiting_to_write = smartlist_new(); channels_pending = smartlist_new(); + queue_heuristic = 0; + queue_heuristic_timestamp = approx_time(); } /** Check if there's more scheduling work */ @@ -290,7 +313,8 @@ scheduler_more_work(void) { tor_assert(channels_pending); - return (smartlist_len(channels_pending) > 0) ? 1 : 0; + return ((scheduler_get_queue_heuristic() < SCHED_Q_LOW_WATER) && + ((smartlist_len(channels_pending) > 0))) ? 1 : 0; } /** Retrigger the scheduler in a way safe to use from the callback */ @@ -324,39 +348,70 @@ void scheduler_run(void) { smartlist_t *tmp = NULL; - int n_cells; + int n_cells, n_chans_before, n_chans_after; + uint64_t q_len_before, q_heur_before, q_len_after, q_heur_after; ssize_t flushed, flushed_this_time; log_debug(LD_SCHED, "We have a chance to run the scheduler"); - tmp = channels_pending; - channels_pending = smartlist_new(); + if (scheduler_get_queue_heuristic() < SCHED_Q_LOW_WATER) { + n_chans_before = smartlist_len(channels_pending); + q_len_before = channel_get_global_queue_estimate(); + q_heur_before = scheduler_get_queue_heuristic(); + tmp = channels_pending; + channels_pending = smartlist_new(); - /* For now, just run the old scheduler on all the chans in the list */ + /* + * For now, just run the old scheduler on all the chans in the list, until + * we hit the high-water mark. TODO real channel priority API + */ - SMARTLIST_FOREACH_BEGIN(tmp, channel_t *, chan) { - n_cells = channel_num_cells_writeable(chan); - if (n_cells > 0) { - log_debug(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p with " - "%d cells writeable", - U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); + SMARTLIST_FOREACH_BEGIN(tmp, channel_t *, chan) { + if (scheduler_get_queue_heuristic() <= SCHED_Q_HIGH_WATER) { + n_cells = channel_num_cells_writeable(chan); + if (n_cells > 0) { + log_debug(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "%d cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); - flushed = 0; - while (flushed < n_cells) { - flushed_this_time = channel_flush_some_cells(chan, n_cells - flushed); - if (flushed_this_time <= 0) break; - flushed += flushed_this_time; + flushed = 0; + while (flushed < n_cells) { + flushed_this_time = + channel_flush_some_cells(chan, n_cells - flushed); + if (flushed_this_time <= 0) break; + flushed += flushed_this_time; + } + + log_debug(LD_SCHED, + "Scheduler flushed %d cells onto pending channel " + U64_FORMAT " at %p", + flushed, U64_PRINTF_ARG(chan->global_identifier), chan); + } else { + log_info(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "no cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan); + } + } else { + /* Not getting it this round; put it back on the list */ + smartlist_add(channels_pending, chan); } - } else { - log_info(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p with " - "no cells writeable", - U64_PRINTF_ARG(chan->global_identifier), chan); - } - } SMARTLIST_FOREACH_END(chan); + } SMARTLIST_FOREACH_END(chan); - smartlist_free(tmp); + smartlist_free(tmp); + + n_chans_after = smartlist_len(channels_pending); + q_len_after = channel_get_global_queue_estimate(); + q_heur_after = scheduler_get_queue_heuristic(); + log_debug(LD_SCHED, + "Scheduler handled %d of %d pending channels, queue size from " + U64_FORMAT " to " U64_FORMAT ", queue heuristic from " + U64_FORMAT " to " U64_FORMAT, + n_chans_before - n_chans_after, n_chans_before, + U64_PRINTF_ARG(q_len_before), U64_PRINTF_ARG(q_len_after), + U64_PRINTF_ARG(q_heur_before), U64_PRINTF_ARG(q_heur_after)); + } } /** Trigger the scheduling event so we run the scheduler later */ @@ -420,3 +475,88 @@ scheduler_channel_wants_writes(channel_t *chan) if (became_pending) scheduler_retrigger(); } +/** + * Notify the scheduler of a queue size adjustment, to recalculate the + * queue heuristic. + */ + +void +scheduler_adjust_queue_size(channel_t *chan, char dir, uint64_t adj) +{ + time_t now = approx_time(); + + log_debug(LD_SCHED, + "Queue size adjustment by %s" U64_FORMAT " for channel " + U64_FORMAT, + (dir >= 0) ? "+" : "-", + U64_PRINTF_ARG(adj), + U64_PRINTF_ARG(chan->global_identifier)); + + /* Get the queue heuristic up to date */ + scheduler_update_queue_heuristic(now); + + /* Adjust as appropriate */ + if (dir >= 0) { + /* Increasing it */ + queue_heuristic += adj; + } else { + /* Decreasing it */ + if (queue_heuristic > adj) queue_heuristic -= adj; + else queue_heuristic = 0; + } + + log_debug(LD_SCHED, + "Queue heuristic is now " U64_FORMAT, + U64_PRINTF_ARG(queue_heuristic)); +} + +/** + * Query the current value of the queue heuristic + */ + +static uint64_t +scheduler_get_queue_heuristic(void) +{ + time_t now = approx_time(); + + scheduler_update_queue_heuristic(now); + + return queue_heuristic; +} + +/** + * Adjust the queue heuristic value to the present time + */ + +static void +scheduler_update_queue_heuristic(time_t now) +{ + time_t diff; + + if (queue_heuristic_timestamp == 0) { + /* + * Nothing we can sensibly do; must not have been initted properly. + * Oh well. + */ + queue_heuristic_timestamp = now; + } else if (queue_heuristic_timestamp < now) { + diff = now - queue_heuristic_timestamp; + /* + * This is a simple exponential age-out; the other proposed alternative + * was a linear age-out using the bandwidth history in rephist.c; I'm + * going with this out of concern that if an adversary can jam the + * scheduler long enough, it would cause the bandwidth to drop to + * zero and render the aging mechanism ineffective thereafter. + */ + if (0 <= diff && diff < 64) queue_heuristic >>= diff; + else queue_heuristic = 0; + + queue_heuristic_timestamp = now; + + log_debug(LD_SCHED, + "Queue heuristic is now " U64_FORMAT, + U64_PRINTF_ARG(queue_heuristic)); + } + /* else no update needed, or time went backward */ +} + diff --git a/src/or/scheduler.h b/src/or/scheduler.h index b25e36e902..8fe59cb0b3 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -27,5 +27,8 @@ void scheduler_channel_wants_writes(channel_t *chan); /* Notify the scheduler of a channel being closed */ void scheduler_release_channel(channel_t *chan); +/* Notify scheduler of queue size adjustments */ +void scheduler_adjust_queue_size(channel_t *chan, char dir, uint64_t adj); + #endif /* !defined(TOR_SCHEDULER_H) */ From 9db596d2ef441d9293f5341be28150739baac98d Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Sat, 30 Nov 2013 01:25:20 -0800 Subject: [PATCH 16/79] Add cmux support for inter-cmux comparisons --- src/or/circuitmux.c | 48 +++++++++++++++++++++++++++++++++++++++++++++ src/or/circuitmux.h | 6 ++++++ 2 files changed, 54 insertions(+) diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 3ca33b03ce..29c3be17d5 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -1951,3 +1951,51 @@ circuitmux_count_queued_destroy_cells(const channel_t *chan, return n_destroy_cells; } +/** + * Compare cmuxes to see which is more preferred; return < 0 if + * cmux_1 has higher priority (i.e., cmux_1 < cmux_2 in the scheduler's + * sort order), > 0 if cmux_2 has higher priority, or 0 if they are + * equally preferred. + * + * If the cmuxes have different cmux policies or the policy does not + * support the cmp_cmux method, return 0. + */ + +int +circuitmux_compare_muxes(circuitmux_t *cmux_1, circuitmux_t *cmux_2) +{ + const circuitmux_policy_t *policy; + + tor_assert(cmux_1); + tor_assert(cmux_2); + + if (cmux_1 == cmux_2) { + /* Equivalent because they're the same cmux */ + return 0; + } + + if (cmux_1->policy && cmux_2->policy) { + if (cmux_1->policy == cmux_2->policy) { + policy = cmux_1->policy; + + if (policy->cmp_cmux) { + /* Okay, we can compare! */ + return policy->cmp_cmux(cmux_1, cmux_1->policy_data, + cmux_2, cmux_2->policy_data); + } else { + /* + * Equivalent because the policy doesn't know how to compare between + * muxes. + */ + return 0; + } + } else { + /* Equivalent because they have different policies */ + return 0; + } + } else { + /* Equivalent because one or both are missing a policy */ + return 0; + } +} + diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index 2b5fb7e51e..5833ee5eee 100644 --- a/src/or/circuitmux.h +++ b/src/or/circuitmux.h @@ -57,6 +57,9 @@ struct circuitmux_policy_s { /* Choose a circuit */ circuit_t * (*pick_active_circuit)(circuitmux_t *cmux, circuitmux_policy_data_t *pol_data); + /* Optional: channel comparator for use by the scheduler */ + int (*cmp_cmux)(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1, + circuitmux_t *cmux_2, circuitmux_policy_data_t *pol_data_2); }; /* @@ -148,5 +151,8 @@ void circuitmux_append_destroy_cell(channel_t *chan, void circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, channel_t *chan); +/* Optional interchannel comparisons for scheduling */ +int circuitmux_compare_muxes(circuitmux_t *cmux_1, circuitmux_t *cmux_2); + #endif /* TOR_CIRCUITMUX_H */ From 700d6e75258fb94819df3da686482dd8565e3691 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Sat, 30 Nov 2013 03:04:36 -0800 Subject: [PATCH 17/79] Add inter-cmux comparison support to circuitmux_ewma.c --- src/or/circuitmux_ewma.c | 58 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c index 3f37d7b9a0..3a2df26771 100644 --- a/src/or/circuitmux_ewma.c +++ b/src/or/circuitmux_ewma.c @@ -187,6 +187,9 @@ ewma_notify_xmit_cells(circuitmux_t *cmux, static circuit_t * ewma_pick_active_circuit(circuitmux_t *cmux, circuitmux_policy_data_t *pol_data); +static int +ewma_cmp_cmux(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1, + circuitmux_t *cmux_2, circuitmux_policy_data_t *pol_data_2); /*** EWMA global variables ***/ @@ -209,7 +212,8 @@ circuitmux_policy_t ewma_policy = { /*.notify_circ_inactive =*/ ewma_notify_circ_inactive, /*.notify_set_n_cells =*/ NULL, /* EWMA doesn't need this */ /*.notify_xmit_cells =*/ ewma_notify_xmit_cells, - /*.pick_active_circuit =*/ ewma_pick_active_circuit + /*.pick_active_circuit =*/ ewma_pick_active_circuit, + /*.cmp_cmux =*/ ewma_cmp_cmux }; /*** EWMA method implementations using the below EWMA helper functions ***/ @@ -453,6 +457,58 @@ ewma_pick_active_circuit(circuitmux_t *cmux, return circ; } +/** + * Compare two EWMA cmuxes, and return -1, 0 or 1 to indicate which should + * be more preferred - see circuitmux_compare_muxes() of circuitmux.c. + */ + +static int +ewma_cmp_cmux(circuitmux_t *cmux_1, circuitmux_policy_data_t *pol_data_1, + circuitmux_t *cmux_2, circuitmux_policy_data_t *pol_data_2) +{ + ewma_policy_data_t *p1 = NULL, *p2 = NULL; + cell_ewma_t *ce1 = NULL, *ce2 = NULL; + + tor_assert(cmux_1); + tor_assert(pol_data_1); + tor_assert(cmux_2); + tor_assert(pol_data_2); + + p1 = TO_EWMA_POL_DATA(pol_data_1); + p2 = TO_EWMA_POL_DATA(pol_data_1); + + if (p1 != p2) { + /* Get the head cell_ewma_t from each queue */ + if (smartlist_len(p1->active_circuit_pqueue) > 0) { + ce1 = smartlist_get(p1->active_circuit_pqueue, 0); + } + + if (smartlist_len(p2->active_circuit_pqueue) > 0) { + ce2 = smartlist_get(p2->active_circuit_pqueue, 0); + } + + /* Got both of them? */ + if (ce1 != NULL && ce2 != NULL) { + /* Pick whichever one has the better best circuit */ + return compare_cell_ewma_counts(ce1, ce2); + } else { + if (ce1 != NULL ) { + /* We only have a circuit on cmux_1, so prefer it */ + return -1; + } else if (ce2 != NULL) { + /* We only have a circuit on cmux_2, so prefer it */ + return 1; + } else { + /* No circuits at all; no preference */ + return 0; + } + } + } else { + /* We got identical params */ + return 0; + } +} + /** Helper for sorting cell_ewma_t values in their priority queue. */ static int compare_cell_ewma_counts(const void *p1, const void *p2) From 55907da28dd933a7724935a8a4676aec1b54059d Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Mon, 2 Dec 2013 01:05:28 -0800 Subject: [PATCH 18/79] Sort the scheduler's channel list by cmux comparisons --- src/or/scheduler.c | 53 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 1baf3750d6..b45df3ebfc 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -139,6 +139,7 @@ static time_t queue_heuristic_timestamp = 0; /* Scheduler static function declarations */ +static int scheduler_compare_channels(const void **c1_v, const void **c2_v); static void scheduler_evt_callback(evutil_socket_t fd, short events, void *arg); static int scheduler_more_work(void); @@ -180,6 +181,52 @@ scheduler_free_all(void) } } +/** + * Comparison function to use when sorting pending channels + */ + +static int +scheduler_compare_channels(const void **c1_v, const void **c2_v) +{ + channel_t *c1 = NULL, *c2 = NULL; + /* These are a workaround for -Wbad-function-cast throwing a fit */ + const circuitmux_policy_t *p1, *p2; + uintptr_t p1_i, p2_i; + + tor_assert(c1_v); + tor_assert(c2_v); + + c1 = (channel_t *)(*c1_v); + c2 = (channel_t *)(*c2_v); + + tor_assert(c1); + tor_assert(c2); + + if (c1 != c2) { + if (circuitmux_get_policy(c1->cmux) == + circuitmux_get_policy(c2->cmux)) { + /* Same cmux policy, so use the mux comparison */ + return circuitmux_compare_muxes(c1->cmux, c2->cmux); + } else { + /* + * Different policies; not important to get this edge case perfect + * because the current code never actually gives different channels + * different cmux policies anyway. Just use this arbitrary but + * definite choice. + */ + p1 = circuitmux_get_policy(c1->cmux); + p2 = circuitmux_get_policy(c2->cmux); + p1_i = (uintptr_t)p1; + p2_i = (uintptr_t)p2; + + return (p1_i < p2_i) ? -1 : 1; + } + } else { + /* c1 == c2, so always equal */ + return 0; + } +} + /* * Scheduler event callback; this should get triggered once per event loop * if any scheduling work was created during the event loop. @@ -362,9 +409,11 @@ scheduler_run(void) channels_pending = smartlist_new(); /* - * For now, just run the old scheduler on all the chans in the list, until - * we hit the high-water mark. TODO real channel priority API + * UGLY HACK: sort the list on each invocation + * + * TODO smarter data structures */ + smartlist_sort(tmp, scheduler_compare_channels); SMARTLIST_FOREACH_BEGIN(tmp, channel_t *, chan) { if (scheduler_get_queue_heuristic() <= SCHED_Q_HIGH_WATER) { From 63bb9a795e3a05620831512fe7d70690e5f5fb5a Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Mon, 2 Dec 2013 01:05:53 -0800 Subject: [PATCH 19/79] Fix compiler warning --- src/or/scheduler.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index b45df3ebfc..c4c26329ba 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -435,7 +435,8 @@ scheduler_run(void) log_debug(LD_SCHED, "Scheduler flushed %d cells onto pending channel " U64_FORMAT " at %p", - flushed, U64_PRINTF_ARG(chan->global_identifier), chan); + (int)flushed, U64_PRINTF_ARG(chan->global_identifier), + chan); } else { log_info(LD_SCHED, "Scheduler saw pending channel " U64_FORMAT " at %p with " From 283646fd9089a337003dcd7c020e0820c7270662 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 4 Dec 2013 21:43:13 -0800 Subject: [PATCH 20/79] Fix scheduler assertion in circuitmux/destroy_cell_queue unit test --- src/test/test_circuitmux.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/test/test_circuitmux.c b/src/test/test_circuitmux.c index a3cacc4cc7..c1c8f77641 100644 --- a/src/test/test_circuitmux.c +++ b/src/test/test_circuitmux.c @@ -8,6 +8,7 @@ #include "channel.h" #include "circuitmux.h" #include "relay.h" +#include "scheduler.h" #include "test.h" /* XXXX duplicated function from test_circuitlist.c */ @@ -36,6 +37,8 @@ test_cmux_destroy_cell_queue(void *arg) cell_queue_t *cq = NULL; packed_cell_t *pc = NULL; + scheduler_init(); + #ifdef ENABLE_MEMPOOLS init_cell_pool(); #endif /* ENABLE_MEMPOOLS */ From 3530825c53b7a28cf894b64e6d97aa90d0acccae Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 6 Dec 2013 00:04:12 -0800 Subject: [PATCH 21/79] Eliminate some unnecessary smartlists in scheduler.c --- src/or/channel.c | 3 ++ src/or/channel.h | 23 ++++++++++ src/or/scheduler.c | 112 ++++++++++++++++++--------------------------- 3 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index 7ed38945ba..60e59c7937 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -806,6 +806,9 @@ channel_init(channel_t *chan) /* It hasn't been open yet. */ chan->has_been_open = 0; + + /* Scheduler state is idle */ + chan->scheduler_state = SCHED_CHAN_IDLE; } /** diff --git a/src/or/channel.h b/src/or/channel.h index 18f7cfc01e..ced717a531 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -57,6 +57,29 @@ struct channel_s { CHANNEL_CLOSE_FOR_ERROR } reason_for_closing; + /** State variable for use by the scheduler */ + enum { + /* + * The channel is not open, or it has a full output buffer but no queued + * cells. + */ + SCHED_CHAN_IDLE = 0, + /* + * The channel has space on its output buffer to write, but no queued + * cells. + */ + SCHED_CHAN_WAITING_FOR_CELLS, + /* + * The scheduler has queued cells but no output buffer space to write. + */ + SCHED_CHAN_WAITING_TO_WRITE, + /* + * The scheduler has both queued cells and output buffer space, and is + * eligible for the scheduler loop. + */ + SCHED_CHAN_PENDING + } scheduler_state; + /** Timestamps for both cell channels and listeners */ time_t timestamp_created; /* Channel created */ time_t timestamp_active; /* Any activity */ diff --git a/src/or/scheduler.c b/src/or/scheduler.c index c4c26329ba..c1b64dfbb6 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -24,12 +24,13 @@ #define SCHED_Q_HIGH_WATER (2 * SCHED_Q_LOW_WATER) /* - * Write scheduling works by keeping track of lists of channels that can + * Write scheduling works by keeping track of which channels can * accept cells, and have cells to write. From the scheduler's perspective, * a channel can be in four possible states: * * 1.) Not open for writes, no cells to send - * - Not much to do here, and the channel will appear in neither list. + * - Not much to do here, and the channel will have scheduler_state == + * SCHED_CHAN_IDLE * - Transitions from: * - Open for writes/has cells by simultaneously draining all circuit * queues and filling the output buffer. @@ -41,7 +42,8 @@ * * 2.) Open for writes, no cells to send * - Not much here either; this will be the state an idle but open channel - * can be expected to settle in. + * can be expected to settle in. It will have scheduler_state == + * SCHED_CHAN_WAITING_FOR_CELLS * - Transitions from: * - Not open for writes/no cells by flushing some of the output * buffer. @@ -55,6 +57,7 @@ * 3.) Not open for writes, cells to send * - This is the state of a busy circuit limited by output bandwidth; * cells have piled up in the circuit queues waiting to be relayed. + * The channel will have scheduler_state == SCHED_CHAN_WAITING_TO_WRITE. * - Transitions from: * - Not open for writes/no cells by arrival of cells on an attached * circuit @@ -66,7 +69,8 @@ * * 4.) Open for writes, cells to send * - This connection is ready to relay some cells and waiting for - * the scheduler to choose it + * the scheduler to choose it. The channel will have scheduler_state == + * SCHED_CHAN_PENDING. * - Transitions from: * - Not open for writes/has cells by the connection_or_flushed_some() * path @@ -91,29 +95,11 @@ /* Scheduler global data structures */ /* - * We keep lists of channels that either have cells queued, can accept - * writes, or both (states 2, 3 and 4 above) - no explicit list of state - * 1 channels is kept, so we don't have to worry about registering new - * channels here or anything. The scheduler will learn about them when - * it needs to. We can check how many channels in state 4 in O(1), so - * the test whether we have anything to do in scheduler_run() is fast - * and there's no harm in calling it opportunistically whenever we get - * the chance. - * - * Note that it takes time O(n) to search for a channel in these smartlists - * or move one; I don't think the number of channels on a relay will be large - * enough for this to be a severe problem, but this would benefit from using - * a doubly-linked list rather than smartlist_t, together with a hash map from - * channel identifiers to pointers to list entries, so we can perform those - * operations in O(log(n)). + * We keep a list of channels that are pending - i.e, have cells to write + * and can accept them to send. The enum scheduler_state in channel_t + * is reserved for our use. */ -/* List of channels that can write but have no cells (state 2 above) */ -static smartlist_t *channels_waiting_for_cells = NULL; - -/* List of channels with cells waiting to write (state 3 above) */ -static smartlist_t *channels_waiting_to_write = NULL; - /* List of channels that can write and have cells (pending work) */ static smartlist_t *channels_pending = NULL; @@ -165,16 +151,6 @@ scheduler_free_all(void) run_sched_ev = NULL; } - if (channels_waiting_for_cells) { - smartlist_free(channels_waiting_for_cells); - channels_waiting_for_cells = NULL; - } - - if (channels_waiting_to_write) { - smartlist_free(channels_waiting_to_write); - channels_waiting_to_write = NULL; - } - if (channels_pending) { smartlist_free(channels_pending); channels_pending = NULL; @@ -255,19 +231,18 @@ void scheduler_channel_doesnt_want_writes(channel_t *chan) { tor_assert(chan); - tor_assert(channels_waiting_for_cells); - tor_assert(channels_waiting_to_write); + tor_assert(channels_pending); /* If it's already in pending, we can put it in waiting_to_write */ - if (smartlist_contains(channels_pending, chan)) { + if (chan->scheduler_state == SCHED_CHAN_PENDING) { /* * It's in channels_pending, so it shouldn't be in any of * the other lists. It can't write any more, so it goes to * channels_waiting_to_write. */ smartlist_remove(channels_pending, chan); - smartlist_add(channels_waiting_to_write, chan); + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from pending " "to waiting_to_write", @@ -278,8 +253,8 @@ scheduler_channel_doesnt_want_writes(channel_t *chan) * either not in any of the lists (nothing to do) or it's already in * waiting_for_cells (remove it, can't write any more). */ - if (smartlist_contains(channels_waiting_for_cells, chan)) { - smartlist_remove(channels_waiting_for_cells, chan); + if (chan->scheduler_state == SCHED_CHAN_WAITING_FOR_CELLS) { + chan->scheduler_state = SCHED_CHAN_IDLE; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p left waiting_for_cells", U64_PRINTF_ARG(chan->global_identifier), chan); @@ -295,18 +270,16 @@ scheduler_channel_has_waiting_cells(channel_t *chan) int became_pending = 0; tor_assert(chan); - tor_assert(channels_waiting_for_cells); - tor_assert(channels_waiting_to_write); tor_assert(channels_pending); /* First, check if this one also writeable */ - if (smartlist_contains(channels_waiting_for_cells, chan)) { + if (chan->scheduler_state == SCHED_CHAN_WAITING_FOR_CELLS) { /* * It's in channels_waiting_for_cells, so it shouldn't be in any of * the other lists. It has waiting cells now, so it goes to * channels_pending. */ - smartlist_remove(channels_waiting_for_cells, chan); + chan->scheduler_state = SCHED_CHAN_PENDING; smartlist_add(channels_pending, chan); log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from waiting_for_cells " @@ -319,9 +292,9 @@ scheduler_channel_has_waiting_cells(channel_t *chan) * either not in any of the lists (we add it to waiting_to_write) * or it's already in waiting_to_write or pending (we do nothing) */ - if (!(smartlist_contains(channels_waiting_to_write, chan) || - smartlist_contains(channels_pending, chan))) { - smartlist_add(channels_waiting_to_write, chan); + if (!(chan->scheduler_state == SCHED_CHAN_WAITING_TO_WRITE || + chan->scheduler_state == SCHED_CHAN_PENDING)) { + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p entered waiting_to_write", U64_PRINTF_ARG(chan->global_identifier), chan); @@ -346,8 +319,6 @@ scheduler_init(void) run_sched_ev = tor_event_new(tor_libevent_get_base(), -1, 0, scheduler_evt_callback, NULL); - channels_waiting_for_cells = smartlist_new(); - channels_waiting_to_write = smartlist_new(); channels_pending = smartlist_new(); queue_heuristic = 0; queue_heuristic_timestamp = approx_time(); @@ -379,14 +350,13 @@ void scheduler_release_channel(channel_t *chan) { tor_assert(chan); - - tor_assert(channels_waiting_for_cells); - tor_assert(channels_waiting_to_write); tor_assert(channels_pending); - smartlist_remove(channels_waiting_for_cells, chan); - smartlist_remove(channels_waiting_to_write, chan); - smartlist_remove(channels_pending, chan); + if (chan->scheduler_state == SCHED_CHAN_PENDING) { + smartlist_remove(channels_pending, chan); + } + + chan->scheduler_state = SCHED_CHAN_IDLE; } /** Run the scheduling algorithm if necessary */ @@ -432,6 +402,13 @@ scheduler_run(void) flushed += flushed_this_time; } + if (flushed < n_cells) { + /* We ran out of cells to flush */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + } else { + /* TODO get this right */ + } + log_debug(LD_SCHED, "Scheduler flushed %d cells onto pending channel " U64_FORMAT " at %p", @@ -442,10 +419,13 @@ scheduler_run(void) "Scheduler saw pending channel " U64_FORMAT " at %p with " "no cells writeable", U64_PRINTF_ARG(chan->global_identifier), chan); + /* Put it back to WAITING_TO_WRITE */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; } } else { /* Not getting it this round; put it back on the list */ smartlist_add(channels_pending, chan); + /* It states in SCHED_CHAN_PENDING */ } } SMARTLIST_FOREACH_END(chan); @@ -486,18 +466,15 @@ scheduler_channel_wants_writes(channel_t *chan) int became_pending = 0; tor_assert(chan); - tor_assert(channels_waiting_for_cells); - tor_assert(channels_waiting_to_write); tor_assert(channels_pending); /* If it's already in waiting_to_write, we can put it in pending */ - if (smartlist_contains(channels_waiting_to_write, chan)) { + if (chan->scheduler_state == SCHED_CHAN_WAITING_TO_WRITE) { /* - * It's in channels_waiting_to_write, so it shouldn't be in any of - * the other lists. It can write now, so it goes to channels_pending. + * It can write now, so it goes to channels_pending. */ - smartlist_remove(channels_waiting_to_write, chan); smartlist_add(channels_pending, chan); + chan->scheduler_state = SCHED_CHAN_PENDING; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from waiting_to_write " "to pending", @@ -505,13 +482,12 @@ scheduler_channel_wants_writes(channel_t *chan) became_pending = 1; } else { /* - * It's not in waiting_to_write, so it can't become pending; it's - * either not in any of the lists (we add it to waiting_for_cells) - * or it's already in waiting_for_cells or pending (we do nothing) + * It's not in SCHED_CHAN_WAITING_TO_WRITE, so it can't become pending; + * it's either idle and goes to WAITING_FOR_CELLS, or it's a no-op. */ - if (!(smartlist_contains(channels_waiting_for_cells, chan) || - smartlist_contains(channels_pending, chan))) { - smartlist_add(channels_waiting_for_cells, chan); + if (!(chan->scheduler_state == SCHED_CHAN_WAITING_FOR_CELLS || + chan->scheduler_state == SCHED_CHAN_PENDING)) { + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p entered waiting_for_cells", U64_PRINTF_ARG(chan->global_identifier), chan); From ed1927d6bf8b9d60d40f6fbc20f9e1575a35e59d Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 12 Dec 2013 04:22:53 -0800 Subject: [PATCH 22/79] Use a non-stupid data structure in the scheduler --- src/or/channel.h | 3 + src/or/scheduler.c | 194 +++++++++++++++++++++++++++++++-------------- 2 files changed, 139 insertions(+), 58 deletions(-) diff --git a/src/or/channel.h b/src/or/channel.h index ced717a531..023c39d0dd 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -80,6 +80,9 @@ struct channel_s { SCHED_CHAN_PENDING } scheduler_state; + /** Heap index for use by the scheduler */ + int sched_heap_idx; + /** Timestamps for both cell channels and listeners */ time_t timestamp_created; /* Channel created */ time_t timestamp_active; /* Any activity */ diff --git a/src/or/scheduler.c b/src/or/scheduler.c index c1b64dfbb6..4f12696c68 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -23,6 +23,14 @@ #define SCHED_Q_LOW_WATER 16384 #define SCHED_Q_HIGH_WATER (2 * SCHED_Q_LOW_WATER) +/* + * Maximum cells to flush in a single call to channel_flush_some_cells(); + * setting this low means more calls, but too high and we could overshoot + * SCHED_Q_HIGH_WATER. + */ + +#define SCHED_MAX_FLUSH_CELLS 16 + /* * Write scheduling works by keeping track of which channels can * accept cells, and have cells to write. From the scheduler's perspective, @@ -100,7 +108,7 @@ * is reserved for our use. */ -/* List of channels that can write and have cells (pending work) */ +/* Pqueue of channels that can write and have cells (pending work) */ static smartlist_t *channels_pending = NULL; /* @@ -125,7 +133,7 @@ static time_t queue_heuristic_timestamp = 0; /* Scheduler static function declarations */ -static int scheduler_compare_channels(const void **c1_v, const void **c2_v); +static int scheduler_compare_channels(const void *c1_v, const void *c2_v); static void scheduler_evt_callback(evutil_socket_t fd, short events, void *arg); static int scheduler_more_work(void); @@ -162,7 +170,7 @@ scheduler_free_all(void) */ static int -scheduler_compare_channels(const void **c1_v, const void **c2_v) +scheduler_compare_channels(const void *c1_v, const void *c2_v) { channel_t *c1 = NULL, *c2 = NULL; /* These are a workaround for -Wbad-function-cast throwing a fit */ @@ -172,8 +180,8 @@ scheduler_compare_channels(const void **c1_v, const void **c2_v) tor_assert(c1_v); tor_assert(c2_v); - c1 = (channel_t *)(*c1_v); - c2 = (channel_t *)(*c2_v); + c1 = (channel_t *)(c1_v); + c2 = (channel_t *)(c2_v); tor_assert(c1); tor_assert(c2); @@ -241,7 +249,10 @@ scheduler_channel_doesnt_want_writes(channel_t *chan) * the other lists. It can't write any more, so it goes to * channels_waiting_to_write. */ - smartlist_remove(channels_pending, chan); + smartlist_pqueue_remove(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from pending " @@ -280,7 +291,10 @@ scheduler_channel_has_waiting_cells(channel_t *chan) * channels_pending. */ chan->scheduler_state = SCHED_CHAN_PENDING; - smartlist_add(channels_pending, chan); + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from waiting_for_cells " "to pending", @@ -353,7 +367,10 @@ scheduler_release_channel(channel_t *chan) tor_assert(channels_pending); if (chan->scheduler_state == SCHED_CHAN_PENDING) { - smartlist_remove(channels_pending, chan); + smartlist_pqueue_remove(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); } chan->scheduler_state = SCHED_CHAN_IDLE; @@ -364,10 +381,11 @@ scheduler_release_channel(channel_t *chan) void scheduler_run(void) { - smartlist_t *tmp = NULL; int n_cells, n_chans_before, n_chans_after; uint64_t q_len_before, q_heur_before, q_len_after, q_heur_after; ssize_t flushed, flushed_this_time; + smartlist_t *to_readd = NULL; + channel_t *chan = NULL; log_debug(LD_SCHED, "We have a chance to run the scheduler"); @@ -375,61 +393,118 @@ scheduler_run(void) n_chans_before = smartlist_len(channels_pending); q_len_before = channel_get_global_queue_estimate(); q_heur_before = scheduler_get_queue_heuristic(); - tmp = channels_pending; - channels_pending = smartlist_new(); - /* - * UGLY HACK: sort the list on each invocation - * - * TODO smarter data structures - */ - smartlist_sort(tmp, scheduler_compare_channels); + while (scheduler_get_queue_heuristic() <= SCHED_Q_HIGH_WATER && + smartlist_len(channels_pending) > 0) { + /* Pop off a channel */ + chan = smartlist_pqueue_pop(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx)); + tor_assert(chan); - SMARTLIST_FOREACH_BEGIN(tmp, channel_t *, chan) { - if (scheduler_get_queue_heuristic() <= SCHED_Q_HIGH_WATER) { - n_cells = channel_num_cells_writeable(chan); - if (n_cells > 0) { + /* Figure out how many cells we can write */ + n_cells = channel_num_cells_writeable(chan); + if (n_cells > 0) { + log_debug(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "%d cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); + + flushed = 0; + while (flushed < n_cells && + scheduler_get_queue_heuristic() <= SCHED_Q_HIGH_WATER) { + flushed_this_time = + channel_flush_some_cells(chan, + MIN(SCHED_MAX_FLUSH_CELLS, + n_cells - flushed)); + if (flushed_this_time <= 0) break; + flushed += flushed_this_time; + } + + if (flushed < n_cells) { + /* We ran out of cells to flush */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; log_debug(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p with " - "%d cells writeable", - U64_PRINTF_ARG(chan->global_identifier), chan, n_cells); - - flushed = 0; - while (flushed < n_cells) { - flushed_this_time = - channel_flush_some_cells(chan, n_cells - flushed); - if (flushed_this_time <= 0) break; - flushed += flushed_this_time; - } - - if (flushed < n_cells) { - /* We ran out of cells to flush */ - chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; - } else { - /* TODO get this right */ - } - - log_debug(LD_SCHED, - "Scheduler flushed %d cells onto pending channel " - U64_FORMAT " at %p", - (int)flushed, U64_PRINTF_ARG(chan->global_identifier), + "Channel " U64_FORMAT " at %p " + "entered waiting_for_cells from pending", + U64_PRINTF_ARG(chan->global_identifier), chan); } else { - log_info(LD_SCHED, - "Scheduler saw pending channel " U64_FORMAT " at %p with " - "no cells writeable", - U64_PRINTF_ARG(chan->global_identifier), chan); - /* Put it back to WAITING_TO_WRITE */ - chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + /* The channel may still have some cells */ + if (channel_more_to_flush(chan)) { + /* The channel goes to either pending or waiting_to_write */ + if (channel_num_cells_writeable(chan) > 0) { + /* Add it back to pending later */ + if (!to_readd) to_readd = smartlist_new(); + smartlist_add(to_readd, chan); + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "is still pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* It's waiting to be able to write more */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_to_write from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } + } else { + /* No cells left; it can go to idle or waiting_for_cells */ + if (channel_num_cells_writeable(chan) > 0) { + /* + * It can still accept writes, so it goes to + * waiting_for_cells + */ + chan->scheduler_state = SCHED_CHAN_WAITING_FOR_CELLS; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "entered waiting_for_cells from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + /* + * We exactly filled up the output queue with all available + * cells; go to idle. + */ + chan->scheduler_state = SCHED_CHAN_IDLE; + log_debug(LD_SCHED, + "Channel " U64_FORMAT " at %p " + "become idle from pending", + U64_PRINTF_ARG(chan->global_identifier), + chan); + } + } } - } else { - /* Not getting it this round; put it back on the list */ - smartlist_add(channels_pending, chan); - /* It states in SCHED_CHAN_PENDING */ - } - } SMARTLIST_FOREACH_END(chan); - smartlist_free(tmp); + log_debug(LD_SCHED, + "Scheduler flushed %d cells onto pending channel " + U64_FORMAT " at %p", + (int)flushed, U64_PRINTF_ARG(chan->global_identifier), + chan); + } else { + log_info(LD_SCHED, + "Scheduler saw pending channel " U64_FORMAT " at %p with " + "no cells writeable", + U64_PRINTF_ARG(chan->global_identifier), chan); + /* Put it back to WAITING_TO_WRITE */ + chan->scheduler_state = SCHED_CHAN_WAITING_TO_WRITE; + } + } + + /* Readd any channels we need to */ + if (to_readd) { + SMARTLIST_FOREACH_BEGIN(to_readd, channel_t *, chan) { + chan->scheduler_state = SCHED_CHAN_PENDING; + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + } SMARTLIST_FOREACH_END(chan); + smartlist_free(to_readd); + } n_chans_after = smartlist_len(channels_pending); q_len_after = channel_get_global_queue_estimate(); @@ -473,7 +548,10 @@ scheduler_channel_wants_writes(channel_t *chan) /* * It can write now, so it goes to channels_pending. */ - smartlist_add(channels_pending, chan); + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); chan->scheduler_state = SCHED_CHAN_PENDING; log_debug(LD_SCHED, "Channel " U64_FORMAT " at %p went from waiting_to_write " From ac1b627e85a41bb734ab22bef268a5145cf83f15 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 12 Dec 2013 04:29:45 -0800 Subject: [PATCH 23/79] Implement scheduler_touch_channel() --- src/or/scheduler.c | 24 ++++++++++++++++++++++++ src/or/scheduler.h | 3 +++ 2 files changed, 27 insertions(+) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 4f12696c68..140ff2fef1 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -579,6 +579,30 @@ scheduler_channel_wants_writes(channel_t *chan) if (became_pending) scheduler_retrigger(); } +/** + * Notify the scheduler that a channel's position in the pqueue may have + * changed + */ + +void +scheduler_touch_channel(channel_t *chan) +{ + tor_assert(chan); + + if (chan->scheduler_state == SCHED_CHAN_PENDING) { + /* Remove and re-add it */ + smartlist_pqueue_remove(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + smartlist_pqueue_add(channels_pending, + scheduler_compare_channels, + STRUCT_OFFSET(channel_t, sched_heap_idx), + chan); + } + /* else no-op, since it isn't in the queue */ +} + /** * Notify the scheduler of a queue size adjustment, to recalculate the * queue heuristic. diff --git a/src/or/scheduler.h b/src/or/scheduler.h index 8fe59cb0b3..e2d2eb5192 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -30,5 +30,8 @@ void scheduler_release_channel(channel_t *chan); /* Notify scheduler of queue size adjustments */ void scheduler_adjust_queue_size(channel_t *chan, char dir, uint64_t adj); +/* Notify scheduler that a channel's queue position may have changed */ +void scheduler_touch_channel(channel_t *chan); + #endif /* !defined(TOR_SCHEDULER_H) */ From 52cfaa84b7b2f1098a15c431433f486a150cc854 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 12 Dec 2013 04:36:15 -0800 Subject: [PATCH 24/79] Add changes file for global scheduler/cmux refactor --- changes/global_scheduler | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changes/global_scheduler diff --git a/changes/global_scheduler b/changes/global_scheduler new file mode 100644 index 0000000000..d0b8305ceb --- /dev/null +++ b/changes/global_scheduler @@ -0,0 +1,4 @@ + o Major changes: + - Implement a new inter-cmux comparison API, a global high/low watermark + mechanism and a global scheduler loop for transmission prioritization + across all channels as well as among circuits on one channel. From dabf4c33e2c42024aebb305c17e9caf1d6b6c84b Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 13 Dec 2013 05:55:12 -0800 Subject: [PATCH 25/79] Refactor channel_get_cell_queue_entry_size() to avoid an unreachable line for test coverage, and fix a nasty lurking memory bug in channel_flush_some_cells_from_outgoing_queue() --- src/or/channel.c | 55 ++++++++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index 60e59c7937..9e3e452d0b 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -1741,25 +1741,28 @@ cell_queue_entry_new_var(var_cell_t *var_cell) static size_t channel_get_cell_queue_entry_size(channel_t *chan, cell_queue_entry_t *q) { + size_t rv = 0; + tor_assert(chan); tor_assert(q); switch (q->type) { case CELL_QUEUE_FIXED: - return get_cell_network_size(chan->wide_circ_ids); + rv = get_cell_network_size(chan->wide_circ_ids); break; case CELL_QUEUE_VAR: tor_assert(q->u.var.var_cell); - return get_var_cell_header_size(chan->wide_circ_ids) + - q->u.var.var_cell->payload_len; + rv = get_var_cell_header_size(chan->wide_circ_ids) + + q->u.var.var_cell->payload_len; break; case CELL_QUEUE_PACKED: - return get_cell_network_size(chan->wide_circ_ids); + rv = get_cell_network_size(chan->wide_circ_ids); break; default: tor_assert(1); - return 0; } + + return rv; } /** @@ -2309,6 +2312,7 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, ssize_t flushed = 0; cell_queue_entry_t *q = NULL; size_t cell_size; + int free_q = 0, handed_off = 0; tor_assert(chan); tor_assert(chan->write_cell); @@ -2322,6 +2326,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, if (chan->state == CHANNEL_STATE_OPEN) { while ((unlimited || num_cells > flushed) && NULL != (q = TOR_SIMPLEQ_FIRST(&chan->outgoing_queue))) { + free_q = 0; + handed_off = 0; if (1) { /* Figure out how big it is for statistical purposes */ @@ -2339,8 +2345,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); chan->n_bytes_xmitted += cell_size; - cell_queue_entry_free(q, 1); - q = NULL; + free_q = 1; + handed_off = 1; } /* Else couldn't write it; leave it on the queue */ } else { @@ -2351,8 +2357,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, "(global ID " U64_FORMAT ").", chan, U64_PRINTF_ARG(chan->global_identifier)); /* Throw it away */ - cell_queue_entry_free(q, 0); - q = NULL; + free_q = 1; + handed_off = 0; } break; case CELL_QUEUE_PACKED: @@ -2363,8 +2369,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); chan->n_bytes_xmitted += cell_size; - cell_queue_entry_free(q, 1); - q = NULL; + free_q = 1; + handed_off = 1; } /* Else couldn't write it; leave it on the queue */ } else { @@ -2375,8 +2381,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, "(global ID " U64_FORMAT ").", chan, U64_PRINTF_ARG(chan->global_identifier)); /* Throw it away */ - cell_queue_entry_free(q, 0); - q = NULL; + free_q = 1; + handed_off = 0; } break; case CELL_QUEUE_VAR: @@ -2387,8 +2393,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, channel_timestamp_xmit(chan); ++(chan->n_cells_xmitted); chan->n_bytes_xmitted += cell_size; - cell_queue_entry_free(q, 1); - q = NULL; + free_q = 1; + handed_off = 1; } /* Else couldn't write it; leave it on the queue */ } else { @@ -2399,8 +2405,8 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, "(global ID " U64_FORMAT ").", chan, U64_PRINTF_ARG(chan->global_identifier)); /* Throw it away */ - cell_queue_entry_free(q, 0); - q = NULL; + free_q = 1; + handed_off = 0; } break; default: @@ -2410,12 +2416,16 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, "(global ID " U64_FORMAT "; ignoring it." " Someone should fix this.", q->type, chan, U64_PRINTF_ARG(chan->global_identifier)); - cell_queue_entry_free(q, 0); - q = NULL; + free_q = 1; + handed_off = 0; } - /* if q got NULLed out, we used it and should remove the queue entry */ - if (!q) { + /* + * if free_q is set, we used it and should remove the queue entry; + * we have to do the free down here so TOR_SIMPLEQ_REMOVE_HEAD isn't + * accessing freed memory + */ + if (free_q) { TOR_SIMPLEQ_REMOVE_HEAD(&chan->outgoing_queue, next); /* * ...and we handed a cell off to the lower layer, so we should @@ -2428,6 +2438,9 @@ channel_flush_some_cells_from_outgoing_queue(channel_t *chan, channel_assert_counter_consistency(); /* Update the channel's queue size too */ chan->bytes_in_queue -= cell_size; + /* Finally, free q */ + cell_queue_entry_free(q, handed_off); + q = NULL; } /* No cell removed from list, so we can't go on any further */ else break; From 8907554cf3e4e87515fecde40ae2088fdded523f Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 13 Dec 2013 06:13:05 -0800 Subject: [PATCH 26/79] Make channel_note_destroy_not_pending() mockable --- src/or/circuitlist.c | 4 ++-- src/or/circuitlist.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 9d72ea1111..7b4eda3fa7 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -302,8 +302,8 @@ channel_note_destroy_pending(channel_t *chan, circid_t id) /** Called to indicate that a DESTROY is no longer pending on chan with * circuit ID id -- typically, because it has been sent. */ -void -channel_note_destroy_not_pending(channel_t *chan, circid_t id) +MOCK_IMPL(void, channel_note_destroy_not_pending, + (channel_t *chan, circid_t id)) { circuit_t *circ = circuit_get_by_circid_channel_even_if_marked(id,chan); if (circ) { diff --git a/src/or/circuitlist.h b/src/or/circuitlist.h index 03934f971e..a8d746be53 100644 --- a/src/or/circuitlist.h +++ b/src/or/circuitlist.h @@ -72,7 +72,8 @@ void circuit_free_all(void); void circuits_handle_oom(size_t current_allocation); void channel_note_destroy_pending(channel_t *chan, circid_t id); -void channel_note_destroy_not_pending(channel_t *chan, circid_t id); +MOCK_DECL(void, channel_note_destroy_not_pending, + (channel_t *chan, circid_t id)); #ifdef CIRCUITLIST_PRIVATE STATIC void circuit_free(circuit_t *circ); From 85ee07085281a1fa47d0b44b0addbb54fcfa6061 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 13 Dec 2013 06:27:00 -0800 Subject: [PATCH 27/79] Make scheduler_release_channel() mockable --- src/or/scheduler.c | 4 ++-- src/or/scheduler.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 140ff2fef1..450eb02903 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -360,8 +360,8 @@ scheduler_retrigger(void) /** Notify the scheduler of a channel being closed */ -void -scheduler_release_channel(channel_t *chan) +MOCK_IMPL(void, +scheduler_release_channel,(channel_t *chan)) { tor_assert(chan); tor_assert(channels_pending); diff --git a/src/or/scheduler.h b/src/or/scheduler.h index e2d2eb5192..9cdf6c1c4d 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -11,6 +11,7 @@ #include "or.h" #include "channel.h" +#include "testsupport.h" /* Global-visibility scheduler functions */ @@ -25,7 +26,7 @@ void scheduler_channel_has_waiting_cells(channel_t *chan); void scheduler_channel_wants_writes(channel_t *chan); /* Notify the scheduler of a channel being closed */ -void scheduler_release_channel(channel_t *chan); +MOCK_DECL(void,scheduler_release_channel,(channel_t *chan)); /* Notify scheduler of queue size adjustments */ void scheduler_adjust_queue_size(channel_t *chan, char dir, uint64_t adj); From 6d886787e3e112693dc8ac9cda021c93eaefee8b Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 13 Dec 2013 06:34:46 -0800 Subject: [PATCH 28/79] Unit tests for channel_get_cell_queue_entry_size() and channel_write_*() functions --- src/test/Makefile.nmake | 2 +- src/test/include.am | 1 + src/test/test.c | 3 + src/test/test_channel.c | 295 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 src/test/test_channel.c diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index 822431f3b8..fceef99564 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -11,7 +11,7 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ ws2_32.lib advapi32.lib shell32.lib \ crypt32.lib gdi32.lib user32.lib -TEST_OBJECTS = test.obj test_addr.obj test_containers.obj \ +TEST_OBJECTS = test.obj test_addr.obj test_channel.obj test_containers.obj \ test_controller_events.ogj test_crypto.obj test_data.obj test_dir.obj \ test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ test_cell_formats.obj test_replay.obj test_introduce.obj tinytest.obj \ diff --git a/src/test/include.am b/src/test/include.am index 77c92f12f8..547f23b733 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -20,6 +20,7 @@ src_test_test_SOURCES = \ src/test/test_addr.c \ src/test/test_buffers.c \ src/test/test_cell_formats.c \ + src/test/test_channel.c \ src/test/test_circuitlist.c \ src/test/test_circuitmux.c \ src/test/test_containers.c \ diff --git a/src/test/test.c b/src/test/test.c index cfbe203d2e..4d4f6f23a4 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1308,6 +1308,8 @@ extern struct testcase_t oom_tests[]; extern struct testcase_t policy_tests[]; extern struct testcase_t status_tests[]; extern struct testcase_t routerset_tests[]; +extern struct testcase_t router_tests[]; +extern struct testcase_t channel_tests[]; static struct testgroup_t testgroups[] = { { "", test_array }, @@ -1340,6 +1342,7 @@ static struct testgroup_t testgroups[] = { { "policy/" , policy_tests }, { "status/" , status_tests }, { "routerset/" , routerset_tests }, + { "channel/", channel_tests }, END_OF_GROUPS }; diff --git a/src/test/test_channel.c b/src/test/test_channel.c new file mode 100644 index 0000000000..c4241db77e --- /dev/null +++ b/src/test/test_channel.c @@ -0,0 +1,295 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define TOR_CHANNEL_INTERNAL_ +#include "or.h" +#include "channel.h" +/* For channel_note_destroy_not_pending */ +#include "circuitlist.h" +/* For var_cell_free */ +#include "connection_or.h" +/* For packed_cell stuff */ +#define RELAY_PRIVATE +#include "relay.h" +/* For init/free stuff */ +#include "scheduler.h" +#include "test.h" + +static int test_chan_accept_cells = 0; +static int test_cells_written = 0; +static int test_destroy_not_pending_calls = 0; + +static void channel_note_destroy_not_pending_mock(channel_t *ch, + circid_t circid); +static void chan_test_close(channel_t *ch); +static size_t chan_test_num_bytes_queued(channel_t *ch); +static int chan_test_write_cell(channel_t *ch, cell_t *cell); +static int chan_test_write_packed_cell(channel_t *ch, + packed_cell_t *packed_cell); +static int chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell); +static void make_fake_cell(cell_t *c); +static void make_fake_var_cell(var_cell_t *c); +static channel_t * new_fake_channel(void); +static void scheduler_release_channel_mock(channel_t *ch); +static void test_channel_write(void *arg); + +static void +channel_note_destroy_not_pending_mock(channel_t *ch, + circid_t circid) +{ + (void)ch; + (void)circid; + + ++test_destroy_not_pending_calls; +} + +static void +chan_test_close(channel_t *ch) +{ + test_assert(ch); + + done: + return; +} + +static size_t +chan_test_num_bytes_queued(channel_t *ch) +{ + test_assert(ch); + + done: + return 0; +} + +static int +chan_test_write_cell(channel_t *ch, cell_t *cell) +{ + int rv = 0; + + test_assert(ch); + test_assert(cell); + + if (test_chan_accept_cells) { + /* Free the cell and bump the counter */ + tor_free(cell); + ++test_cells_written; + rv = 1; + } + /* else return 0, we didn't accept it */ + + done: + return rv; +} + +static int +chan_test_write_packed_cell(channel_t *ch, + packed_cell_t *packed_cell) +{ + int rv = 0; + + test_assert(ch); + test_assert(packed_cell); + + if (test_chan_accept_cells) { + /* Free the cell and bump the counter */ + packed_cell_free(packed_cell); + ++test_cells_written; + rv = 1; + } + /* else return 0, we didn't accept it */ + + done: + return rv; +} + +static int +chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell) +{ + int rv = 0; + + test_assert(ch); + test_assert(var_cell); + + if (test_chan_accept_cells) { + /* Free the cell and bump the counter */ + var_cell_free(var_cell); + ++test_cells_written; + rv = 1; + } + /* else return 0, we didn't accept it */ + + done: + return rv; +} + +static void +make_fake_cell(cell_t *c) +{ + test_assert(c != NULL); + + c->circ_id = 1; + c->command = CELL_RELAY; + memset(c->payload, 0, CELL_PAYLOAD_SIZE); + + done: + return; +} + +static void +make_fake_var_cell(var_cell_t *c) +{ + test_assert(c != NULL); + + c->circ_id = 1; + c->command = CELL_VERSIONS; + c->payload_len = CELL_PAYLOAD_SIZE / 2; + memset(c->payload, 0, c->payload_len); + + done: + return; +} + +static channel_t * +new_fake_channel(void) +{ + channel_t *chan = tor_malloc_zero(sizeof(channel_t)); + channel_init(chan); + + chan->close = chan_test_close; + chan->num_bytes_queued = chan_test_num_bytes_queued; + chan->write_cell = chan_test_write_cell; + chan->write_packed_cell = chan_test_write_packed_cell; + chan->write_var_cell = chan_test_write_var_cell; + chan->state = CHANNEL_STATE_OPEN; + + return chan; +} + +static void +scheduler_release_channel_mock(channel_t *ch) +{ + (void)ch; + + /* Increment counter */ + ++test_releases_count; + + return; +} + +static void +test_channel_write(void *arg) +{ + channel_t *ch = NULL; + cell_t *cell = tor_malloc_zero(sizeof(cell_t)); + packed_cell_t *packed_cell = NULL; + var_cell_t *var_cell = + tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); + int old_count; + + (void)arg; + + init_cell_pool(); + + packed_cell = packed_cell_new(); + test_assert(packed_cell); + + ch = new_fake_channel(); + test_assert(ch); + make_fake_cell(cell); + make_fake_var_cell(var_cell); + + /* Tell it to accept cells */ + test_chan_accept_cells = 1; + + old_count = test_cells_written; + channel_write_cell(ch, cell); + test_assert(test_cells_written == old_count + 1); + + channel_write_var_cell(ch, var_cell); + test_assert(test_cells_written == old_count + 2); + + channel_write_packed_cell(ch, packed_cell); + test_assert(test_cells_written == old_count + 3); + + /* Now we test queueing; tell it not to accept cells */ + test_chan_accept_cells = 0; + /* ...and keep it from trying to flush the queue */ + ch->state = CHANNEL_STATE_MAINT; + + /* Get a fresh cell */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + + old_count = test_cells_written; + channel_write_cell(ch, cell); + test_assert(test_cells_written == old_count); + + /* + * Now change back to open with channel_change_state() and assert that it + * gets drained from the queue. + */ + test_chan_accept_cells = 1; + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_assert(test_cells_written == old_count + 1); + + /* + * Check the note destroy case + */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + cell->command = CELL_DESTROY; + + /* Set up the mock */ + MOCK(channel_note_destroy_not_pending, + channel_note_destroy_not_pending_mock); + + old_count = test_destroy_not_pending_calls; + channel_write_cell(ch, cell); + test_assert(test_destroy_not_pending_calls == old_count + 1); + + /* Now send a non-destroy and check we don't call it */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch, cell); + test_assert(test_destroy_not_pending_calls == old_count + 1); + + UNMOCK(channel_note_destroy_not_pending); + + /* + * Now switch it to CLOSING so we can test the discard-cells case + * in the channel_write_*() functions. + */ + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + channel_mark_for_close(ch); + UNMOCK(scheduler_release_channel); + + /* Send cells that will drop in the closing state */ + old_count = test_cells_written; + + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch, cell); + test_assert(test_cells_written == old_count); + + var_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); + make_fake_var_cell(var_cell); + channel_write_var_cell(ch, var_cell); + test_assert(test_cells_written == old_count); + + packed_cell = packed_cell_new(); + channel_write_packed_cell(ch, packed_cell); + test_assert(test_cells_written == old_count); + + free_cell_pool(); + + done: + tor_free(ch); + + return; +} + +struct testcase_t channel_tests[] = { + { "write", test_channel_write, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + From 6e427c30af389fdc15ae3a8d946a46ad5803688c Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 18 Dec 2013 13:42:02 -0800 Subject: [PATCH 29/79] Implement channel queue size estimate unit test --- src/test/test_channel.c | 143 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index c4241db77e..08abd96cd6 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -18,11 +18,13 @@ static int test_chan_accept_cells = 0; static int test_cells_written = 0; static int test_destroy_not_pending_calls = 0; +static double test_overhead_estimate = 1.0f; static void channel_note_destroy_not_pending_mock(channel_t *ch, circid_t circid); static void chan_test_close(channel_t *ch); static size_t chan_test_num_bytes_queued(channel_t *ch); +static int chan_test_num_cells_writeable(channel_t *ch); static int chan_test_write_cell(channel_t *ch, cell_t *cell); static int chan_test_write_packed_cell(channel_t *ch, packed_cell_t *packed_cell); @@ -31,6 +33,7 @@ static void make_fake_cell(cell_t *c); static void make_fake_var_cell(var_cell_t *c); static channel_t * new_fake_channel(void); static void scheduler_release_channel_mock(channel_t *ch); +static void test_channel_queue_size(void *arg); static void test_channel_write(void *arg); static void @@ -52,6 +55,15 @@ chan_test_close(channel_t *ch) return; } +static double +chan_test_get_overhead_estimate(channel_t *ch) +{ + test_assert(ch); + + done: + return test_overhead_estimate; +} + static size_t chan_test_num_bytes_queued(channel_t *ch) { @@ -61,6 +73,15 @@ chan_test_num_bytes_queued(channel_t *ch) return 0; } +static int +chan_test_num_cells_writeable(channel_t *ch) +{ + test_assert(ch); + + done: + return 32; +} + static int chan_test_write_cell(channel_t *ch, cell_t *cell) { @@ -156,7 +177,9 @@ new_fake_channel(void) channel_init(chan); chan->close = chan_test_close; + chan->get_overhead_estimate = chan_test_get_overhead_estimate; chan->num_bytes_queued = chan_test_num_bytes_queued; + chan->num_cells_writeable = chan_test_num_cells_writeable; chan->write_cell = chan_test_write_cell; chan->write_packed_cell = chan_test_write_packed_cell; chan->write_var_cell = chan_test_write_var_cell; @@ -176,6 +199,125 @@ scheduler_release_channel_mock(channel_t *ch) return; } +static void +test_channel_queue_size(void *arg) +{ + channel_t *ch = NULL; + cell_t *cell = NULL; + int n, old_count; + uint64_t global_queue_estimate; + + (void)arg; + + ch = new_fake_channel(); + test_assert(ch); + + /* Initial queue size update */ + channel_update_xmit_queue_size(ch); + test_eq(ch->bytes_queued_for_xmit, 0); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 0); + + /* Test the call-through to our fake lower layer */ + n = channel_num_cells_writeable(ch); + /* chan_test_num_cells_writeable() always returns 32 */ + test_eq(n, 32); + + /* + * Now we queue some cells and check that channel_num_cells_writeable() + * adjusts properly + */ + + /* tell it not to accept cells */ + test_chan_accept_cells = 0; + /* ...and keep it from trying to flush the queue */ + ch->state = CHANNEL_STATE_MAINT; + + /* Get a fresh cell */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + + old_count = test_cells_written; + channel_write_cell(ch, cell); + /* Assert that it got queued, not written through, correctly */ + test_eq(test_cells_written, old_count); + + /* Now check chan_test_num_cells_writeable() again */ + n = channel_num_cells_writeable(ch); + test_eq(n, 0); /* Should return 0 since we're in CHANNEL_STATE_MAINT */ + + /* Update queue size estimates */ + channel_update_xmit_queue_size(ch); + /* One cell, times an overhead factor of 1.0 */ + test_eq(ch->bytes_queued_for_xmit, 512); + /* Try a different overhead factor */ + test_overhead_estimate = 0.5f; + /* This one should be ignored since it's below 1.0 */ + channel_update_xmit_queue_size(ch); + test_eq(ch->bytes_queued_for_xmit, 512); + /* Now try a larger one */ + test_overhead_estimate = 2.0f; + channel_update_xmit_queue_size(ch); + test_eq(ch->bytes_queued_for_xmit, 1024); + /* Go back to 1.0 */ + test_overhead_estimate = 1.0f; + channel_update_xmit_queue_size(ch); + test_eq(ch->bytes_queued_for_xmit, 512); + /* Check the global estimate too */ + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 512); + + /* Go to open */ + old_count = test_cells_written; + channel_change_state(ch, CHANNEL_STATE_OPEN); + + /* + * It should try to write, but we aren't accepting cells right now, so + * it'll requeue + */ + test_eq(test_cells_written, old_count); + + /* Check the queue size again */ + channel_update_xmit_queue_size(ch); + test_eq(ch->bytes_queued_for_xmit, 512); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 512); + + /* + * Now the cell is in the queue, and we're open, so we should get 31 + * writeable cells. + */ + n = channel_num_cells_writeable(ch); + test_eq(n, 31); + + /* Accept cells again */ + test_chan_accept_cells = 1; + /* ...and re-process the queue */ + old_count = test_cells_written; + channel_flush_cells(ch); + test_eq(test_cells_written, old_count + 1); + + /* Should have 32 writeable now */ + n = channel_num_cells_writeable(ch); + test_eq(n, 32); + + /* Should have queue size estimate of zero */ + channel_update_xmit_queue_size(ch); + test_eq(ch->bytes_queued_for_xmit, 0); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 0); + + /* Okay, now we're done with this one */ + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + channel_mark_for_close(ch); + UNMOCK(scheduler_release_channel); + + done: + tor_free(ch); + + return; +} + static void test_channel_write(void *arg) { @@ -289,6 +431,7 @@ test_channel_write(void *arg) } struct testcase_t channel_tests[] = { + { "queue_size", test_channel_queue_size, TT_FORK, NULL, NULL }, { "write", test_channel_write, TT_FORK, NULL, NULL }, END_OF_TESTCASES }; From e00fde17975810d7cc420fb185dd84591f89b7a5 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 18 Dec 2013 15:04:00 -0800 Subject: [PATCH 30/79] Implement two-channel queue estimate test --- src/test/test_channel.c | 133 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 08abd96cd6..acbfca1c7d 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -33,6 +33,8 @@ static void make_fake_cell(cell_t *c); static void make_fake_var_cell(var_cell_t *c); static channel_t * new_fake_channel(void); static void scheduler_release_channel_mock(channel_t *ch); + +static void test_channel_multi(void *arg); static void test_channel_queue_size(void *arg); static void test_channel_write(void *arg); @@ -199,6 +201,136 @@ scheduler_release_channel_mock(channel_t *ch) return; } +static void +test_channel_multi(void *arg) +{ + channel_t *ch1 = NULL, *ch2 = NULL; + uint64_t global_queue_estimate; + cell_t *cell = NULL; + + (void)arg; + + /* Accept cells to lower layer */ + test_chan_accept_cells = 1; + /* Use default overhead factor */ + test_overhead_estimate = 1.0f; + + ch1 = new_fake_channel(); + test_assert(ch1); + ch2 = new_fake_channel(); + test_assert(ch2); + + /* Initial queue size update */ + channel_update_xmit_queue_size(ch1); + test_eq(ch1->bytes_queued_for_xmit, 0); + channel_update_xmit_queue_size(ch2); + test_eq(ch2->bytes_queued_for_xmit, 0); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 0); + + /* Queue some cells, check queue estimates */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch1, cell); + + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch2, cell); + + channel_update_xmit_queue_size(ch1); + channel_update_xmit_queue_size(ch2); + test_eq(ch1->bytes_queued_for_xmit, 0); + test_eq(ch2->bytes_queued_for_xmit, 0); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 0); + + /* Stop accepting cells at lower layer */ + test_chan_accept_cells = 0; + + /* Queue some cells and check queue estimates */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch1, cell); + + channel_update_xmit_queue_size(ch1); + test_eq(ch1->bytes_queued_for_xmit, 512); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 512); + + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch2, cell); + + channel_update_xmit_queue_size(ch2); + test_eq(ch2->bytes_queued_for_xmit, 512); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 1024); + + /* Allow cells through again */ + test_chan_accept_cells = 1; + + /* Flush chan 2 */ + channel_flush_cells(ch2); + + /* Update and check queue sizes */ + channel_update_xmit_queue_size(ch1); + channel_update_xmit_queue_size(ch2); + test_eq(ch1->bytes_queued_for_xmit, 512); + test_eq(ch2->bytes_queued_for_xmit, 0); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 512); + + /* Flush chan 1 */ + channel_flush_cells(ch1); + + /* Update and check queue sizes */ + channel_update_xmit_queue_size(ch1); + channel_update_xmit_queue_size(ch2); + test_eq(ch1->bytes_queued_for_xmit, 0); + test_eq(ch2->bytes_queued_for_xmit, 0); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 0); + + /* Now block again */ + test_chan_accept_cells = 0; + + /* Queue some cells */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch1, cell); + + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch2, cell); + + /* Check the estimates */ + channel_update_xmit_queue_size(ch1); + channel_update_xmit_queue_size(ch2); + test_eq(ch1->bytes_queued_for_xmit, 512); + test_eq(ch2->bytes_queued_for_xmit, 512); + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 1024); + + /* Now close channel 2; it should be subtracted from the global queue */ + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + channel_mark_for_close(ch2); + UNMOCK(scheduler_release_channel); + + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 512); + + /* Now free everything */ + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + channel_free_all(); + UNMOCK(scheduler_release_channel); + + done: + tor_free(ch1); + tor_free(ch2); + + return; +} + static void test_channel_queue_size(void *arg) { @@ -431,6 +563,7 @@ test_channel_write(void *arg) } struct testcase_t channel_tests[] = { + { "multi", test_channel_multi, TT_FORK, NULL, NULL }, { "queue_size", test_channel_queue_size, TT_FORK, NULL, NULL }, { "write", test_channel_write, TT_FORK, NULL, NULL }, END_OF_TESTCASES From fd57840a778f591fa175a1933c0b619741010bd1 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 18 Dec 2013 15:31:54 -0800 Subject: [PATCH 31/79] Make scheduler_channel_doesnt_want_writes() mockable --- src/or/scheduler.c | 4 ++-- src/or/scheduler.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 450eb02903..8a6620b049 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -235,8 +235,8 @@ scheduler_evt_callback(evutil_socket_t fd, short events, void *arg) /** Mark a channel as no longer ready to accept writes */ -void -scheduler_channel_doesnt_want_writes(channel_t *chan) +MOCK_IMPL(void, +scheduler_channel_doesnt_want_writes,(channel_t *chan)) { tor_assert(chan); diff --git a/src/or/scheduler.h b/src/or/scheduler.h index 9cdf6c1c4d..0752ae0760 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -21,7 +21,7 @@ void scheduler_init(void); void scheduler_run(void); /* Mark channels as having cells or wanting/not wanting writes */ -void scheduler_channel_doesnt_want_writes(channel_t *chan); +MOCK_DECL(void,scheduler_channel_doesnt_want_writes,(channel_t *chan)); void scheduler_channel_has_waiting_cells(channel_t *chan); void scheduler_channel_wants_writes(channel_t *chan); From 37baef0687e4bef0cfadf927bfbc832a59de32f4 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 18 Dec 2013 15:50:32 -0800 Subject: [PATCH 32/79] Add channel lifecycle test --- src/test/test_channel.c | 114 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index acbfca1c7d..c47d2925b4 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -18,7 +18,9 @@ static int test_chan_accept_cells = 0; static int test_cells_written = 0; static int test_destroy_not_pending_calls = 0; +static int test_doesnt_want_writes_count = 0; static double test_overhead_estimate = 1.0f; +static int test_releases_count = 0; static void channel_note_destroy_not_pending_mock(channel_t *ch, circid_t circid); @@ -32,8 +34,10 @@ static int chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell); static void make_fake_cell(cell_t *c); static void make_fake_var_cell(var_cell_t *c); static channel_t * new_fake_channel(void); +static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch); static void scheduler_release_channel_mock(channel_t *ch); +static void test_channel_lifecycle(void *arg); static void test_channel_multi(void *arg); static void test_channel_queue_size(void *arg); static void test_channel_write(void *arg); @@ -190,6 +194,17 @@ new_fake_channel(void) return chan; } +static void +scheduler_channel_doesnt_want_writes_mock(channel_t *ch) +{ + (void)ch; + + /* Increment counter */ + ++test_doesnt_want_writes_count; + + return; +} + static void scheduler_release_channel_mock(channel_t *ch) { @@ -201,6 +216,104 @@ scheduler_release_channel_mock(channel_t *ch) return; } +static void +test_channel_lifecycle(void *arg) +{ + channel_t *ch1 = NULL, *ch2 = NULL; + cell_t *cell = NULL; + int old_count, init_doesnt_want_writes_count; + int init_releases_count; + + (void)arg; + + /* Mock these for the whole lifecycle test */ + MOCK(scheduler_channel_doesnt_want_writes, + scheduler_channel_doesnt_want_writes_mock); + MOCK(scheduler_release_channel, + scheduler_release_channel_mock); + + /* Cache some initial counter values */ + init_doesnt_want_writes_count = test_doesnt_want_writes_count; + init_releases_count = test_releases_count; + + /* Accept cells to lower layer */ + test_chan_accept_cells = 1; + /* Use default overhead factor */ + test_overhead_estimate = 1.0f; + + ch1 = new_fake_channel(); + test_assert(ch1); + /* Start it off in OPENING */ + ch1->state = CHANNEL_STATE_OPENING; + + /* Try to register it */ + channel_register(ch1); + test_assert(ch1->registered); + + /* Try to write a cell through (should queue) */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + old_count = test_cells_written; + channel_write_cell(ch1, cell); + test_eq(old_count, test_cells_written); + + /* Move it to OPEN and flush */ + channel_change_state(ch1, CHANNEL_STATE_OPEN); + + /* Queue should drain */ + test_eq(old_count + 1, test_cells_written); + + /* Get another one */ + ch2 = new_fake_channel(); + test_assert(ch2); + ch2->state = CHANNEL_STATE_OPENING; + + /* Register */ + channel_register(ch2); + test_assert(ch2->registered); + + /* Check counters */ + test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count); + test_eq(test_releases_count, init_releases_count); + + /* Move ch1 to MAINT */ + channel_change_state(ch1, CHANNEL_STATE_MAINT); + test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); + test_eq(test_releases_count, init_releases_count); + + /* Move ch2 to OPEN */ + channel_change_state(ch2, CHANNEL_STATE_OPEN); + test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); + test_eq(test_releases_count, init_releases_count); + + /* Move ch1 back to OPEN */ + channel_change_state(ch1, CHANNEL_STATE_OPEN); + test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); + test_eq(test_releases_count, init_releases_count); + + /* Mark ch2 for close */ + channel_mark_for_close(ch2); + test_eq(ch2->state, CHANNEL_STATE_CLOSING); + test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); + test_eq(test_releases_count, init_releases_count + 1); + + /* Shut down channels */ + channel_free_all(); + ch1 = ch2 = NULL; + test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); + /* channel_free() calls scheduler_release_channel() */ + test_eq(test_releases_count, init_releases_count + 4); + + done: + tor_free(ch1); + tor_free(ch2); + + UNMOCK(scheduler_channel_doesnt_want_writes); + UNMOCK(scheduler_release_channel); + + return; +} + static void test_channel_multi(void *arg) { @@ -563,6 +676,7 @@ test_channel_write(void *arg) } struct testcase_t channel_tests[] = { + { "lifecycle", test_channel_lifecycle, TT_FORK, NULL, NULL }, { "multi", test_channel_multi, TT_FORK, NULL, NULL }, { "queue_size", test_channel_queue_size, TT_FORK, NULL, NULL }, { "write", test_channel_write, TT_FORK, NULL, NULL }, From ba294ff2dcdbf75c49a478cd9f0ad20fc0ad4b50 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 18 Dec 2013 17:07:54 -0800 Subject: [PATCH 33/79] Implement channel flush unit test --- src/test/test_channel.c | 61 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index c47d2925b4..9e6ef17502 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -37,6 +37,7 @@ static channel_t * new_fake_channel(void); static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch); static void scheduler_release_channel_mock(channel_t *ch); +static void test_channel_flush(void *arg); static void test_channel_lifecycle(void *arg); static void test_channel_multi(void *arg); static void test_channel_queue_size(void *arg); @@ -216,6 +217,65 @@ scheduler_release_channel_mock(channel_t *ch) return; } +static void +test_channel_flush(void *arg) +{ + channel_t *ch = NULL; + cell_t *cell = NULL; + packed_cell_t *p_cell = NULL; + var_cell_t *v_cell = NULL; + int init_count; + + (void)arg; + + init_cell_pool(); + + ch = new_fake_channel(); + test_assert(ch); + + /* Cache the original count */ + init_count = test_cells_written; + + /* Stop accepting so we can queue some */ + test_chan_accept_cells = 0; + + /* Queue a regular cell */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch, cell); + /* It should be queued, so assert that we didn't write it */ + test_eq(test_cells_written, init_count); + + /* Queue a var cell */ + v_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); + make_fake_var_cell(v_cell); + channel_write_var_cell(ch, v_cell); + /* It should be queued, so assert that we didn't write it */ + test_eq(test_cells_written, init_count); + + /* Try a packed cell now */ + p_cell = packed_cell_new(); + test_assert(p_cell); + channel_write_packed_cell(ch, p_cell); + /* It should be queued, so assert that we didn't write it */ + test_eq(test_cells_written, init_count); + + /* Now allow writes through again */ + test_chan_accept_cells = 1; + + /* ...and flush */ + channel_flush_cells(ch); + + /* All three should have gone through */ + test_eq(test_cells_written, init_count + 3); + + done: + tor_free(ch); + free_cell_pool(); + + return; +} + static void test_channel_lifecycle(void *arg) { @@ -676,6 +736,7 @@ test_channel_write(void *arg) } struct testcase_t channel_tests[] = { + { "flush", test_channel_flush, TT_FORK, NULL, NULL }, { "lifecycle", test_channel_lifecycle, TT_FORK, NULL, NULL }, { "multi", test_channel_multi, TT_FORK, NULL, NULL }, { "queue_size", test_channel_queue_size, TT_FORK, NULL, NULL }, From 79b8f14c25ecbb4cda8fe587f18694dedd3ee9c0 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 00:46:57 -0800 Subject: [PATCH 34/79] Expose fake channel utility functions in test suite in fakechans.h, and fix a test_channel.c bug --- src/test/fakechans.h | 16 ++++++++++++++++ src/test/test_channel.c | 35 +++++++++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 src/test/fakechans.h diff --git a/src/test/fakechans.h b/src/test/fakechans.h new file mode 100644 index 0000000000..b474810bc4 --- /dev/null +++ b/src/test/fakechans.h @@ -0,0 +1,16 @@ + /* Copyright (c) 2014, The Tor Project, Inc. */ + /* See LICENSE for licensing information */ + +#ifndef TOR_FAKECHANS_H +#define TOR_FAKECHANS_H + +/** + * \file fakechans.h + * \brief Declarations for fake channels for test suite use + */ + +void make_fake_cell(cell_t *c); +void make_fake_var_cell(var_cell_t *c); +channel_t * new_fake_channel(void); + +#endif /* !defined(TOR_FAKECHANS_H) */ diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 9e6ef17502..1ab7f321bd 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -13,7 +13,10 @@ #include "relay.h" /* For init/free stuff */ #include "scheduler.h" + +/* Test suite stuff */ #include "test.h" +#include "fakechans.h" static int test_chan_accept_cells = 0; static int test_cells_written = 0; @@ -31,9 +34,6 @@ static int chan_test_write_cell(channel_t *ch, cell_t *cell); static int chan_test_write_packed_cell(channel_t *ch, packed_cell_t *packed_cell); static int chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell); -static void make_fake_cell(cell_t *c); -static void make_fake_var_cell(var_cell_t *c); -static channel_t * new_fake_channel(void); static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch); static void scheduler_release_channel_mock(channel_t *ch); @@ -150,7 +150,11 @@ chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell) return rv; } -static void +/** + * Fill out c with a new fake cell for test suite use + */ + +void make_fake_cell(cell_t *c) { test_assert(c != NULL); @@ -163,7 +167,11 @@ make_fake_cell(cell_t *c) return; } -static void +/** + * Fill out c with a new fake var_cell for test suite use + */ + +void make_fake_var_cell(var_cell_t *c) { test_assert(c != NULL); @@ -177,7 +185,11 @@ make_fake_var_cell(var_cell_t *c) return; } -static channel_t * +/** + * Set up a new fake channel for the test suite + */ + +channel_t * new_fake_channel(void) { channel_t *chan = tor_malloc_zero(sizeof(channel_t)); @@ -492,6 +504,17 @@ test_channel_multi(void *arg) global_queue_estimate = channel_get_global_queue_estimate(); test_eq(global_queue_estimate, 512); + /* + * Since the fake channels aren't registered, channel_free_all() can't + * see them properly. + */ + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + channel_mark_for_close(ch1); + UNMOCK(scheduler_release_channel); + + global_queue_estimate = channel_get_global_queue_estimate(); + test_eq(global_queue_estimate, 0); + /* Now free everything */ MOCK(scheduler_release_channel, scheduler_release_channel_mock); channel_free_all(); From bef11b715637ad28f6c8802adf6893e050c05d0a Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 00:58:27 -0800 Subject: [PATCH 35/79] Expose a useful mock from test_channel.c --- src/test/fakechans.h | 3 +++ src/test/test_channel.c | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/test/fakechans.h b/src/test/fakechans.h index b474810bc4..99773e721d 100644 --- a/src/test/fakechans.h +++ b/src/test/fakechans.h @@ -13,4 +13,7 @@ void make_fake_cell(cell_t *c); void make_fake_var_cell(var_cell_t *c); channel_t * new_fake_channel(void); +/* Also exposes some a mock used by both test_channel.c and test_relay.c */ +void scheduler_release_channel_mock(channel_t *ch); + #endif /* !defined(TOR_FAKECHANS_H) */ diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 1ab7f321bd..1ae9810d66 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -35,7 +35,6 @@ static int chan_test_write_packed_cell(channel_t *ch, packed_cell_t *packed_cell); static int chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell); static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch); -static void scheduler_release_channel_mock(channel_t *ch); static void test_channel_flush(void *arg); static void test_channel_lifecycle(void *arg); @@ -218,7 +217,7 @@ scheduler_channel_doesnt_want_writes_mock(channel_t *ch) return; } -static void +void scheduler_release_channel_mock(channel_t *ch) { (void)ch; From ade60890d013e8af25f432cca97193bdc8659701 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 02:49:21 -0800 Subject: [PATCH 36/79] Make scheduler_channel_doesnt_want_writes() mockable --- src/or/scheduler.c | 4 ++-- src/or/scheduler.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 8a6620b049..367480cb31 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -275,8 +275,8 @@ scheduler_channel_doesnt_want_writes,(channel_t *chan)) /** Mark a channel as having waiting cells */ -void -scheduler_channel_has_waiting_cells(channel_t *chan) +MOCK_IMPL(void, +scheduler_channel_has_waiting_cells,(channel_t *chan)) { int became_pending = 0; diff --git a/src/or/scheduler.h b/src/or/scheduler.h index 0752ae0760..7f59ec45d0 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -22,7 +22,7 @@ void scheduler_run(void); /* Mark channels as having cells or wanting/not wanting writes */ MOCK_DECL(void,scheduler_channel_doesnt_want_writes,(channel_t *chan)); -void scheduler_channel_has_waiting_cells(channel_t *chan); +MOCK_DECL(void,scheduler_channel_has_waiting_cells,(channel_t *chan)); void scheduler_channel_wants_writes(channel_t *chan); /* Notify the scheduler of a channel being closed */ From 2ee69bd5d74842681aa3c90aae93b93fb0657269 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 02:49:51 -0800 Subject: [PATCH 37/79] Expose get_unique_circ_id_by_chan() to test suite --- src/or/circuitbuild.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index edf7d2863e..7a23fbaa79 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -14,6 +14,7 @@ #include "or.h" #include "channel.h" #include "circpathbias.h" +#define CIRCUITBUILD_PRIVATE #include "circuitbuild.h" #include "circuitlist.h" #include "circuitstats.h" From 46ff91b6ec90fc8ee9a84622f9dce91903686559 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 02:50:33 -0800 Subject: [PATCH 38/79] Add scheduler_channel_has_waiting_cells_mock() and some mock counter queries --- src/test/fakechans.h | 6 ++++++ src/test/test_channel.c | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/test/fakechans.h b/src/test/fakechans.h index 99773e721d..b129ab49a5 100644 --- a/src/test/fakechans.h +++ b/src/test/fakechans.h @@ -14,6 +14,12 @@ void make_fake_var_cell(var_cell_t *c); channel_t * new_fake_channel(void); /* Also exposes some a mock used by both test_channel.c and test_relay.c */ +void scheduler_channel_has_waiting_cells_mock(channel_t *ch); void scheduler_release_channel_mock(channel_t *ch); +/* Query some counters used by the exposed mocks */ +int get_mock_scheduler_has_waiting_cells_count(void); +int get_mock_scheduler_release_channel_count(void); + #endif /* !defined(TOR_FAKECHANS_H) */ + diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 1ae9810d66..eff418957b 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -22,6 +22,7 @@ static int test_chan_accept_cells = 0; static int test_cells_written = 0; static int test_destroy_not_pending_calls = 0; static int test_doesnt_want_writes_count = 0; +static int test_has_waiting_cells_count = 0; static double test_overhead_estimate = 1.0f; static int test_releases_count = 0; @@ -206,6 +207,31 @@ new_fake_channel(void) return chan; } +/** + * Counter query for scheduler_channel_has_waiting_cells_mock() + */ + +int +get_mock_scheduler_has_waiting_cells_count(void) +{ + return test_has_waiting_cells_count; +} + +/** + * Mock for scheduler_channel_has_waiting_cells() + */ + +void +scheduler_channel_has_waiting_cells_mock(channel_t *ch) +{ + (void)ch; + + /* Increment counter */ + ++test_has_waiting_cells_count; + + return; +} + static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch) { @@ -217,6 +243,20 @@ scheduler_channel_doesnt_want_writes_mock(channel_t *ch) return; } +/** + * Counter query for scheduler_release_channel_mock() + */ + +int +get_mock_scheduler_release_channel_count(void) +{ + return test_releases_count; +} + +/** + * Mock for scheduler_release_channel() + */ + void scheduler_release_channel_mock(channel_t *ch) { From 5992a69deee324fb91f5f2995e145edd2c0560d2 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 02:54:35 -0800 Subject: [PATCH 39/79] Add append_cell_to_circuit_queue() unit test --- src/test/Makefile.nmake | 4 +- src/test/include.am | 1 + src/test/test.c | 2 + src/test/test_relay.c | 130 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 src/test/test_relay.c diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index fceef99564..2a347bd677 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -14,8 +14,8 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ TEST_OBJECTS = test.obj test_addr.obj test_channel.obj test_containers.obj \ test_controller_events.ogj test_crypto.obj test_data.obj test_dir.obj \ test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ - test_cell_formats.obj test_replay.obj test_introduce.obj tinytest.obj \ - test_hs.obj + test_cell_formats.obj test_relay.obj test_replay.obj \ + test_introduce.obj tinytest.obj test_hs.obj tinytest.obj: ..\ext\tinytest.c $(CC) $(CFLAGS) /D snprintf=_snprintf /c ..\ext\tinytest.c diff --git a/src/test/include.am b/src/test/include.am index 547f23b733..531b81ef52 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -38,6 +38,7 @@ src_test_test_SOURCES = \ src/test/test_options.c \ src/test/test_pt.c \ src/test/test_relaycell.c \ + src/test/test_relay.c \ src/test/test_replay.c \ src/test/test_routerkeys.c \ src/test/test_socks.c \ diff --git a/src/test/test.c b/src/test/test.c index 4d4f6f23a4..7ade83d907 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1310,6 +1310,7 @@ extern struct testcase_t status_tests[]; extern struct testcase_t routerset_tests[]; extern struct testcase_t router_tests[]; extern struct testcase_t channel_tests[]; +extern struct testcase_t relay_tests[]; static struct testgroup_t testgroups[] = { { "", test_array }, @@ -1343,6 +1344,7 @@ static struct testgroup_t testgroups[] = { { "status/" , status_tests }, { "routerset/" , routerset_tests }, { "channel/", channel_tests }, + { "relay/" , relay_tests }, END_OF_GROUPS }; diff --git a/src/test/test_relay.c b/src/test/test_relay.c new file mode 100644 index 0000000000..6b9cb33ca3 --- /dev/null +++ b/src/test/test_relay.c @@ -0,0 +1,130 @@ +/* Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#define CIRCUITBUILD_PRIVATE +#include "circuitbuild.h" +#define RELAY_PRIVATE +#include "relay.h" +/* For init/free stuff */ +#include "scheduler.h" + +/* Test suite stuff */ +#include "test.h" +#include "fakechans.h" + +static or_circuit_t * new_fake_orcirc(channel_t *nchan, channel_t *pchan); + +static void test_relay_append_cell_to_circuit_queue(void *arg); + +static or_circuit_t * +new_fake_orcirc(channel_t *nchan, channel_t *pchan) +{ + or_circuit_t *orcirc = NULL; + circuit_t *circ = NULL; + + orcirc = tor_malloc_zero(sizeof(*orcirc)); + circ = &(orcirc->base_); + circ->magic = OR_CIRCUIT_MAGIC; + + circ->n_chan = nchan; + circ->n_circ_id = get_unique_circ_id_by_chan(nchan); + circ->n_mux = NULL; /* ?? */ + cell_queue_init(&(circ->n_chan_cells)); + circ->n_hop = NULL; + circ->streams_blocked_on_n_chan = 0; + circ->streams_blocked_on_p_chan = 0; + circ->n_delete_pending = 0; + circ->p_delete_pending = 0; + circ->received_destroy = 0; + circ->state = CIRCUIT_STATE_OPEN; + circ->purpose = CIRCUIT_PURPOSE_OR; + circ->package_window = CIRCWINDOW_START_MAX; + circ->deliver_window = CIRCWINDOW_START_MAX; + circ->n_chan_create_cell = NULL; + + orcirc->p_chan = pchan; + orcirc->p_circ_id = get_unique_circ_id_by_chan(pchan); + cell_queue_init(&(orcirc->p_chan_cells)); + + return orcirc; +} + +static void +test_relay_append_cell_to_circuit_queue(void *arg) +{ + channel_t *nchan = NULL, *pchan = NULL; + or_circuit_t *orcirc = NULL; + cell_t *cell = NULL; + int old_count, new_count; + + (void)arg; + + /* We'll need the cell pool for append_cell_to_circuit_queue() to work */ + init_cell_pool(); + + /* Make fake channels to be nchan and pchan for the circuit */ + nchan = new_fake_channel(); + test_assert(nchan); + + pchan = new_fake_channel(); + test_assert(pchan); + + /* We'll need chans with working cmuxes */ + nchan->cmux = circuitmux_alloc(); + pchan->cmux = circuitmux_alloc(); + + /* Make a fake orcirc */ + orcirc = new_fake_orcirc(nchan, pchan); + test_assert(orcirc); + + /* Make a cell */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + + MOCK(scheduler_channel_has_waiting_cells, + scheduler_channel_has_waiting_cells_mock); + + /* Append it */ + old_count = get_mock_scheduler_has_waiting_cells_count(); + append_cell_to_circuit_queue(TO_CIRCUIT(orcirc), nchan, cell, + CELL_DIRECTION_OUT, 0); + new_count = get_mock_scheduler_has_waiting_cells_count(); + test_eq(new_count, old_count + 1); + + /* Now try the reverse direction */ + old_count = get_mock_scheduler_has_waiting_cells_count(); + append_cell_to_circuit_queue(TO_CIRCUIT(orcirc), pchan, cell, + CELL_DIRECTION_IN, 0); + new_count = get_mock_scheduler_has_waiting_cells_count(); + test_eq(new_count, old_count + 1); + + UNMOCK(scheduler_channel_has_waiting_cells); + + /* Get rid of the fake channels */ + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + channel_mark_for_close(nchan); + channel_mark_for_close(pchan); + UNMOCK(scheduler_release_channel); + + /* Shut down channels */ + channel_free_all(); + nchan = pchan = NULL; + + done: + tor_free(orcirc); + if (nchan && nchan->cmux) circuitmux_free(nchan->cmux); + tor_free(nchan); + if (pchan && pchan->cmux) circuitmux_free(pchan->cmux); + tor_free(pchan); + free_cell_pool(); + + return; +} + +struct testcase_t relay_tests[] = { + { "append_cell_to_circuit_queue", test_relay_append_cell_to_circuit_queue, + TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + From bbb06b73cd46d8e603b9a5b99f2cdd28e31888eb Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 20:51:21 -0800 Subject: [PATCH 40/79] Expose some channel cell queue stuff to the test suite --- src/or/channel.c | 32 +++++--------------------------- src/or/channel.h | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index 9e3e452d0b..76ade4fbdd 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -13,6 +13,9 @@ #define TOR_CHANNEL_INTERNAL_ +/* This one's for stuff only channel.c and the test suite should see */ +#define CHANNEL_PRIVATE_ + #include "or.h" #include "channel.h" #include "channeltls.h" @@ -31,29 +34,6 @@ #include "routerlist.h" #include "scheduler.h" -/* Cell queue structure */ - -typedef struct cell_queue_entry_s cell_queue_entry_t; -struct cell_queue_entry_s { - TOR_SIMPLEQ_ENTRY(cell_queue_entry_s) next; - enum { - CELL_QUEUE_FIXED, - CELL_QUEUE_VAR, - CELL_QUEUE_PACKED - } type; - union { - struct { - cell_t *cell; - } fixed; - struct { - var_cell_t *var_cell; - } var; - struct { - packed_cell_t *packed_cell; - } packed; - } u; -}; - /* Global lists of channels */ /* All channel_t instances */ @@ -175,7 +155,6 @@ static cell_queue_entry_t * cell_queue_entry_new_fixed(cell_t *cell); static cell_queue_entry_t * cell_queue_entry_new_var(var_cell_t *var_cell); -static int chan_cell_queue_len(const chan_cell_queue_t *queue); static int is_destroy_cell(channel_t *chan, const cell_queue_entry_t *q, circid_t *circid_out); @@ -1751,9 +1730,8 @@ channel_get_cell_queue_entry_size(channel_t *chan, cell_queue_entry_t *q) rv = get_cell_network_size(chan->wide_circ_ids); break; case CELL_QUEUE_VAR: - tor_assert(q->u.var.var_cell); rv = get_var_cell_header_size(chan->wide_circ_ids) + - q->u.var.var_cell->payload_len; + (q->u.var.var_cell ? q->u.var.var_cell->payload_len : 0); break; case CELL_QUEUE_PACKED: rv = get_cell_network_size(chan->wide_circ_ids); @@ -3455,7 +3433,7 @@ channel_listener_describe_transport(channel_listener_t *chan_l) /** * Return the number of entries in queue */ -static int +STATIC int chan_cell_queue_len(const chan_cell_queue_t *queue) { int r = 0; diff --git a/src/or/channel.h b/src/or/channel.h index 023c39d0dd..ba6d66b350 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -354,6 +354,34 @@ void channel_set_cmux_policy_everywhere(circuitmux_policy_t *pol); #ifdef TOR_CHANNEL_INTERNAL_ +#ifdef CHANNEL_PRIVATE_ +/* Cell queue structure (here rather than channel.c for test suite use) */ + +typedef struct cell_queue_entry_s cell_queue_entry_t; +struct cell_queue_entry_s { + TOR_SIMPLEQ_ENTRY(cell_queue_entry_s) next; + enum { + CELL_QUEUE_FIXED, + CELL_QUEUE_VAR, + CELL_QUEUE_PACKED + } type; + union { + struct { + cell_t *cell; + } fixed; + struct { + var_cell_t *var_cell; + } var; + struct { + packed_cell_t *packed_cell; + } packed; + } u; +}; + +/* Cell queue functions for benefit of test suite */ +STATIC int chan_cell_queue_len(const chan_cell_queue_t *queue); +#endif + /* Channel operations for subclasses and internal use only */ /* Initialize a newly allocated channel - do this first in subclass From b6d0aaec07e1251af3735f4d5ec8250c4f9ce470 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 20:51:50 -0800 Subject: [PATCH 41/79] Check some can't-happen cases draining channel cell queues --- src/test/test_channel.c | 125 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index eff418957b..8c0b06336b 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -2,6 +2,7 @@ /* See LICENSE for licensing information */ #define TOR_CHANNEL_INTERNAL_ +#define CHANNEL_PRIVATE_ #include "or.h" #include "channel.h" /* For channel_note_destroy_not_pending */ @@ -566,6 +567,129 @@ test_channel_multi(void *arg) return; } +/** + * Check some hopefully-impossible edge cases in the channel queue we + * can only trigger by doing evil things to the queue directly. + */ + +static void +test_channel_queue_impossible(void *arg) +{ + channel_t *ch = NULL; + cell_t *cell = NULL; + packed_cell_t *packed_cell = NULL; + var_cell_t *var_cell = NULL; + int old_count; + cell_queue_entry_t *q = NULL; + + (void)arg; + + init_cell_pool(); + + packed_cell = packed_cell_new(); + test_assert(packed_cell); + + ch = new_fake_channel(); + test_assert(ch); + + /* We test queueing here; tell it not to accept cells */ + test_chan_accept_cells = 0; + /* ...and keep it from trying to flush the queue */ + ch->state = CHANNEL_STATE_MAINT; + + /* Cache the cell written count */ + old_count = test_cells_written; + + /* Assert that the queue is initially empty */ + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + + /* Get a fresh cell and write it to the channel*/ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch, cell); + + /* Now it should be queued */ + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)),1); + q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); + test_assert(q); + if (q) { + test_eq(q->type, CELL_QUEUE_FIXED); + test_eq(q->u.fixed.cell, cell); + } + /* Do perverse things to it */ + tor_free(q->u.fixed.cell); + q->u.fixed.cell = NULL; + + /* + * Now change back to open with channel_change_state() and assert that it + * gets thrown away properly. + */ + test_chan_accept_cells = 1; + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_assert(test_cells_written == old_count); + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + + /* Same thing but for a var_cell */ + + test_chan_accept_cells = 0; + ch->state = CHANNEL_STATE_MAINT; + var_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); + make_fake_var_cell(var_cell); + channel_write_var_cell(ch, var_cell); + + /* Check that it's queued */ + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 1); + q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); + test_assert(q); + if (q) { + test_eq(q->type, CELL_QUEUE_VAR); + test_eq(q->u.var.var_cell, var_cell); + } + + /* Remove the cell from the queue entry */ + tor_free(q->u.var.var_cell); + q->u.var.var_cell = NULL; + + /* Let it drain and check that the bad entry is discarded */ + test_chan_accept_cells = 1; + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_assert(test_cells_written == old_count); + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + + /* Same thing with a packed_cell */ + + test_chan_accept_cells = 0; + ch->state = CHANNEL_STATE_MAINT; + packed_cell = packed_cell_new(); + test_assert(packed_cell); + channel_write_packed_cell(ch, packed_cell); + + /* Check that it's queued */ + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 1); + q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); + test_assert(q); + if (q) { + test_eq(q->type, CELL_QUEUE_PACKED); + test_eq(q->u.packed.packed_cell, packed_cell); + } + + /* Remove the cell from the queue entry */ + packed_cell_free(q->u.packed.packed_cell); + q->u.packed.packed_cell = NULL; + + /* Let it drain and check that the bad entry is discarded */ + test_chan_accept_cells = 1; + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_assert(test_cells_written == old_count); + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + + done: + tor_free(ch); + free_cell_pool(); + + return; +} + static void test_channel_queue_size(void *arg) { @@ -801,6 +925,7 @@ struct testcase_t channel_tests[] = { { "flush", test_channel_flush, TT_FORK, NULL, NULL }, { "lifecycle", test_channel_lifecycle, TT_FORK, NULL, NULL }, { "multi", test_channel_multi, TT_FORK, NULL, NULL }, + { "queue_impossible", test_channel_queue_impossible, TT_FORK, NULL, NULL }, { "queue_size", test_channel_queue_size, TT_FORK, NULL, NULL }, { "write", test_channel_write, TT_FORK, NULL, NULL }, END_OF_TESTCASES From 3b78667d65848d0fbbe73a57874312ddd7f88140 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 22:20:59 -0800 Subject: [PATCH 42/79] Unit test for unusual channel lifecycles --- src/test/test_channel.c | 204 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 8c0b06336b..9cc709dee0 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -7,6 +7,7 @@ #include "channel.h" /* For channel_note_destroy_not_pending */ #include "circuitlist.h" +#include "circuitmux.h" /* For var_cell_free */ #include "connection_or.h" /* For packed_cell stuff */ @@ -30,6 +31,8 @@ static int test_releases_count = 0; static void channel_note_destroy_not_pending_mock(channel_t *ch, circid_t circid); static void chan_test_close(channel_t *ch); +static void chan_test_error(channel_t *ch); +static void chan_test_finish_close(channel_t *ch); static size_t chan_test_num_bytes_queued(channel_t *ch); static int chan_test_num_cells_writeable(channel_t *ch); static int chan_test_write_cell(channel_t *ch, cell_t *cell); @@ -63,6 +66,40 @@ chan_test_close(channel_t *ch) return; } +/* + * Close a channel through the error path + */ + +static void +chan_test_error(channel_t *ch) +{ + test_assert(ch); + test_assert(!(ch->state == CHANNEL_STATE_CLOSING || + ch->state == CHANNEL_STATE_ERROR || + ch->state == CHANNEL_STATE_CLOSED)); + + channel_close_for_error(ch); + + done: + return; +} + +/* + * Finish closing a channel from CHANNEL_STATE_CLOSING + */ + +static void +chan_test_finish_close(channel_t *ch) +{ + test_assert(ch); + test_assert(ch->state == CHANNEL_STATE_CLOSING); + + channel_closed(ch); + + done: + return; +} + static double chan_test_get_overhead_estimate(channel_t *ch) { @@ -328,6 +365,12 @@ test_channel_flush(void *arg) return; } +/** + * Normal channel lifecycle test: + * + * OPENING->OPEN->MAINT->OPEN->CLOSING->CLOSED + */ + static void test_channel_lifecycle(void *arg) { @@ -426,6 +469,166 @@ test_channel_lifecycle(void *arg) return; } +/** + * Weird channel lifecycle test: + * + * OPENING->CLOSING->CLOSED + * OPENING->OPEN->CLOSING->ERROR + * OPENING->OPEN->MAINT->CLOSING->CLOSED + * OPENING->OPEN->MAINT->CLOSING->ERROR + */ + +static void +test_channel_lifecycle_2(void *arg) +{ + channel_t *ch = NULL; + + (void)arg; + + /* Mock these for the whole lifecycle test */ + MOCK(scheduler_channel_doesnt_want_writes, + scheduler_channel_doesnt_want_writes_mock); + MOCK(scheduler_release_channel, + scheduler_release_channel_mock); + + /* Accept cells to lower layer */ + test_chan_accept_cells = 1; + /* Use default overhead factor */ + test_overhead_estimate = 1.0f; + + ch = new_fake_channel(); + test_assert(ch); + /* Start it off in OPENING */ + ch->state = CHANNEL_STATE_OPENING; + /* The full lifecycle test needs a cmux */ + ch->cmux = circuitmux_alloc(); + + /* Try to register it */ + channel_register(ch); + test_assert(ch->registered); + + /* Try to close it */ + channel_mark_for_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSING); + + /* Finish closing it */ + chan_test_finish_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSED); + channel_run_cleanup(); + ch = NULL; + + /* Now try OPENING->OPEN->CLOSING->ERROR */ + ch = new_fake_channel(); + test_assert(ch); + ch->state = CHANNEL_STATE_OPENING; + ch->cmux = circuitmux_alloc(); + channel_register(ch); + test_assert(ch->registered); + + /* Finish opening it */ + channel_change_state(ch, CHANNEL_STATE_OPEN); + + /* Error exit from lower layer */ + chan_test_error(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSING); + chan_test_finish_close(ch); + test_eq(ch->state, CHANNEL_STATE_ERROR); + channel_run_cleanup(); + ch = NULL; + + /* OPENING->OPEN->MAINT->CLOSING->CLOSED close from maintenance state */ + ch = new_fake_channel(); + test_assert(ch); + ch->state = CHANNEL_STATE_OPENING; + ch->cmux = circuitmux_alloc(); + channel_register(ch); + test_assert(ch->registered); + + /* Finish opening it */ + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_eq(ch->state, CHANNEL_STATE_OPEN); + + /* Go to maintenance state */ + channel_change_state(ch, CHANNEL_STATE_MAINT); + test_eq(ch->state, CHANNEL_STATE_MAINT); + + /* Lower layer close */ + channel_mark_for_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSING); + + /* Finish */ + chan_test_finish_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSED); + channel_run_cleanup(); + ch = NULL; + + /* + * OPENING->OPEN->MAINT->CLOSING->CLOSED lower-layer close during + * maintenance state + */ + ch = new_fake_channel(); + test_assert(ch); + ch->state = CHANNEL_STATE_OPENING; + ch->cmux = circuitmux_alloc(); + channel_register(ch); + test_assert(ch->registered); + + /* Finish opening it */ + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_eq(ch->state, CHANNEL_STATE_OPEN); + + /* Go to maintenance state */ + channel_change_state(ch, CHANNEL_STATE_MAINT); + test_eq(ch->state, CHANNEL_STATE_MAINT); + + /* Lower layer close */ + channel_close_from_lower_layer(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSING); + + /* Finish */ + chan_test_finish_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSED); + channel_run_cleanup(); + ch = NULL; + + /* OPENING->OPEN->MAINT->CLOSING->ERROR */ + ch = new_fake_channel(); + test_assert(ch); + ch->state = CHANNEL_STATE_OPENING; + ch->cmux = circuitmux_alloc(); + channel_register(ch); + test_assert(ch->registered); + + /* Finish opening it */ + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_eq(ch->state, CHANNEL_STATE_OPEN); + + /* Go to maintenance state */ + channel_change_state(ch, CHANNEL_STATE_MAINT); + test_eq(ch->state, CHANNEL_STATE_MAINT); + + /* Lower layer close */ + chan_test_error(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSING); + + /* Finish */ + chan_test_finish_close(ch); + test_eq(ch->state, CHANNEL_STATE_ERROR); + channel_run_cleanup(); + ch = NULL; + + /* Shut down channels */ + channel_free_all(); + + done: + tor_free(ch); + + UNMOCK(scheduler_channel_doesnt_want_writes); + UNMOCK(scheduler_release_channel); + + return; +} + static void test_channel_multi(void *arg) { @@ -924,6 +1127,7 @@ test_channel_write(void *arg) struct testcase_t channel_tests[] = { { "flush", test_channel_flush, TT_FORK, NULL, NULL }, { "lifecycle", test_channel_lifecycle, TT_FORK, NULL, NULL }, + { "lifecycle_2", test_channel_lifecycle_2, TT_FORK, NULL, NULL }, { "multi", test_channel_multi, TT_FORK, NULL, NULL }, { "queue_impossible", test_channel_queue_impossible, TT_FORK, NULL, NULL }, { "queue_size", test_channel_queue_size, TT_FORK, NULL, NULL }, From ae3ed185e495499516173d6c5dd51ea49a0aa9c1 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 22:30:02 -0800 Subject: [PATCH 43/79] Let channel unit tests mess with global queue estimate --- src/or/channel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/or/channel.c b/src/or/channel.c index 76ade4fbdd..18c236fe66 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -109,7 +109,7 @@ static uint64_t n_channel_bytes_in_queues = 0; * transmit overhead* */ -static uint64_t estimated_total_queue_size = 0; +STATIC uint64_t estimated_total_queue_size = 0; /* Digest->channel map * From f7951d318a29e8f9af5af080160902b62ee278b7 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 22:33:04 -0800 Subject: [PATCH 44/79] Small channel unit test improvements --- src/test/test_channel.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 9cc709dee0..2f07413ad7 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -20,6 +20,9 @@ #include "test.h" #include "fakechans.h" +/* This comes from channel.c */ +extern uint64_t estimated_total_queue_size; + static int test_chan_accept_cells = 0; static int test_cells_written = 0; static int test_destroy_not_pending_calls = 0; @@ -400,6 +403,8 @@ test_channel_lifecycle(void *arg) test_assert(ch1); /* Start it off in OPENING */ ch1->state = CHANNEL_STATE_OPENING; + /* We'll need a cmux */ + ch1->cmux = circuitmux_alloc(); /* Try to register it */ channel_register(ch1); @@ -422,6 +427,7 @@ test_channel_lifecycle(void *arg) ch2 = new_fake_channel(); test_assert(ch2); ch2->state = CHANNEL_STATE_OPENING; + ch2->cmux = circuitmux_alloc(); /* Register */ channel_register(ch2); @@ -784,6 +790,10 @@ test_channel_queue_impossible(void *arg) var_cell_t *var_cell = NULL; int old_count; cell_queue_entry_t *q = NULL; + uint64_t global_queue_estimate; + + /* Cache the global queue size (see below) */ + global_queue_estimate = channel_get_global_queue_estimate(); (void)arg; @@ -890,6 +900,13 @@ test_channel_queue_impossible(void *arg) tor_free(ch); free_cell_pool(); + /* + * Doing that meant that we couldn't correctly adjust the queue size + * for the var cell, so manually reset the global queue size estimate + * so the next test doesn't break if we run with --no-fork. + */ + estimated_total_queue_size = global_queue_estimate; + return; } From b5d4ef18e172c18dd169570b17dfb14a4a270ae1 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 21 Jan 2014 22:54:40 -0800 Subject: [PATCH 45/79] Add unknown cell queue entry type case to channel/queue_impossible unit test --- src/test/test_channel.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 2f07413ad7..add57deb50 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -896,6 +896,32 @@ test_channel_queue_impossible(void *arg) test_assert(test_cells_written == old_count); test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + /* Unknown cell type case */ + test_chan_accept_cells = 0; + ch->state = CHANNEL_STATE_MAINT; + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + channel_write_cell(ch, cell); + + /* Check that it's queued */ + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)),1); + q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); + test_assert(q); + if (q) { + test_eq(q->type, CELL_QUEUE_FIXED); + test_eq(q->u.fixed.cell, cell); + } + /* Clobber it, including the queue entry type */ + tor_free(q->u.fixed.cell); + q->u.fixed.cell = NULL; + q->type = CELL_QUEUE_PACKED + 1; + + /* Let it drain and check that the bad entry is discarded */ + test_chan_accept_cells = 1; + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_assert(test_cells_written == old_count); + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + done: tor_free(ch); free_cell_pool(); From 76fcb8cb55d91aa66699266b00ff5379e3c99461 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 00:06:06 -0800 Subject: [PATCH 46/79] Add channel/incoming unit test --- src/test/test_channel.c | 123 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index add57deb50..18746df26c 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -24,6 +24,8 @@ extern uint64_t estimated_total_queue_size; static int test_chan_accept_cells = 0; +static int test_chan_fixed_cells_recved = 0; +static int test_chan_var_cells_recved = 0; static int test_cells_written = 0; static int test_destroy_not_pending_calls = 0; static int test_doesnt_want_writes_count = 0; @@ -33,6 +35,10 @@ static int test_releases_count = 0; static void channel_note_destroy_not_pending_mock(channel_t *ch, circid_t circid); +static void chan_test_cell_handler(channel_t *ch, + cell_t *cell); +static void chan_test_var_cell_handler(channel_t *ch, + var_cell_t *var_cell); static void chan_test_close(channel_t *ch); static void chan_test_error(channel_t *ch); static void chan_test_finish_close(channel_t *ch); @@ -45,6 +51,7 @@ static int chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell); static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch); static void test_channel_flush(void *arg); +static void test_channel_incoming(void *arg); static void test_channel_lifecycle(void *arg); static void test_channel_multi(void *arg); static void test_channel_queue_size(void *arg); @@ -60,6 +67,42 @@ channel_note_destroy_not_pending_mock(channel_t *ch, ++test_destroy_not_pending_calls; } +/* + * Handle an incoming fixed-size cell for unit tests + */ + +static void +chan_test_cell_handler(channel_t *ch, + cell_t *cell) +{ + test_assert(ch); + test_assert(cell); + + tor_free(cell); + ++test_chan_fixed_cells_recved; + + done: + return; +} + +/* + * Handle an incoming variable-size cell for unit tests + */ + +static void +chan_test_var_cell_handler(channel_t *ch, + var_cell_t *var_cell) +{ + test_assert(ch); + test_assert(var_cell); + + tor_free(var_cell); + ++test_chan_var_cells_recved; + + done: + return; +} + static void chan_test_close(channel_t *ch) { @@ -368,6 +411,85 @@ test_channel_flush(void *arg) return; } +static void +test_channel_incoming(void *arg) +{ + channel_t *ch = NULL; + cell_t *cell = NULL; + var_cell_t *var_cell = NULL; + int old_count; + + (void)arg; + + /* Mock these for duration of the test */ + MOCK(scheduler_channel_doesnt_want_writes, + scheduler_channel_doesnt_want_writes_mock); + MOCK(scheduler_release_channel, + scheduler_release_channel_mock); + + /* Accept cells to lower layer */ + test_chan_accept_cells = 1; + /* Use default overhead factor */ + test_overhead_estimate = 1.0f; + + ch = new_fake_channel(); + test_assert(ch); + /* Start it off in OPENING */ + ch->state = CHANNEL_STATE_OPENING; + /* We'll need a cmux */ + ch->cmux = circuitmux_alloc(); + + /* Install incoming cell handlers */ + channel_set_cell_handlers(ch, + chan_test_cell_handler, + chan_test_var_cell_handler); + /* Test cell handler getters */ + test_eq(channel_get_cell_handler(ch), chan_test_cell_handler); + test_eq(channel_get_var_cell_handler(ch), chan_test_var_cell_handler); + + /* Try to register it */ + channel_register(ch); + test_assert(ch->registered); + + /* Open it */ + channel_change_state(ch, CHANNEL_STATE_OPEN); + test_eq(ch->state, CHANNEL_STATE_OPEN); + + /* Receive a fixed cell */ + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + old_count = test_chan_fixed_cells_recved; + channel_queue_cell(ch, cell); + cell = NULL; + test_eq(test_chan_fixed_cells_recved, old_count + 1); + + /* Receive a variable-size cell */ + var_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); + make_fake_var_cell(var_cell); + old_count = test_chan_var_cells_recved; + channel_queue_var_cell(ch, var_cell); + var_cell = NULL; + test_eq(test_chan_var_cells_recved, old_count + 1); + + /* Close it */ + channel_mark_for_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSING); + chan_test_finish_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSED); + channel_run_cleanup(); + ch = NULL; + + done: + tor_free(ch); + tor_free(cell); + tor_free(var_cell); + + UNMOCK(scheduler_channel_doesnt_want_writes); + UNMOCK(scheduler_release_channel); + + return; +} + /** * Normal channel lifecycle test: * @@ -1169,6 +1291,7 @@ test_channel_write(void *arg) struct testcase_t channel_tests[] = { { "flush", test_channel_flush, TT_FORK, NULL, NULL }, + { "incoming", test_channel_incoming, TT_FORK, NULL, NULL }, { "lifecycle", test_channel_lifecycle, TT_FORK, NULL, NULL }, { "lifecycle_2", test_channel_lifecycle_2, TT_FORK, NULL, NULL }, { "multi", test_channel_multi, TT_FORK, NULL, NULL }, From 5b7a58f7c43c98c1f1fbb6bc347263c5247a5ab7 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 01:00:25 -0800 Subject: [PATCH 47/79] Make circuitmux_num_cells() mockable --- src/or/circuitmux.c | 4 ++-- src/or/circuitmux.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 29c3be17d5..71bc4efc4a 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -896,8 +896,8 @@ circuitmux_num_cells_for_circuit(circuitmux_t *cmux, circuit_t *circ) * Query total number of available cells on a circuitmux */ -unsigned int -circuitmux_num_cells(circuitmux_t *cmux) +MOCK_IMPL(unsigned int, +circuitmux_num_cells, (circuitmux_t *cmux)) { tor_assert(cmux); diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index 5833ee5eee..9a5ea7d675 100644 --- a/src/or/circuitmux.h +++ b/src/or/circuitmux.h @@ -120,7 +120,7 @@ int circuitmux_is_circuit_attached(circuitmux_t *cmux, circuit_t *circ); int circuitmux_is_circuit_active(circuitmux_t *cmux, circuit_t *circ); unsigned int circuitmux_num_cells_for_circuit(circuitmux_t *cmux, circuit_t *circ); -unsigned int circuitmux_num_cells(circuitmux_t *cmux); +MOCK_DECL(unsigned int, circuitmux_num_cells, (circuitmux_t *cmux)); unsigned int circuitmux_num_circuits(circuitmux_t *cmux); unsigned int circuitmux_num_active_circuits(circuitmux_t *cmux); From 9eea42f844801f673b90788ff4f5389b300d81fb Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 01:04:39 -0800 Subject: [PATCH 48/79] Make channel_flush_from_first_active_circuit() mockable --- src/or/relay.c | 4 ++-- src/or/relay.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/or/relay.c b/src/or/relay.c index b653616c2c..fee2eec39d 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -2592,8 +2592,8 @@ packed_cell_get_circid(const packed_cell_t *cell, int wide_circ_ids) * queue of the first active circuit on chan, and write them to * chan->outbuf. Return the number of cells written. Advance * the active circuit pointer to the next active circuit in the ring. */ -int -channel_flush_from_first_active_circuit(channel_t *chan, int max) +MOCK_IMPL(int, +channel_flush_from_first_active_circuit, (channel_t *chan, int max)) { circuitmux_t *cmux = NULL; int n_flushed = 0; diff --git a/src/or/relay.h b/src/or/relay.h index 969c6fb61d..68b4fdf197 100644 --- a/src/or/relay.h +++ b/src/or/relay.h @@ -64,7 +64,8 @@ void append_cell_to_circuit_queue(circuit_t *circ, channel_t *chan, cell_t *cell, cell_direction_t direction, streamid_t fromstream); void channel_unlink_all_circuits(channel_t *chan, smartlist_t *detached_out); -int channel_flush_from_first_active_circuit(channel_t *chan, int max); +MOCK_DECL(int, channel_flush_from_first_active_circuit, + (channel_t *chan, int max)); void assert_circuit_mux_okay(channel_t *chan); void update_circuit_on_cmux_(circuit_t *circ, cell_direction_t direction, const char *file, int lineno); From f12f7159a53f191a981104bae3f87d65a581e1f8 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 02:11:59 -0800 Subject: [PATCH 49/79] Add channel/flushmux unit test --- src/test/test_channel.c | 118 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 18746df26c..2f5e9465a7 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -32,7 +32,12 @@ static int test_doesnt_want_writes_count = 0; static int test_has_waiting_cells_count = 0; static double test_overhead_estimate = 1.0f; static int test_releases_count = 0; +static circuitmux_t *test_target_cmux = NULL; +static unsigned int test_cmux_cells = 0; +static int chan_test_channel_flush_from_first_active_circuit_mock( + channel_t *chan, int max); +static unsigned int chan_test_circuitmux_num_cells_mock(circuitmux_t *cmux); static void channel_note_destroy_not_pending_mock(channel_t *ch, circid_t circid); static void chan_test_cell_handler(channel_t *ch, @@ -51,6 +56,7 @@ static int chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell); static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch); static void test_channel_flush(void *arg); +static void test_channel_flushmux(void *arg); static void test_channel_incoming(void *arg); static void test_channel_lifecycle(void *arg); static void test_channel_multi(void *arg); @@ -67,6 +73,62 @@ channel_note_destroy_not_pending_mock(channel_t *ch, ++test_destroy_not_pending_calls; } +/** + * If the target cmux is the cmux for chan, make fake cells up to the + * target number of cells and write them to chan. Otherwise, invoke + * the real channel_flush_from_first_active_circuit(). + */ + +static int +chan_test_channel_flush_from_first_active_circuit_mock(channel_t *chan, + int max) +{ + int result = 0, c = 0; + packed_cell_t *cell = NULL; + + test_assert(chan != NULL); + if (test_target_cmux != NULL && + test_target_cmux == chan->cmux) { + while (c <= max && test_cmux_cells > 0) { + cell = packed_cell_new(); + channel_write_packed_cell(chan, cell); + ++c; + --test_cmux_cells; + } + result = c; + } else { + result = channel_flush_from_first_active_circuit__real(chan, max); + } + + done: + return result; +} + +/** + * If we have a target cmux set and this matches it, lie about how + * many cells we have according to the number indicated; otherwise + * pass to the real circuitmux_num_cells(). + */ + +static unsigned int +chan_test_circuitmux_num_cells_mock(circuitmux_t *cmux) +{ + unsigned int result = 0; + + test_assert(cmux != NULL); + if (cmux != NULL) { + if (cmux == test_target_cmux) { + result = test_cmux_cells; + } else { + result = circuitmux_num_cells__real(cmux); + } + } + + done: + + return result; +} + /* * Handle an incoming fixed-size cell for unit tests */ @@ -411,6 +473,61 @@ test_channel_flush(void *arg) return; } +/** + * Channel flush tests that require cmux mocking + */ + +static void +test_channel_flushmux(void *arg) +{ + channel_t *ch = NULL; + int old_count; + ssize_t result; + + (void)arg; + + init_cell_pool(); + + /* Install mocks we need for this test */ + MOCK(channel_flush_from_first_active_circuit, + chan_test_channel_flush_from_first_active_circuit_mock); + MOCK(circuitmux_num_cells, + chan_test_circuitmux_num_cells_mock); + + ch = new_fake_channel(); + test_assert(ch); + ch->cmux = circuitmux_alloc(); + + old_count = test_cells_written; + + test_target_cmux = ch->cmux; + test_cmux_cells = 1; + + /* Enable cell acceptance */ + test_chan_accept_cells = 1; + + result = channel_flush_some_cells(ch, 1); + + test_eq(result, 1); + test_eq(test_cells_written, old_count + 1); + test_eq(test_cmux_cells, 0); + + test_target_cmux = NULL; + test_cmux_cells = 0; + + done: + tor_free(ch); + + UNMOCK(channel_flush_from_first_active_circuit); + UNMOCK(circuitmux_num_cells); + + test_chan_accept_cells = 0; + + free_cell_pool(); + + return; +} + static void test_channel_incoming(void *arg) { @@ -1291,6 +1408,7 @@ test_channel_write(void *arg) struct testcase_t channel_tests[] = { { "flush", test_channel_flush, TT_FORK, NULL, NULL }, + { "flushmux", test_channel_flushmux, TT_FORK, NULL, NULL }, { "incoming", test_channel_incoming, TT_FORK, NULL, NULL }, { "lifecycle", test_channel_lifecycle, TT_FORK, NULL, NULL }, { "lifecycle_2", test_channel_lifecycle_2, TT_FORK, NULL, NULL }, From 9740a07b8c5b2608f0a937799f9e24ce0f468de7 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 02:33:27 -0800 Subject: [PATCH 50/79] Check queueing case in channel/flushmux unit test too --- src/test/test_channel.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 2f5e9465a7..588f698854 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -481,7 +481,7 @@ static void test_channel_flushmux(void *arg) { channel_t *ch = NULL; - int old_count; + int old_count, q_len_before, q_len_after; ssize_t result; (void)arg; @@ -512,6 +512,28 @@ test_channel_flushmux(void *arg) test_eq(test_cells_written, old_count + 1); test_eq(test_cmux_cells, 0); + /* Now try it without accepting to force them into the queue */ + test_chan_accept_cells = 0; + test_cmux_cells = 1; + q_len_before = chan_cell_queue_len(&(ch->outgoing_queue)); + + result = channel_flush_some_cells(ch, 1); + + /* We should not have actually flushed any */ + test_eq(result, 0); + test_eq(test_cells_written, old_count + 1); + /* But we should have gotten to the fake cellgen loop */ + test_eq(test_cmux_cells, 0); + /* ...and we should have a queued cell */ + q_len_after = chan_cell_queue_len(&(ch->outgoing_queue)); + test_eq(q_len_after, q_len_before + 1); + + /* Now accept cells again and drain the queue */ + test_chan_accept_cells = 1; + channel_flush_cells(ch); + test_eq(test_cells_written, old_count + 2); + test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + test_target_cmux = NULL; test_cmux_cells = 0; From 5a24ff0563ed916abffcde083019d23142197fdf Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 02:50:40 -0800 Subject: [PATCH 51/79] What the hell was I on? --- src/or/channel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/or/channel.c b/src/or/channel.c index 18c236fe66..94cca5caf8 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -2263,7 +2263,8 @@ channel_flush_some_cells(channel_t *chan, ssize_t num_cells) /* Now process the queue if necessary */ - if (q_len_after > q_len_before && num_cells < flushed) { + if ((q_len_after > q_len_before) && + (unlimited || (flushed < num_cells))) { flushed += channel_flush_some_cells_from_outgoing_queue(chan, (unlimited ? -1 : num_cells - flushed)); } From 452bce6c7200600d4810f490ba5cc1c93d49cc96 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 03:51:22 -0800 Subject: [PATCH 52/79] Make channel_dump_statistics() mockable --- src/or/channel.c | 4 ++-- src/or/channel.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index 94cca5caf8..ec44cce427 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -3450,8 +3450,8 @@ chan_cell_queue_len(const chan_cell_queue_t *queue) * Dump statistics for one channel to the log */ -void -channel_dump_statistics(channel_t *chan, int severity) +MOCK_IMPL(void, +channel_dump_statistics, (channel_t *chan, int severity)) { double avg, interval, age; time_t now = time(NULL); diff --git a/src/or/channel.h b/src/or/channel.h index ba6d66b350..07a66ebcda 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -506,7 +506,7 @@ channel_t * channel_next_with_digest(channel_t *chan); */ const char * channel_describe_transport(channel_t *chan); -void channel_dump_statistics(channel_t *chan, int severity); +MOCK_DECL(void, channel_dump_statistics, (channel_t *chan, int severity)); void channel_dump_transport_statistics(channel_t *chan, int severity); const char * channel_get_actual_remote_descr(channel_t *chan); const char * channel_get_actual_remote_address(channel_t *chan); From 5ee25cc185cf7d91c5147faf5dc92b6b305c16ee Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 04:10:15 -0800 Subject: [PATCH 53/79] Add channel/dumpstats unit test --- src/test/test_channel.c | 95 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 588f698854..fc0ef3da4c 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -34,7 +34,11 @@ static double test_overhead_estimate = 1.0f; static int test_releases_count = 0; static circuitmux_t *test_target_cmux = NULL; static unsigned int test_cmux_cells = 0; +static channel_t *dump_statistics_mock_target = NULL; +static int dump_statistics_mock_matches = 0; +static void chan_test_channel_dump_statistics_mock( + channel_t *chan, int severity); static int chan_test_channel_flush_from_first_active_circuit_mock( channel_t *chan, int max); static unsigned int chan_test_circuitmux_num_cells_mock(circuitmux_t *cmux); @@ -55,6 +59,7 @@ static int chan_test_write_packed_cell(channel_t *ch, static int chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell); static void scheduler_channel_doesnt_want_writes_mock(channel_t *ch); +static void test_channel_dumpstats(void *arg); static void test_channel_flush(void *arg); static void test_channel_flushmux(void *arg); static void test_channel_incoming(void *arg); @@ -73,6 +78,26 @@ channel_note_destroy_not_pending_mock(channel_t *ch, ++test_destroy_not_pending_calls; } +/** + * Mock for channel_dump_statistics(); if the channel matches the + * target, bump a counter - otherwise ignore. + */ + +static void +chan_test_channel_dump_statistics_mock(channel_t *chan, int severity) +{ + test_assert(chan != NULL); + + (void)severity; + + if (chan != NULL && chan == dump_statistics_mock_target) { + ++dump_statistics_mock_matches; + } + + done: + return; +} + /** * If the target cmux is the cmux for chan, make fake cells up to the * target number of cells and write them to chan. Otherwise, invoke @@ -414,6 +439,75 @@ scheduler_release_channel_mock(channel_t *ch) return; } +/** + * Test for channel_dumpstats() + */ + +static void +test_channel_dumpstats(void *arg) +{ + channel_t *ch = NULL; + + (void)arg; + + /* Mock these for duration of the test */ + MOCK(scheduler_channel_doesnt_want_writes, + scheduler_channel_doesnt_want_writes_mock); + MOCK(scheduler_release_channel, + scheduler_release_channel_mock); + + /* Set up a new fake channel */ + ch = new_fake_channel(); + test_assert(ch); + ch->cmux = circuitmux_alloc(); + + /* Try to register it */ + channel_register(ch); + test_assert(ch->registered); + + /* Set up mock */ + dump_statistics_mock_target = ch; + dump_statistics_mock_matches = 0; + MOCK(channel_dump_statistics, + chan_test_channel_dump_statistics_mock); + + /* Call channel_dumpstats() */ + channel_dumpstats(LOG_DEBUG); + + /* Assert that we hit the mock */ + test_eq(dump_statistics_mock_matches, 1); + + /* Close the channel */ + channel_mark_for_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSING); + chan_test_finish_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSED); + + /* Try again and hit the finished channel */ + channel_dumpstats(LOG_DEBUG); + test_eq(dump_statistics_mock_matches, 2); + + channel_run_cleanup(); + ch = NULL; + + /* Now we should hit nothing */ + channel_dumpstats(LOG_DEBUG); + test_eq(dump_statistics_mock_matches, 2); + + /* Unmock */ + UNMOCK(channel_dump_statistics); + dump_statistics_mock_target = NULL; + dump_statistics_mock_matches = 0; + + done: + tor_free(ch); + + UNMOCK(scheduler_channel_doesnt_want_writes); + UNMOCK(scheduler_release_channel); + + return; +} + static void test_channel_flush(void *arg) { @@ -1429,6 +1523,7 @@ test_channel_write(void *arg) } struct testcase_t channel_tests[] = { + { "dumpstats", test_channel_dumpstats, TT_FORK, NULL, NULL }, { "flush", test_channel_flush, TT_FORK, NULL, NULL }, { "flushmux", test_channel_flushmux, TT_FORK, NULL, NULL }, { "incoming", test_channel_incoming, TT_FORK, NULL, NULL }, From 462eaed43e73af7869bd4ed96561e9bed88a9de3 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 16:23:54 -0800 Subject: [PATCH 54/79] Limited unit test for channel_dump_statistics() --- src/test/test_channel.c | 112 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 1 deletion(-) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index fc0ef3da4c..9ad271d1f2 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -29,6 +29,7 @@ static int test_chan_var_cells_recved = 0; static int test_cells_written = 0; static int test_destroy_not_pending_calls = 0; static int test_doesnt_want_writes_count = 0; +static int test_dumpstats_calls = 0; static int test_has_waiting_cells_count = 0; static double test_overhead_estimate = 1.0f; static int test_releases_count = 0; @@ -46,11 +47,15 @@ static void channel_note_destroy_not_pending_mock(channel_t *ch, circid_t circid); static void chan_test_cell_handler(channel_t *ch, cell_t *cell); +static const char * chan_test_describe_transport(channel_t *ch); +static void chan_test_dumpstats(channel_t *ch, int severity); static void chan_test_var_cell_handler(channel_t *ch, var_cell_t *var_cell); static void chan_test_close(channel_t *ch); static void chan_test_error(channel_t *ch); static void chan_test_finish_close(channel_t *ch); +static const char * chan_test_get_remote_descr(channel_t *ch, int flags); +static int chan_test_is_canonical(channel_t *ch, int req); static size_t chan_test_num_bytes_queued(channel_t *ch); static int chan_test_num_cells_writeable(channel_t *ch); static int chan_test_write_cell(channel_t *ch, cell_t *cell); @@ -78,6 +83,15 @@ channel_note_destroy_not_pending_mock(channel_t *ch, ++test_destroy_not_pending_calls; } +static const char * +chan_test_describe_transport(channel_t *ch) +{ + test_assert(ch != NULL); + + done: + return "Fake channel for unit tests"; +} + /** * Mock for channel_dump_statistics(); if the channel matches the * target, bump a counter - otherwise ignore. @@ -172,6 +186,23 @@ chan_test_cell_handler(channel_t *ch, return; } +/* + * Fake transport-specific stats call + */ + +static void +chan_test_dumpstats(channel_t *ch, int severity) +{ + test_assert(ch != NULL); + + (void)severity; + + ++test_dumpstats_calls; + + done: + return; +} + /* * Handle an incoming variable-size cell for unit tests */ @@ -233,6 +264,16 @@ chan_test_finish_close(channel_t *ch) return; } +static const char * +chan_test_get_remote_descr(channel_t *ch, int flags) +{ + test_assert(ch); + test_eq(flags & ~(GRD_FLAG_ORIGINAL | GRD_FLAG_ADDR_ONLY), 0); + + done: + return "Fake channel for unit tests; no real endpoint"; +} + static double chan_test_get_overhead_estimate(channel_t *ch) { @@ -242,6 +283,17 @@ chan_test_get_overhead_estimate(channel_t *ch) return test_overhead_estimate; } +static int +chan_test_is_canonical(channel_t *ch, int req) +{ + test_assert(ch != NULL); + test_assert(req == 0 || req == 1); + + done: + /* Fake channels are always canonical */ + return 1; +} + static size_t chan_test_num_bytes_queued(channel_t *ch) { @@ -440,13 +492,16 @@ scheduler_release_channel_mock(channel_t *ch) } /** - * Test for channel_dumpstats() + * Test for channel_dumpstats() and limited test for + * channel_dump_statistics() */ static void test_channel_dumpstats(void *arg) { channel_t *ch = NULL; + cell_t *cell = NULL; + int old_count; (void)arg; @@ -499,7 +554,62 @@ test_channel_dumpstats(void *arg) dump_statistics_mock_target = NULL; dump_statistics_mock_matches = 0; + /* Now make another channel */ + ch = new_fake_channel(); + test_assert(ch); + ch->cmux = circuitmux_alloc(); + channel_register(ch); + test_assert(ch->registered); + /* Lie about its age so dumpstats gets coverage for rate calculations */ + ch->timestamp_created = time(NULL) - 30; + test_assert(ch->timestamp_created > 0); + test_assert(time(NULL) > ch->timestamp_created); + + /* Put cells through it both ways to make the counters non-zero */ + cell = tor_malloc_zero(sizeof(*cell)); + make_fake_cell(cell); + test_chan_accept_cells = 1; + old_count = test_cells_written; + channel_write_cell(ch, cell); + cell = NULL; + test_eq(test_cells_written, old_count + 1); + test_assert(ch->n_bytes_xmitted > 0); + test_assert(ch->n_cells_xmitted > 0); + + /* Receive path */ + channel_set_cell_handlers(ch, + chan_test_cell_handler, + chan_test_var_cell_handler); + test_eq(channel_get_cell_handler(ch), chan_test_cell_handler); + test_eq(channel_get_var_cell_handler(ch), chan_test_var_cell_handler); + cell = tor_malloc_zero(sizeof(cell_t)); + make_fake_cell(cell); + old_count = test_chan_fixed_cells_recved; + channel_queue_cell(ch, cell); + cell = NULL; + test_eq(test_chan_fixed_cells_recved, old_count + 1); + test_assert(ch->n_bytes_recved > 0); + test_assert(ch->n_cells_recved > 0); + + /* Test channel_dump_statistics */ + ch->describe_transport = chan_test_describe_transport; + ch->dumpstats = chan_test_dumpstats; + ch->get_remote_descr = chan_test_get_remote_descr; + ch->is_canonical = chan_test_is_canonical; + old_count = test_dumpstats_calls; + channel_dump_statistics(ch, LOG_DEBUG); + test_eq(test_dumpstats_calls, old_count + 1); + + /* Close the channel */ + channel_mark_for_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSING); + chan_test_finish_close(ch); + test_eq(ch->state, CHANNEL_STATE_CLOSED); + channel_run_cleanup(); + ch = NULL; + done: + tor_free(cell); tor_free(ch); UNMOCK(scheduler_channel_doesnt_want_writes); From 50d5fb87bd6e763895eb0a784e4dbb40a132b759 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Wed, 22 Jan 2014 16:51:44 -0800 Subject: [PATCH 55/79] Initial test_channeltls.c --- src/test/Makefile.nmake | 6 +++--- src/test/include.am | 1 + src/test/test.c | 2 ++ src/test/test_channeltls.c | 18 ++++++++++++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/test/test_channeltls.c diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index 2a347bd677..7986eb07ef 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -11,9 +11,9 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ ws2_32.lib advapi32.lib shell32.lib \ crypt32.lib gdi32.lib user32.lib -TEST_OBJECTS = test.obj test_addr.obj test_channel.obj test_containers.obj \ - test_controller_events.ogj test_crypto.obj test_data.obj test_dir.obj \ - test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ +TEST_OBJECTS = test.obj test_addr.obj test_channel.obj test_channeltls.obj \ + test_containers.obj test_controller_events.obj test_crypto.obj test_data.obj \ + test_dir.obj test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ test_cell_formats.obj test_relay.obj test_replay.obj \ test_introduce.obj tinytest.obj test_hs.obj diff --git a/src/test/include.am b/src/test/include.am index 531b81ef52..fdd88db0ea 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -21,6 +21,7 @@ src_test_test_SOURCES = \ src/test/test_buffers.c \ src/test/test_cell_formats.c \ src/test/test_channel.c \ + src/test/test_channeltls.c \ src/test/test_circuitlist.c \ src/test/test_circuitmux.c \ src/test/test_containers.c \ diff --git a/src/test/test.c b/src/test/test.c index 7ade83d907..d0f00416ab 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1310,6 +1310,7 @@ extern struct testcase_t status_tests[]; extern struct testcase_t routerset_tests[]; extern struct testcase_t router_tests[]; extern struct testcase_t channel_tests[]; +extern struct testcase_t channeltls_tests[]; extern struct testcase_t relay_tests[]; static struct testgroup_t testgroups[] = { @@ -1344,6 +1345,7 @@ static struct testgroup_t testgroups[] = { { "status/" , status_tests }, { "routerset/" , routerset_tests }, { "channel/", channel_tests }, + { "channeltls/", channeltls_tests }, { "relay/" , relay_tests }, END_OF_GROUPS }; diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c new file mode 100644 index 0000000000..6284cc0a05 --- /dev/null +++ b/src/test/test_channeltls.c @@ -0,0 +1,18 @@ +/* Copyright (c) 2013, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define TOR_CHANNEL_INTERNAL_ +#include "or.h" +#include "channel.h" +#include "channeltls.h" +/* For init/free stuff */ +#include "scheduler.h" + +/* Test suite stuff */ +#include "test.h" +#include "fakechans.h" + +struct testcase_t channeltls_tests[] = { + END_OF_TESTCASES +}; + From 3bc7108d2cfb6795f1beb0f5dbb3ab66fe1601d3 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 04:52:59 -0800 Subject: [PATCH 56/79] Make is_local_addr() mockable --- src/or/config.c | 4 ++-- src/or/config.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index 4ae9fadfeb..86d7c6e502 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -2259,8 +2259,8 @@ resolve_my_address(int warn_severity, const or_options_t *options, /** Return true iff addr is judged to be on the same network as us, or * on a private network. */ -int -is_local_addr(const tor_addr_t *addr) +MOCK_IMPL(int, +is_local_addr, (const tor_addr_t *addr)) { if (tor_addr_is_internal(addr, 0)) return 1; diff --git a/src/or/config.h b/src/or/config.h index 8a1919c2ed..bf176a6860 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -32,7 +32,7 @@ uint32_t get_last_resolved_addr(void); int resolve_my_address(int warn_severity, const or_options_t *options, uint32_t *addr_out, const char **method_out, char **hostname_out); -int is_local_addr(const tor_addr_t *addr); +MOCK_DECL(int, is_local_addr, (const tor_addr_t *addr)); void options_init(or_options_t *options); #define OPTIONS_DUMP_MINIMAL 1 From 3b080230e9fda4befda17739d202b9bc59006add Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 04:54:44 -0800 Subject: [PATCH 57/79] Make connection_or_connect() mockable --- src/or/connection_or.c | 8 ++++---- src/or/connection_or.h | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 445335b43a..52f417d9dd 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -1174,10 +1174,10 @@ connection_or_notify_error(or_connection_t *conn, * * Return the launched conn, or NULL if it failed. */ -or_connection_t * -connection_or_connect(const tor_addr_t *_addr, uint16_t port, - const char *id_digest, - channel_tls_t *chan) + +MOCK_IMPL(or_connection_t *, +connection_or_connect, (const tor_addr_t *_addr, uint16_t port, + const char *id_digest, channel_tls_t *chan)) { or_connection_t *conn; const or_options_t *options = get_options(); diff --git a/src/or/connection_or.h b/src/or/connection_or.h index 7b46d7d111..c922fb5d52 100644 --- a/src/or/connection_or.h +++ b/src/or/connection_or.h @@ -37,9 +37,10 @@ void connection_or_connect_failed(or_connection_t *conn, int reason, const char *msg); void connection_or_notify_error(or_connection_t *conn, int reason, const char *msg); -or_connection_t *connection_or_connect(const tor_addr_t *addr, uint16_t port, - const char *id_digest, - channel_tls_t *chan); +MOCK_DECL(or_connection_t *, + connection_or_connect, + (const tor_addr_t *addr, uint16_t port, + const char *id_digest, channel_tls_t *chan)); void connection_or_close_normally(or_connection_t *orconn, int flush); void connection_or_close_for_error(or_connection_t *orconn, int flush); From 8719f8ff09346f2aaa596ec77ff326923379ccde Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 05:59:46 -0800 Subject: [PATCH 58/79] Implement tlschan create and overhead estimate unit tests --- src/test/test_channeltls.c | 212 ++++++++++++++++++++++++++++++++++++- 1 file changed, 211 insertions(+), 1 deletion(-) diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c index 6284cc0a05..9be738f8c6 100644 --- a/src/test/test_channeltls.c +++ b/src/test/test_channeltls.c @@ -1,18 +1,228 @@ -/* Copyright (c) 2013, The Tor Project, Inc. */ +/* Copyright (c) 2014, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#include + #define TOR_CHANNEL_INTERNAL_ #include "or.h" +#include "address.h" #include "channel.h" #include "channeltls.h" +#include "connection_or.h" +#include "config.h" /* For init/free stuff */ #include "scheduler.h" +#include "tortls.h" /* Test suite stuff */ #include "test.h" #include "fakechans.h" +/* The channeltls unit tests */ +static void test_channeltls_create(void *arg); +static void test_channeltls_overhead_estimate(void *arg); + +/* Mocks used by channeltls unit tests */ +static or_connection_t * tlschan_connection_or_connect_mock( + const tor_addr_t *addr, + uint16_t port, + const char *digest, + channel_tls_t *tlschan); +static int tlschan_is_local_addr_mock(const tor_addr_t *addr); + +/* Fake close method */ +static void tlschan_fake_close_method(channel_t *chan); + +/* Flags controlling behavior of channeltls unit test mocks */ +static int tlschan_local = 0; + +/* Thing to cast to fake tor_tls_t * to appease assert_connection_ok() */ +static int fake_tortls = 0; /* Bleh... */ + +static void +test_channeltls_create(void *arg) +{ + tor_addr_t test_addr; + channel_t *ch = NULL; + const char test_digest[DIGEST_LEN] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14 }; + + (void)arg; + + /* Set up a fake address to fake-connect to */ + test_addr.family = AF_INET; + test_addr.addr.in_addr.s_addr = htonl(0x01020304); + + /* For this test we always want the address to be treated as non-local */ + tlschan_local = 0; + /* Install is_local_addr() mock */ + MOCK(is_local_addr, tlschan_is_local_addr_mock); + + /* Install mock for connection_or_connect() */ + MOCK(connection_or_connect, tlschan_connection_or_connect_mock); + + /* Try connecting */ + ch = channel_tls_connect(&test_addr, 567, test_digest); + test_assert(ch != NULL); + + done: + if (ch) { + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + /* + * Use fake close method that doesn't try to do too much to fake + * orconn + */ + ch->close = tlschan_fake_close_method; + channel_mark_for_close(ch); + tor_free(ch); + UNMOCK(scheduler_release_channel); + } + + UNMOCK(connection_or_connect); + UNMOCK(is_local_addr); + + return; +} + +static void +test_channeltls_overhead_estimate(void *arg) +{ + tor_addr_t test_addr; + channel_t *ch = NULL; + const char test_digest[DIGEST_LEN] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14 }; + float r; + channel_tls_t *tlschan = NULL; + + (void)arg; + + /* Set up a fake address to fake-connect to */ + test_addr.family = AF_INET; + test_addr.addr.in_addr.s_addr = htonl(0x01020304); + + /* For this test we always want the address to be treated as non-local */ + tlschan_local = 0; + /* Install is_local_addr() mock */ + MOCK(is_local_addr, tlschan_is_local_addr_mock); + + /* Install mock for connection_or_connect() */ + MOCK(connection_or_connect, tlschan_connection_or_connect_mock); + + /* Try connecting */ + ch = channel_tls_connect(&test_addr, 567, test_digest); + test_assert(ch != NULL); + + /* First case: silly low ratios should get clamped to 1.0f */ + tlschan = BASE_CHAN_TO_TLS(ch); + test_assert(tlschan != NULL); + tlschan->conn->bytes_xmitted = 128; + tlschan->conn->bytes_xmitted_by_tls = 64; + r = ch->get_overhead_estimate(ch); + test_assert(fabsf(r - 1.0f) < 1E-12); + + tlschan->conn->bytes_xmitted_by_tls = 127; + r = ch->get_overhead_estimate(ch); + test_assert(fabsf(r - 1.0f) < 1E-12); + + /* Now middle of the range */ + tlschan->conn->bytes_xmitted_by_tls = 192; + r = ch->get_overhead_estimate(ch); + test_assert(fabsf(r - 1.5f) < 1E-12); + + /* Now above the 2.0f clamp */ + tlschan->conn->bytes_xmitted_by_tls = 257; + r = ch->get_overhead_estimate(ch); + test_assert(fabsf(r - 2.0f) < 1E-12); + + tlschan->conn->bytes_xmitted_by_tls = 512; + r = ch->get_overhead_estimate(ch); + test_assert(fabsf(r - 2.0f) < 1E-12); + + done: + if (ch) { + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + /* + * Use fake close method that doesn't try to do too much to fake + * orconn + */ + ch->close = tlschan_fake_close_method; + channel_mark_for_close(ch); + tor_free(ch); + UNMOCK(scheduler_release_channel); + } + + UNMOCK(connection_or_connect); + UNMOCK(is_local_addr); + + return; +} + +static or_connection_t * +tlschan_connection_or_connect_mock(const tor_addr_t *addr, + uint16_t port, + const char *digest, + channel_tls_t *tlschan) +{ + or_connection_t *result = NULL; + + test_assert(addr != NULL); + test_assert(port != 0); + test_assert(digest != NULL); + test_assert(tlschan != NULL); + + /* Make a fake orconn */ + result = tor_malloc_zero(sizeof(*result)); + result->base_.magic = OR_CONNECTION_MAGIC; + result->base_.state = OR_CONN_STATE_OPEN; + result->base_.type = CONN_TYPE_OR; + result->base_.socket_family = addr->family; + result->base_.address = tor_strdup(""); + memcpy(&(result->base_.addr), addr, sizeof(tor_addr_t)); + result->base_.port = port; + memcpy(result->identity_digest, digest, DIGEST_LEN); + result->chan = tlschan; + memcpy(&(result->real_addr), addr, sizeof(tor_addr_t)); + result->tls = (tor_tls_t *)((void *)(&fake_tortls)); + + done: + return result; +} + +static void +tlschan_fake_close_method(channel_t *chan) +{ + channel_tls_t *tlschan = NULL; + + test_assert(chan != NULL); + test_eq(chan->magic, TLS_CHAN_MAGIC); + + tlschan = BASE_CHAN_TO_TLS(chan); + test_assert(tlschan != NULL); + + /* Just free the fake orconn */ + tor_free(tlschan->conn); + + channel_closed(chan); + + done: + return; +} + +static int +tlschan_is_local_addr_mock(const tor_addr_t *addr) +{ + test_assert(addr != NULL); + + done: + return tlschan_local; +} + struct testcase_t channeltls_tests[] = { + { "create", test_channeltls_create, TT_FORK, NULL, NULL }, + { "overhead_estimate", test_channeltls_overhead_estimate, + TT_FORK, NULL, NULL }, END_OF_TESTCASES }; From a2de0a1034b3d4c1433c548526a1401e51540bc1 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 06:42:14 -0800 Subject: [PATCH 59/79] Make buf_datalen() mockable --- src/or/buffers.c | 4 ++-- src/or/buffers.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/buffers.c b/src/or/buffers.c index 033f86288e..85eff36b4c 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -559,8 +559,8 @@ buf_clear(buf_t *buf) } /** Return the number of bytes stored in buf */ -size_t -buf_datalen(const buf_t *buf) +MOCK_IMPL(size_t, +buf_datalen, (const buf_t *buf)) { return buf->datalen; } diff --git a/src/or/buffers.h b/src/or/buffers.h index c90e14750e..6b91a2d6c1 100644 --- a/src/or/buffers.h +++ b/src/or/buffers.h @@ -24,7 +24,7 @@ void buf_shrink(buf_t *buf); size_t buf_shrink_freelists(int free_all); void buf_dump_freelist_sizes(int severity); -size_t buf_datalen(const buf_t *buf); +MOCK_DECL(size_t, buf_datalen, (const buf_t *buf)); size_t buf_allocation(const buf_t *buf); size_t buf_slack(const buf_t *buf); From 030b0fe1076bd8747e3fecf32740c0b3fd3759eb Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 06:56:18 -0800 Subject: [PATCH 60/79] Add channeltls/num_bytes_queued unit test --- src/test/test_channeltls.c | 95 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c index 9be738f8c6..b70d0fd2fa 100644 --- a/src/test/test_channeltls.c +++ b/src/test/test_channeltls.c @@ -6,6 +6,7 @@ #define TOR_CHANNEL_INTERNAL_ #include "or.h" #include "address.h" +#include "buffers.h" #include "channel.h" #include "channeltls.h" #include "connection_or.h" @@ -20,9 +21,11 @@ /* The channeltls unit tests */ static void test_channeltls_create(void *arg); +static void test_channeltls_num_bytes_queued(void *arg); static void test_channeltls_overhead_estimate(void *arg); /* Mocks used by channeltls unit tests */ +static size_t tlschan_buf_datalen_mock(const buf_t *buf); static or_connection_t * tlschan_connection_or_connect_mock( const tor_addr_t *addr, uint16_t port, @@ -35,6 +38,8 @@ static void tlschan_fake_close_method(channel_t *chan); /* Flags controlling behavior of channeltls unit test mocks */ static int tlschan_local = 0; +static const buf_t * tlschan_buf_datalen_mock_target = NULL; +static size_t tlschan_buf_datalen_mock_size = 0; /* Thing to cast to fake tor_tls_t * to appease assert_connection_ok() */ static int fake_tortls = 0; /* Bleh... */ @@ -85,6 +90,84 @@ test_channeltls_create(void *arg) return; } +static void +test_channeltls_num_bytes_queued(void *arg) +{ + tor_addr_t test_addr; + channel_t *ch = NULL; + const char test_digest[DIGEST_LEN] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, + 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14 }; + channel_tls_t *tlschan = NULL; + size_t len; + int fake_outbuf = 0; + + (void)arg; + + /* Set up a fake address to fake-connect to */ + test_addr.family = AF_INET; + test_addr.addr.in_addr.s_addr = htonl(0x01020304); + + /* For this test we always want the address to be treated as non-local */ + tlschan_local = 0; + /* Install is_local_addr() mock */ + MOCK(is_local_addr, tlschan_is_local_addr_mock); + + /* Install mock for connection_or_connect() */ + MOCK(connection_or_connect, tlschan_connection_or_connect_mock); + + /* Try connecting */ + ch = channel_tls_connect(&test_addr, 567, test_digest); + test_assert(ch != NULL); + + /* + * Next, we have to test ch->num_bytes_queued, which is + * channel_tls_num_bytes_queued_method. We can't mock + * connection_get_outbuf_len() directly because it's static INLINE + * in connection.h, but we can mock buf_datalen(). Note that + * if bufferevents ever work, this will break with them enabled. + */ + + test_assert(ch->num_bytes_queued != NULL); + tlschan = BASE_CHAN_TO_TLS(ch); + test_assert(tlschan != NULL); + if (TO_CONN(tlschan->conn)->outbuf == NULL) { + /* We need an outbuf to make sure buf_datalen() gets called */ + fake_outbuf = 1; + TO_CONN(tlschan->conn)->outbuf = buf_new(); + } + tlschan_buf_datalen_mock_target = TO_CONN(tlschan->conn)->outbuf; + tlschan_buf_datalen_mock_size = 1024; + MOCK(buf_datalen, tlschan_buf_datalen_mock); + len = ch->num_bytes_queued(ch); + test_eq(len, tlschan_buf_datalen_mock_size); + UNMOCK(buf_datalen); + tlschan_buf_datalen_mock_target = NULL; + tlschan_buf_datalen_mock_size = 0; + if (fake_outbuf) { + buf_free(TO_CONN(tlschan->conn)->outbuf); + TO_CONN(tlschan->conn)->outbuf = NULL; + } + + done: + if (ch) { + MOCK(scheduler_release_channel, scheduler_release_channel_mock); + /* + * Use fake close method that doesn't try to do too much to fake + * orconn + */ + ch->close = tlschan_fake_close_method; + channel_mark_for_close(ch); + tor_free(ch); + UNMOCK(scheduler_release_channel); + } + + UNMOCK(connection_or_connect); + UNMOCK(is_local_addr); + + return; +} + static void test_channeltls_overhead_estimate(void *arg) { @@ -159,6 +242,16 @@ test_channeltls_overhead_estimate(void *arg) return; } +static size_t +tlschan_buf_datalen_mock(const buf_t *buf) +{ + if (buf != NULL && buf == tlschan_buf_datalen_mock_target) { + return tlschan_buf_datalen_mock_size; + }else { + return buf_datalen__real(buf); + } +} + static or_connection_t * tlschan_connection_or_connect_mock(const tor_addr_t *addr, uint16_t port, @@ -221,6 +314,8 @@ tlschan_is_local_addr_mock(const tor_addr_t *addr) struct testcase_t channeltls_tests[] = { { "create", test_channeltls_create, TT_FORK, NULL, NULL }, + { "num_bytes_queued", test_channeltls_num_bytes_queued, + TT_FORK, NULL, NULL }, { "overhead_estimate", test_channeltls_overhead_estimate, TT_FORK, NULL, NULL }, END_OF_TESTCASES From 5e9a88e001f66092b8b3fd5fd14877214d038027 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 07:11:13 -0800 Subject: [PATCH 61/79] Add channel_tls_num_cells_writeable_method() coverage to channeltls/num_bytes_queued unit test --- src/test/test_channeltls.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c index b70d0fd2fa..4dcf4c5579 100644 --- a/src/test/test_channeltls.c +++ b/src/test/test_channeltls.c @@ -100,7 +100,7 @@ test_channeltls_num_bytes_queued(void *arg) 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14 }; channel_tls_t *tlschan = NULL; size_t len; - int fake_outbuf = 0; + int fake_outbuf = 0, n; (void)arg; @@ -141,6 +141,15 @@ test_channeltls_num_bytes_queued(void *arg) MOCK(buf_datalen, tlschan_buf_datalen_mock); len = ch->num_bytes_queued(ch); test_eq(len, tlschan_buf_datalen_mock_size); + /* + * We also cover num_cells_writeable here; since wide_circ_ids = 0 on + * the fake tlschans, cell_network_size returns 512, and so with + * tlschan_buf_datalen_mock_size == 1024, we should be able to write + * ceil((OR_CONN_HIGHWATER - 1024) / 512) = ceil(OR_CONN_HIGHWATER / 512) + * - 2 cells. + */ + n = ch->num_cells_writeable(ch); + test_eq(n, CEIL_DIV(OR_CONN_HIGHWATER, 512) - 2); UNMOCK(buf_datalen); tlschan_buf_datalen_mock_target = NULL; tlschan_buf_datalen_mock_size = 0; From 5a07fb96f2a4072bd9ee31a887ac0c790fdaf6c4 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 20:54:50 -0800 Subject: [PATCH 62/79] Make tor_libevent_get_base() mockable --- src/common/compat_libevent.c | 4 ++-- src/common/compat_libevent.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index 74b54bb855..6b2241ad30 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -280,8 +280,8 @@ tor_libevent_initialize(tor_libevent_cfg *torcfg) } /** Return the current Libevent event base that we're set up to use. */ -struct event_base * -tor_libevent_get_base(void) +MOCK_IMPL(struct event_base *, +tor_libevent_get_base, (void)) { return the_event_base; } diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index 9ee7b49cfb..45d007f9b8 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -72,7 +72,7 @@ typedef struct tor_libevent_cfg { } tor_libevent_cfg; void tor_libevent_initialize(tor_libevent_cfg *cfg); -struct event_base *tor_libevent_get_base(void); +MOCK_DECL(struct event_base *, tor_libevent_get_base, (void)); const char *tor_libevent_get_method(void); void tor_check_libevent_version(const char *m, int server, const char **badness_out); From 9869254608587dbd0d33bcba0233967f6bf3c326 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 20:55:13 -0800 Subject: [PATCH 63/79] Make scheduler.c static globals visible to test suite --- src/or/scheduler.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 367480cb31..71da4a4435 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -109,27 +109,27 @@ */ /* Pqueue of channels that can write and have cells (pending work) */ -static smartlist_t *channels_pending = NULL; +STATIC smartlist_t *channels_pending = NULL; /* * This event runs the scheduler from its callback, and is manually * activated whenever a channel enters open for writes/cells to send. */ -static struct event *run_sched_ev = NULL; +STATIC struct event *run_sched_ev = NULL; /* * Queue heuristic; this is not the queue size, but an 'effective queuesize' * that ages out contributions from stalled channels. */ -static uint64_t queue_heuristic = 0; +STATIC uint64_t queue_heuristic = 0; /* * Timestamp for last queue heuristic update */ -static time_t queue_heuristic_timestamp = 0; +STATIC time_t queue_heuristic_timestamp = 0; /* Scheduler static function declarations */ From 0af88f909661b8fd2987beb839c78b804212abc7 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 20:55:34 -0800 Subject: [PATCH 64/79] Initial test_scheduler.c --- src/test/Makefile.nmake | 8 +-- src/test/include.am | 1 + src/test/test.c | 2 + src/test/test_scheduler.c | 141 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 src/test/test_scheduler.c diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index 7986eb07ef..e2cf53895f 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -12,10 +12,10 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ crypt32.lib gdi32.lib user32.lib TEST_OBJECTS = test.obj test_addr.obj test_channel.obj test_channeltls.obj \ - test_containers.obj test_controller_events.obj test_crypto.obj test_data.obj \ - test_dir.obj test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ - test_cell_formats.obj test_relay.obj test_replay.obj \ - test_introduce.obj tinytest.obj test_hs.obj + test_containers.obj test_controller_events.obj test_crypto.obj \ + test_data.obj test_dir.obj test_microdesc.obj test_pt.obj test_util.obj \ + test_config.obj test_cell_formats.obj test_relay.obj test_replay.obj \ + test_scheduler.obj test_introduce.obj test_hs.obj tinytest.obj tinytest.obj: ..\ext\tinytest.c $(CC) $(CFLAGS) /D snprintf=_snprintf /c ..\ext\tinytest.c diff --git a/src/test/include.am b/src/test/include.am index fdd88db0ea..5a8652b031 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -42,6 +42,7 @@ src_test_test_SOURCES = \ src/test/test_relay.c \ src/test/test_replay.c \ src/test/test_routerkeys.c \ + src/test/test_scheduler.c \ src/test/test_socks.c \ src/test/test_util.c \ src/test/test_config.c \ diff --git a/src/test/test.c b/src/test/test.c index d0f00416ab..8d45ef0f59 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1312,6 +1312,7 @@ extern struct testcase_t router_tests[]; extern struct testcase_t channel_tests[]; extern struct testcase_t channeltls_tests[]; extern struct testcase_t relay_tests[]; +extern struct testcase_t scheduler_tests[]; static struct testgroup_t testgroups[] = { { "", test_array }, @@ -1347,6 +1348,7 @@ static struct testgroup_t testgroups[] = { { "channel/", channel_tests }, { "channeltls/", channeltls_tests }, { "relay/" , relay_tests }, + { "scheduler/", scheduler_tests }, END_OF_GROUPS }; diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c new file mode 100644 index 0000000000..e8bceeb2d4 --- /dev/null +++ b/src/test/test_scheduler.c @@ -0,0 +1,141 @@ +/* Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include + +#include "orconfig.h" + +/* Libevent stuff */ +#ifdef HAVE_EVENT2_EVENT_H +#include +#else +#include +#endif + +#define TOR_CHANNEL_INTERNAL_ +#include "or.h" +#include "compat_libevent.h" +#include "scheduler.h" + +/* Test suite stuff */ +#include "test.h" + +/* Statics in scheduler.c exposed to the test suite */ +extern smartlist_t *channels_pending; +extern struct event *run_sched_ev; +extern uint64_t queue_heuristic; +extern time_t queue_heuristic_timestamp; + +/* Event base for scheduelr tests */ +static struct event_base *mock_event_base = NULL; + +/* Setup for mock event stuff */ +static void mock_event_free_all(void); +static void mock_event_init(void); + +/* Mocks used by scheduler tests */ +static struct event_base * tor_libevent_get_base_mock(void); + +/* Scheduler test cases */ +static void test_scheduler_initfree(void *arg); + +/* Mock event init/free */ + +/* Shamelessly stolen from compat_libevent.c */ +#define V(major, minor, patch) \ + (((major) << 24) | ((minor) << 16) | ((patch) << 8)) + +static void +mock_event_free_all(void) +{ + test_assert(mock_event_base != NULL); + + if (mock_event_base) { + event_base_free(mock_event_base); + mock_event_base = NULL; + } + + test_eq(mock_event_base, NULL); + + done: + return; +} + +static void +mock_event_init(void) +{ +#ifdef HAVE_EVENT2_EVENT_H + struct event_config *cfg = NULL; +#endif + + test_eq(mock_event_base, NULL); + + /* + * Really cut down from tor_libevent_initialize of + * src/common/compat_libevent.c to kill config dependencies + */ + + if (!mock_event_base) { +#ifdef HAVE_EVENT2_EVENT_H + cfg = event_config_new(); +#if LIBEVENT_VERSION_NUMBER >= V(2,0,9) + /* We can enable changelist support with epoll, since we don't give + * Libevent any dup'd fds. This lets us avoid some syscalls. */ + event_config_set_flag(cfg, EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST); +#endif + mock_event_base = event_base_new_with_config(cfg); + event_config_free(cfg); +#else + mock_event_base = event_init(); +#endif + } + + test_assert(mock_event_base != NULL); + + done: + return; +} + +/* Mocks */ + +static struct event_base * +tor_libevent_get_base_mock(void) +{ + return mock_event_base; +} + +/* Test cases */ + +static void +test_scheduler_initfree(void *arg) +{ + (void)arg; + + test_eq(channels_pending, NULL); + test_eq(run_sched_ev, NULL); + + mock_event_init(); + MOCK(tor_libevent_get_base, tor_libevent_get_base_mock); + + scheduler_init(); + + test_assert(channels_pending != NULL); + test_assert(run_sched_ev != NULL); + + scheduler_free_all(); + + UNMOCK(tor_libevent_get_base); + mock_event_free_all(); + + test_eq(channels_pending, NULL); + test_eq(run_sched_ev, NULL); + + done: + return; +} + +struct testcase_t scheduler_tests[] = { + { "initfree", test_scheduler_initfree, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + From 71a9ed6feb9e28cce0a2e776e23b440b0139e479 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 21:13:26 -0800 Subject: [PATCH 65/79] Make some scheduler.c static functions visible to the test suite --- src/or/scheduler.c | 7 +++---- src/or/scheduler.h | 7 +++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 71da4a4435..1b4ae04182 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -12,6 +12,7 @@ #include "channel.h" #include "compat_libevent.h" +#define SCHEDULER_PRIVATE_ #include "scheduler.h" #ifdef HAVE_EVENT2_EVENT_H @@ -141,8 +142,6 @@ static void scheduler_retrigger(void); #if 0 static void scheduler_trigger(void); #endif -static uint64_t scheduler_get_queue_heuristic(void); -static void scheduler_update_queue_heuristic(time_t now); /* Scheduler function implementations */ @@ -642,7 +641,7 @@ scheduler_adjust_queue_size(channel_t *chan, char dir, uint64_t adj) * Query the current value of the queue heuristic */ -static uint64_t +STATIC uint64_t scheduler_get_queue_heuristic(void) { time_t now = approx_time(); @@ -656,7 +655,7 @@ scheduler_get_queue_heuristic(void) * Adjust the queue heuristic value to the present time */ -static void +STATIC void scheduler_update_queue_heuristic(time_t now) { time_t diff; diff --git a/src/or/scheduler.h b/src/or/scheduler.h index 7f59ec45d0..3c0d86dabb 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -34,5 +34,12 @@ void scheduler_adjust_queue_size(channel_t *chan, char dir, uint64_t adj); /* Notify scheduler that a channel's queue position may have changed */ void scheduler_touch_channel(channel_t *chan); +/* Things only scheduler.c and its test suite should see */ + +#ifdef SCHEDULER_PRIVATE_ +STATIC uint64_t scheduler_get_queue_heuristic(void); +STATIC void scheduler_update_queue_heuristic(time_t now); +#endif + #endif /* !defined(TOR_SCHEDULER_H) */ From 030608d68d393c90fb789638e5e93ac6af7d4f5e Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Thu, 23 Jan 2014 21:13:44 -0800 Subject: [PATCH 66/79] Add scheduler/queue_heuristic unit test --- src/test/test_scheduler.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index e8bceeb2d4..02426f3723 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -15,6 +15,7 @@ #define TOR_CHANNEL_INTERNAL_ #include "or.h" #include "compat_libevent.h" +#define SCHEDULER_PRIVATE_ #include "scheduler.h" /* Test suite stuff */ @@ -134,8 +135,44 @@ test_scheduler_initfree(void *arg) return; } +static void +test_scheduler_queue_heuristic(void *arg) +{ + time_t now = approx_time(); + uint64_t qh; + + (void)arg; + + queue_heuristic = 0; + queue_heuristic_timestamp = 0; + + /* Not yet inited case */ + scheduler_update_queue_heuristic(now - 180); + test_eq(queue_heuristic, 0); + test_eq(queue_heuristic_timestamp, now - 180); + + queue_heuristic = 1000000000L; + queue_heuristic_timestamp = now - 120; + + scheduler_update_queue_heuristic(now - 119); + test_eq(queue_heuristic, 500000000L); + test_eq(queue_heuristic_timestamp, now - 119); + + scheduler_update_queue_heuristic(now - 116); + test_eq(queue_heuristic, 62500000L); + test_eq(queue_heuristic_timestamp, now - 116); + + qh = scheduler_get_queue_heuristic(); + test_eq(qh, 0); + + done: + return; +} + struct testcase_t scheduler_tests[] = { { "initfree", test_scheduler_initfree, TT_FORK, NULL, NULL }, + { "queue_heuristic", test_scheduler_queue_heuristic, + TT_FORK, NULL, NULL }, END_OF_TESTCASES }; From c5f73e52e54a60581374ff6355104aea3e0f6233 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 24 Jan 2014 03:10:55 -0800 Subject: [PATCH 67/79] Make circuitmux_compare_muxes() and circuitmux_get_policy() mockable --- src/or/circuitmux.c | 8 ++++---- src/or/circuitmux.h | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index 71bc4efc4a..7175b6bf13 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -621,8 +621,8 @@ circuitmux_clear_policy(circuitmux_t *cmux) * Return the policy currently installed on a circuitmux_t */ -const circuitmux_policy_t * -circuitmux_get_policy(circuitmux_t *cmux) +MOCK_IMPL(const circuitmux_policy_t *, +circuitmux_get_policy, (circuitmux_t *cmux)) { tor_assert(cmux); @@ -1961,8 +1961,8 @@ circuitmux_count_queued_destroy_cells(const channel_t *chan, * support the cmp_cmux method, return 0. */ -int -circuitmux_compare_muxes(circuitmux_t *cmux_1, circuitmux_t *cmux_2) +MOCK_IMPL(int, +circuitmux_compare_muxes, (circuitmux_t *cmux_1, circuitmux_t *cmux_2)) { const circuitmux_policy_t *policy; diff --git a/src/or/circuitmux.h b/src/or/circuitmux.h index 9a5ea7d675..00707d97e2 100644 --- a/src/or/circuitmux.h +++ b/src/or/circuitmux.h @@ -108,7 +108,8 @@ void circuitmux_free(circuitmux_t *cmux); /* Policy control */ void circuitmux_clear_policy(circuitmux_t *cmux); -const circuitmux_policy_t * circuitmux_get_policy(circuitmux_t *cmux); +MOCK_DECL(const circuitmux_policy_t *, + circuitmux_get_policy, (circuitmux_t *cmux)); void circuitmux_set_policy(circuitmux_t *cmux, const circuitmux_policy_t *pol); @@ -152,7 +153,8 @@ void circuitmux_mark_destroyed_circids_usable(circuitmux_t *cmux, channel_t *chan); /* Optional interchannel comparisons for scheduling */ -int circuitmux_compare_muxes(circuitmux_t *cmux_1, circuitmux_t *cmux_2); +MOCK_DECL(int, circuitmux_compare_muxes, + (circuitmux_t *cmux_1, circuitmux_t *cmux_2)); #endif /* TOR_CIRCUITMUX_H */ From b7125961de3e7d2687cc52d63d000ad6b6320d63 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 24 Jan 2014 03:12:28 -0800 Subject: [PATCH 68/79] Expose scheduler_compare_channels() to test suite --- src/or/scheduler.c | 3 +-- src/or/scheduler.h | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 1b4ae04182..544ec83258 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -134,7 +134,6 @@ STATIC time_t queue_heuristic_timestamp = 0; /* Scheduler static function declarations */ -static int scheduler_compare_channels(const void *c1_v, const void *c2_v); static void scheduler_evt_callback(evutil_socket_t fd, short events, void *arg); static int scheduler_more_work(void); @@ -168,7 +167,7 @@ scheduler_free_all(void) * Comparison function to use when sorting pending channels */ -static int +STATIC int scheduler_compare_channels(const void *c1_v, const void *c2_v) { channel_t *c1 = NULL, *c2 = NULL; diff --git a/src/or/scheduler.h b/src/or/scheduler.h index 3c0d86dabb..b99491b221 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -37,6 +37,7 @@ void scheduler_touch_channel(channel_t *chan); /* Things only scheduler.c and its test suite should see */ #ifdef SCHEDULER_PRIVATE_ +STATIC int scheduler_compare_channels(const void *c1_v, const void *c2_v); STATIC uint64_t scheduler_get_queue_heuristic(void); STATIC void scheduler_update_queue_heuristic(time_t now); #endif From 314c2f18ae53c73e790b815567d4c266a4b67382 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 24 Jan 2014 04:33:59 -0800 Subject: [PATCH 69/79] Add scheduler/compare_channels unit test --- src/test/test_scheduler.c | 135 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index 02426f3723..e40df16f2d 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -13,13 +13,16 @@ #endif #define TOR_CHANNEL_INTERNAL_ +#define CHANNEL_PRIVATE_ #include "or.h" #include "compat_libevent.h" +#include "channel.h" #define SCHEDULER_PRIVATE_ #include "scheduler.h" /* Test suite stuff */ #include "test.h" +#include "fakechans.h" /* Statics in scheduler.c exposed to the test suite */ extern smartlist_t *channels_pending; @@ -30,15 +33,30 @@ extern time_t queue_heuristic_timestamp; /* Event base for scheduelr tests */ static struct event_base *mock_event_base = NULL; +/* Statics controlling mocks */ +static circuitmux_t *mock_ccm_tgt_1 = NULL; +static circuitmux_t *mock_ccm_tgt_2 = NULL; + +static circuitmux_t *mock_cgp_tgt_1 = NULL; +static const circuitmux_policy_t *mock_cgp_val_1 = NULL; +static circuitmux_t *mock_cgp_tgt_2 = NULL; +static const circuitmux_policy_t *mock_cgp_val_2 = NULL; + /* Setup for mock event stuff */ static void mock_event_free_all(void); static void mock_event_init(void); /* Mocks used by scheduler tests */ +static int circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, + circuitmux_t *cmux_2); +static const circuitmux_policy_t * circuitmux_get_policy_mock( + circuitmux_t *cmux); static struct event_base * tor_libevent_get_base_mock(void); /* Scheduler test cases */ +static void test_scheduler_compare_channels(void *arg); static void test_scheduler_initfree(void *arg); +static void test_scheduler_queue_heuristic(void *arg); /* Mock event init/free */ @@ -99,6 +117,51 @@ mock_event_init(void) /* Mocks */ +static int +circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, + circuitmux_t *cmux_2) +{ + int result = 0; + + test_assert(cmux_1 != NULL); + test_assert(cmux_2 != NULL); + + if (cmux_1 != cmux_2) { + if (cmux_1 == mock_ccm_tgt_1 && cmux_2 == mock_ccm_tgt_2) result = -1; + else if (cmux_1 == mock_ccm_tgt_2 && cmux_2 == mock_ccm_tgt_1) { + result = 1; + } + else { + if (cmux_1 == mock_ccm_tgt_1 || cmux_1 == mock_ccm_tgt_1) result = -1; + else if (cmux_2 == mock_ccm_tgt_1 || cmux_2 == mock_ccm_tgt_2) { + result = 1; + } else { + result = circuitmux_compare_muxes__real(cmux_1, cmux_2); + } + } + } + /* else result = 0 always */ + + done: + return result; +} + +static const circuitmux_policy_t * +circuitmux_get_policy_mock(circuitmux_t *cmux) +{ + const circuitmux_policy_t *result = NULL; + + test_assert(cmux != NULL); + if (cmux) { + if (cmux == mock_cgp_tgt_1) result = mock_cgp_val_1; + else if (cmux == mock_cgp_tgt_2) result = mock_cgp_val_2; + else result = circuitmux_get_policy__real(cmux); + } + + done: + return result; +} + static struct event_base * tor_libevent_get_base_mock(void) { @@ -107,6 +170,76 @@ tor_libevent_get_base_mock(void) /* Test cases */ +static void +test_scheduler_compare_channels(void *arg) +{ + /* We don't actually need whole fake channels... */ + channel_t c1, c2; + /* ...and some dummy circuitmuxes too */ + circuitmux_t *cm1 = NULL, *cm2 = NULL; + int result; + + (void)arg; + + /* We can't actually see sizeof(circuitmux_t) from here */ + cm1 = tor_malloc_zero(sizeof(void *)); + cm2 = tor_malloc_zero(sizeof(void *)); + + c1.cmux = cm1; + c2.cmux = cm2; + + /* Configure circuitmux_get_policy() mock */ + mock_cgp_tgt_1 = cm1; + /* + * This is to test the different-policies case, which uses the policy + * cast to an intptr_t as an arbitrary but definite thing to compare. + */ + mock_cgp_val_1 = (const circuitmux_policy_t *)(1); + mock_cgp_tgt_2 = cm2; + mock_cgp_val_2 = (const circuitmux_policy_t *)(2); + + MOCK(circuitmux_get_policy, circuitmux_get_policy_mock); + + /* Now set up circuitmux_compare_muxes() mock using cm1/cm2 */ + mock_ccm_tgt_1 = cm1; + mock_ccm_tgt_2 = cm2; + MOCK(circuitmux_compare_muxes, circuitmux_compare_muxes_mock); + + /* Equal-channel case */ + result = scheduler_compare_channels(&c1, &c1); + test_eq(result, 0); + + /* Distinct channels, distinct policies */ + result = scheduler_compare_channels(&c1, &c2); + test_eq(result, -1); + result = scheduler_compare_channels(&c2, &c1); + test_eq(result, 1); + + /* Distinct channels, same policy */ + mock_cgp_val_2 = mock_cgp_val_1; + result = scheduler_compare_channels(&c1, &c2); + test_eq(result, -1); + result = scheduler_compare_channels(&c2, &c1); + test_eq(result, 1); + + done: + + UNMOCK(circuitmux_compare_muxes); + mock_ccm_tgt_1 = NULL; + mock_ccm_tgt_2 = NULL; + + UNMOCK(circuitmux_get_policy); + mock_cgp_tgt_1 = NULL; + mock_cgp_val_1 = NULL; + mock_cgp_tgt_2 = NULL; + mock_cgp_val_2 = NULL; + + tor_free(cm1); + tor_free(cm2); + + return; +} + static void test_scheduler_initfree(void *arg) { @@ -170,6 +303,8 @@ test_scheduler_queue_heuristic(void *arg) } struct testcase_t scheduler_tests[] = { + { "compare_channels", test_scheduler_compare_channels, + TT_FORK, NULL, NULL }, { "initfree", test_scheduler_initfree, TT_FORK, NULL, NULL }, { "queue_heuristic", test_scheduler_queue_heuristic, TT_FORK, NULL, NULL }, From f8ceb0f028eb7661cb31b2f82d5a82460e96ece9 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Fri, 24 Jan 2014 07:03:14 -0800 Subject: [PATCH 70/79] Make scheduler_run() mockable --- src/or/scheduler.c | 4 ++-- src/or/scheduler.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index 544ec83258..bf86810517 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -376,8 +376,8 @@ scheduler_release_channel,(channel_t *chan)) /** Run the scheduling algorithm if necessary */ -void -scheduler_run(void) +MOCK_IMPL(void, +scheduler_run, (void)) { int n_cells, n_chans_before, n_chans_after; uint64_t q_len_before, q_heur_before, q_len_after, q_heur_after; diff --git a/src/or/scheduler.h b/src/or/scheduler.h index b99491b221..b0b66ee957 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -18,7 +18,7 @@ /* Set up and shut down the scheduler from main.c */ void scheduler_free_all(void); void scheduler_init(void); -void scheduler_run(void); +MOCK_DECL(void, scheduler_run, (void)); /* Mark channels as having cells or wanting/not wanting writes */ MOCK_DECL(void,scheduler_channel_doesnt_want_writes,(channel_t *chan)); From dc3af04ba8c65e1217f834f04be2a055e8084ec8 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 28 Jan 2014 17:25:37 -0800 Subject: [PATCH 71/79] Make scheduler_compare_channels() mockable --- src/or/scheduler.c | 4 ++-- src/or/scheduler.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/or/scheduler.c b/src/or/scheduler.c index bf86810517..c161393b5a 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -167,8 +167,8 @@ scheduler_free_all(void) * Comparison function to use when sorting pending channels */ -STATIC int -scheduler_compare_channels(const void *c1_v, const void *c2_v) +MOCK_IMPL(STATIC int, +scheduler_compare_channels, (const void *c1_v, const void *c2_v)) { channel_t *c1 = NULL, *c2 = NULL; /* These are a workaround for -Wbad-function-cast throwing a fit */ diff --git a/src/or/scheduler.h b/src/or/scheduler.h index b0b66ee957..8854d5a508 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -37,7 +37,8 @@ void scheduler_touch_channel(channel_t *chan); /* Things only scheduler.c and its test suite should see */ #ifdef SCHEDULER_PRIVATE_ -STATIC int scheduler_compare_channels(const void *c1_v, const void *c2_v); +MOCK_DECL(STATIC int, scheduler_compare_channels, + (const void *c1_v, const void *c2_v)); STATIC uint64_t scheduler_get_queue_heuristic(void); STATIC void scheduler_update_queue_heuristic(time_t now); #endif From 684bcd886abb36414a1499ced1b038050f6face1 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 28 Jan 2014 17:34:16 -0800 Subject: [PATCH 72/79] Add scheduler channel states unit test --- src/test/test_scheduler.c | 153 +++++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 3 deletions(-) diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index e40df16f2d..650d4f1015 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -41,6 +41,8 @@ static circuitmux_t *mock_cgp_tgt_1 = NULL; static const circuitmux_policy_t *mock_cgp_val_1 = NULL; static circuitmux_t *mock_cgp_tgt_2 = NULL; static const circuitmux_policy_t *mock_cgp_val_2 = NULL; +static int scheduler_compare_channels_mock_ctr = 0; +static int scheduler_run_mock_ctr = 0; /* Setup for mock event stuff */ static void mock_event_free_all(void); @@ -51,9 +53,13 @@ static int circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, circuitmux_t *cmux_2); static const circuitmux_policy_t * circuitmux_get_policy_mock( circuitmux_t *cmux); +static int scheduler_compare_channels_mock(const void *c1_v, + const void *c2_v); +static void scheduler_run_noop_mock(void); static struct event_base * tor_libevent_get_base_mock(void); /* Scheduler test cases */ +static void test_scheduler_channel_states(void *arg); static void test_scheduler_compare_channels(void *arg); static void test_scheduler_initfree(void *arg); static void test_scheduler_queue_heuristic(void *arg); @@ -130,8 +136,7 @@ circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, if (cmux_1 == mock_ccm_tgt_1 && cmux_2 == mock_ccm_tgt_2) result = -1; else if (cmux_1 == mock_ccm_tgt_2 && cmux_2 == mock_ccm_tgt_1) { result = 1; - } - else { + } else { if (cmux_1 == mock_ccm_tgt_1 || cmux_1 == mock_ccm_tgt_1) result = -1; else if (cmux_2 == mock_ccm_tgt_1 || cmux_2 == mock_ccm_tgt_2) { result = 1; @@ -162,6 +167,28 @@ circuitmux_get_policy_mock(circuitmux_t *cmux) return result; } +static int +scheduler_compare_channels_mock(const void *c1_v, + const void *c2_v) +{ + uintptr_t p1, p2; + + p1 = (uintptr_t)(c1_v); + p2 = (uintptr_t)(c2_v); + + ++scheduler_compare_channels_mock_ctr; + + if (p1 == p2) return 0; + else if (p1 < p2) return 1; + else return -1; +} + +static void +scheduler_run_noop_mock(void) +{ + ++scheduler_run_mock_ctr; +} + static struct event_base * tor_libevent_get_base_mock(void) { @@ -170,6 +197,125 @@ tor_libevent_get_base_mock(void) /* Test cases */ +static void +test_scheduler_channel_states(void *arg) +{ + channel_t *ch1 = NULL, *ch2 = NULL; + int old_count; + + (void)arg; + + /* Set up libevent and scheduler */ + + mock_event_init(); + MOCK(tor_libevent_get_base, tor_libevent_get_base_mock); + scheduler_init(); + /* + * Install the compare channels mock so we can test + * scheduler_touch_channel(). + */ + MOCK(scheduler_compare_channels, scheduler_compare_channels_mock); + /* + * Disable scheduler_run so we can just check the state transitions + * without having to make everything it might call work too. + */ + MOCK(scheduler_run, scheduler_run_noop_mock); + + test_eq(smartlist_len(channels_pending), 0); + + /* Set up a fake channel */ + ch1 = new_fake_channel(); + test_assert(ch1); + + /* Start it off in OPENING */ + ch1->state = CHANNEL_STATE_OPENING; + /* We'll need a cmux */ + ch1->cmux = circuitmux_alloc(); + /* Try to register it */ + channel_register(ch1); + test_assert(ch1->registered); + + /* It should start off in SCHED_CHAN_IDLE */ + test_eq(ch1->scheduler_state, SCHED_CHAN_IDLE); + + /* Now get another one */ + ch2 = new_fake_channel(); + test_assert(ch2); + ch2->state = CHANNEL_STATE_OPENING; + ch2->cmux = circuitmux_alloc(); + channel_register(ch2); + test_assert(ch2->registered); + + /* Send it to SCHED_CHAN_WAITING_TO_WRITE */ + scheduler_channel_has_waiting_cells(ch1); + test_eq(ch1->scheduler_state, SCHED_CHAN_WAITING_TO_WRITE); + + /* This should send it to SCHED_CHAN_PENDING */ + scheduler_channel_wants_writes(ch1); + test_eq(ch1->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 1); + + /* Now send ch2 to SCHED_CHAN_WAITING_FOR_CELLS */ + scheduler_channel_wants_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + + /* Drop ch2 back to idle */ + scheduler_channel_doesnt_want_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_IDLE); + + /* ...and back to SCHED_CHAN_WAITING_FOR_CELLS */ + scheduler_channel_wants_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + + /* ...and this should kick ch2 into SCHED_CHAN_PENDING */ + scheduler_channel_has_waiting_cells(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 2); + + /* This should send ch2 to SCHED_CHAN_WAITING_TO_WRITE */ + scheduler_channel_doesnt_want_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_TO_WRITE); + test_eq(smartlist_len(channels_pending), 1); + + /* ...and back to SCHED_CHAN_PENDING */ + scheduler_channel_wants_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 2); + + /* Now we exercise scheduler_touch_channel */ + old_count = scheduler_compare_channels_mock_ctr; + scheduler_touch_channel(ch1); + test_assert(scheduler_compare_channels_mock_ctr > old_count); + + /* Close */ + channel_mark_for_close(ch1); + test_eq(ch1->state, CHANNEL_STATE_CLOSING); + channel_mark_for_close(ch2); + test_eq(ch2->state, CHANNEL_STATE_CLOSING); + channel_closed(ch1); + test_eq(ch1->state, CHANNEL_STATE_CLOSED); + ch1 = NULL; + channel_closed(ch2); + test_eq(ch2->state, CHANNEL_STATE_CLOSED); + ch2 = NULL; + + /* Shut things down */ + + channel_free_all(); + scheduler_free_all(); + mock_event_free_all(); + + done: + tor_free(ch1); + tor_free(ch2); + + UNMOCK(scheduler_compare_channels); + UNMOCK(scheduler_run); + UNMOCK(tor_libevent_get_base); + + return; +} + static void test_scheduler_compare_channels(void *arg) { @@ -208,7 +354,7 @@ test_scheduler_compare_channels(void *arg) /* Equal-channel case */ result = scheduler_compare_channels(&c1, &c1); test_eq(result, 0); - + /* Distinct channels, distinct policies */ result = scheduler_compare_channels(&c1, &c2); test_eq(result, -1); @@ -303,6 +449,7 @@ test_scheduler_queue_heuristic(void *arg) } struct testcase_t scheduler_tests[] = { + { "channel_states", test_scheduler_channel_states, TT_FORK, NULL, NULL }, { "compare_channels", test_scheduler_compare_channels, TT_FORK, NULL, NULL }, { "initfree", test_scheduler_initfree, TT_FORK, NULL, NULL }, From 99d312c2937c0289eec047a70f626e3976a25895 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Mon, 3 Feb 2014 12:52:28 -0800 Subject: [PATCH 73/79] Make channel_flush_some_cells() mockable --- src/or/channel.c | 4 ++-- src/or/channel.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/or/channel.c b/src/or/channel.c index ec44cce427..edeee6937a 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -2197,8 +2197,8 @@ channel_listener_change_state(channel_listener_t *chan_l, #define MAX_CELLS_TO_GET_FROM_CIRCUITS_FOR_UNLIMITED 256 -ssize_t -channel_flush_some_cells(channel_t *chan, ssize_t num_cells) +MOCK_IMPL(ssize_t, +channel_flush_some_cells, (channel_t *chan, ssize_t num_cells)) { unsigned int unlimited = 0; ssize_t flushed = 0; diff --git a/src/or/channel.h b/src/or/channel.h index 07a66ebcda..dd0ca369cd 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -455,7 +455,8 @@ void channel_queue_var_cell(channel_t *chan, var_cell_t *var_cell); void channel_flush_cells(channel_t *chan); /* Request from lower layer for more cells if available */ -ssize_t channel_flush_some_cells(channel_t *chan, ssize_t num_cells); +MOCK_DECL(ssize_t, channel_flush_some_cells, + (channel_t *chan, ssize_t num_cells)); /* Query if data available on this channel */ int channel_more_to_flush(channel_t *chan); From 41cf9f6260d70de8b07740c89fd5273b4f073c95 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 4 Feb 2014 13:59:53 -0800 Subject: [PATCH 74/79] Add scheduler/loop unit test --- src/test/test_scheduler.c | 303 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index 650d4f1015..79fc453bd3 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -44,11 +44,17 @@ static const circuitmux_policy_t *mock_cgp_val_2 = NULL; static int scheduler_compare_channels_mock_ctr = 0; static int scheduler_run_mock_ctr = 0; +static void channel_flush_some_cells_mock_free_all(void); +static void channel_flush_some_cells_mock_set(channel_t *chan, + ssize_t num_cells); + /* Setup for mock event stuff */ static void mock_event_free_all(void); static void mock_event_init(void); /* Mocks used by scheduler tests */ +static ssize_t channel_flush_some_cells_mock(channel_t *chan, + ssize_t num_cells); static int circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, circuitmux_t *cmux_2); static const circuitmux_policy_t * circuitmux_get_policy_mock( @@ -62,6 +68,7 @@ static struct event_base * tor_libevent_get_base_mock(void); static void test_scheduler_channel_states(void *arg); static void test_scheduler_compare_channels(void *arg); static void test_scheduler_initfree(void *arg); +static void test_scheduler_loop(void *arg); static void test_scheduler_queue_heuristic(void *arg); /* Mock event init/free */ @@ -123,6 +130,120 @@ mock_event_init(void) /* Mocks */ +typedef struct { + const channel_t *chan; + ssize_t cells; +} flush_mock_channel_t; + +static smartlist_t *chans_for_flush_mock = NULL; + +static void +channel_flush_some_cells_mock_free_all(void) +{ + if (chans_for_flush_mock) { + SMARTLIST_FOREACH_BEGIN(chans_for_flush_mock, + flush_mock_channel_t *, + flush_mock_ch) { + SMARTLIST_DEL_CURRENT(chans_for_flush_mock, flush_mock_ch); + tor_free(flush_mock_ch); + } SMARTLIST_FOREACH_END(flush_mock_ch); + + smartlist_free(chans_for_flush_mock); + chans_for_flush_mock = NULL; + } +} + +static void +channel_flush_some_cells_mock_set(channel_t *chan, ssize_t num_cells) +{ + flush_mock_channel_t *flush_mock_ch = NULL; + + if (!chan) return; + if (num_cells <= 0) return; + + if (!chans_for_flush_mock) { + chans_for_flush_mock = smartlist_new(); + } + + SMARTLIST_FOREACH_BEGIN(chans_for_flush_mock, + flush_mock_channel_t *, + flush_mock_ch) { + if (flush_mock_ch != NULL && flush_mock_ch->chan != NULL) { + if (flush_mock_ch->chan == chan) { + /* Found it */ + flush_mock_ch->cells = num_cells; + break; + } + } else { + /* That shouldn't be there... */ + SMARTLIST_DEL_CURRENT(chans_for_flush_mock, flush_mock_ch); + tor_free(flush_mock_ch); + } + } SMARTLIST_FOREACH_END(flush_mock_ch); + + if (!flush_mock_ch) { + /* The loop didn't find it */ + flush_mock_ch = tor_malloc_zero(sizeof(*flush_mock_ch)); + flush_mock_ch->chan = chan; + flush_mock_ch->cells = num_cells; + smartlist_add(chans_for_flush_mock, flush_mock_ch); + } +} + +static ssize_t +channel_flush_some_cells_mock(channel_t *chan, ssize_t num_cells) +{ + ssize_t flushed = 0, max; + char unlimited = 0; + flush_mock_channel_t *found = NULL; + + test_assert(chan != NULL); + if (chan) { + if (num_cells < 0) { + num_cells = 0; + unlimited = 1; + } + + /* Check if we have it */ + if (chans_for_flush_mock != NULL) { + SMARTLIST_FOREACH_BEGIN(chans_for_flush_mock, + flush_mock_channel_t *, + flush_mock_ch) { + if (flush_mock_ch != NULL && flush_mock_ch->chan != NULL) { + if (flush_mock_ch->chan == chan) { + /* Found it */ + found = flush_mock_ch; + break; + } + } else { + /* That shouldn't be there... */ + SMARTLIST_DEL_CURRENT(chans_for_flush_mock, flush_mock_ch); + tor_free(flush_mock_ch); + } + } SMARTLIST_FOREACH_END(flush_mock_ch); + + if (found) { + /* We found one */ + if (found->cells < 0) found->cells = 0; + + if (unlimited) max = found->cells; + else max = MIN(found->cells, num_cells); + + flushed += max; + found->cells -= max; + + if (found->cells <= 0) { + smartlist_remove(chans_for_flush_mock, found); + tor_free(found); + } + } + } + } + + done: + return flushed; +} + static int circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, circuitmux_t *cmux_2) @@ -414,6 +535,187 @@ test_scheduler_initfree(void *arg) return; } +static void +test_scheduler_loop(void *arg) +{ + channel_t *ch1 = NULL, *ch2 = NULL; + + (void)arg; + + /* Set up libevent and scheduler */ + + mock_event_init(); + MOCK(tor_libevent_get_base, tor_libevent_get_base_mock); + scheduler_init(); + /* + * Install the compare channels mock so we can test + * scheduler_touch_channel(). + */ + MOCK(scheduler_compare_channels, scheduler_compare_channels_mock); + /* + * Disable scheduler_run so we can just check the state transitions + * without having to make everything it might call work too. + */ + MOCK(scheduler_run, scheduler_run_noop_mock); + + test_eq(smartlist_len(channels_pending), 0); + + /* Set up a fake channel */ + ch1 = new_fake_channel(); + test_assert(ch1); + + /* Start it off in OPENING */ + ch1->state = CHANNEL_STATE_OPENING; + /* We'll need a cmux */ + ch1->cmux = circuitmux_alloc(); + /* Try to register it */ + channel_register(ch1); + test_assert(ch1->registered); + /* Finish opening it */ + channel_change_state(ch1, CHANNEL_STATE_OPEN); + + /* It should start off in SCHED_CHAN_IDLE */ + test_eq(ch1->scheduler_state, SCHED_CHAN_IDLE); + + /* Now get another one */ + ch2 = new_fake_channel(); + test_assert(ch2); + ch2->state = CHANNEL_STATE_OPENING; + ch2->cmux = circuitmux_alloc(); + channel_register(ch2); + test_assert(ch2->registered); + /* + * Don't open ch2; then channel_num_cells_writeable() will return + * zero and we'll get coverage of that exception case in scheduler_run() + */ + + test_eq(ch1->state, CHANNEL_STATE_OPEN); + test_eq(ch2->state, CHANNEL_STATE_OPENING); + + /* Send it to SCHED_CHAN_WAITING_TO_WRITE */ + scheduler_channel_has_waiting_cells(ch1); + test_eq(ch1->scheduler_state, SCHED_CHAN_WAITING_TO_WRITE); + + /* This should send it to SCHED_CHAN_PENDING */ + scheduler_channel_wants_writes(ch1); + test_eq(ch1->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 1); + + /* Now send ch2 to SCHED_CHAN_WAITING_FOR_CELLS */ + scheduler_channel_wants_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + + /* Drop ch2 back to idle */ + scheduler_channel_doesnt_want_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_IDLE); + + /* ...and back to SCHED_CHAN_WAITING_FOR_CELLS */ + scheduler_channel_wants_writes(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + + /* ...and this should kick ch2 into SCHED_CHAN_PENDING */ + scheduler_channel_has_waiting_cells(ch2); + test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 2); + + /* + * Now we've got two pending channels and need to fire off + * scheduler_run(); first, unmock it. + */ + + UNMOCK(scheduler_run); + + scheduler_run(); + + /* Now re-mock it */ + MOCK(scheduler_run, scheduler_run_noop_mock); + + /* + * Assert that they're still in the states we left and aren't still + * pending + */ + test_eq(ch1->state, CHANNEL_STATE_OPEN); + test_eq(ch2->state, CHANNEL_STATE_OPENING); + test_assert(ch1->scheduler_state != SCHED_CHAN_PENDING); + test_assert(ch2->scheduler_state != SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 0); + + /* Now, finish opening ch2, and get both back to pending */ + channel_change_state(ch2, CHANNEL_STATE_OPEN); + scheduler_channel_wants_writes(ch1); + scheduler_channel_wants_writes(ch2); + scheduler_channel_has_waiting_cells(ch1); + scheduler_channel_has_waiting_cells(ch2); + test_eq(ch1->state, CHANNEL_STATE_OPEN); + test_eq(ch2->state, CHANNEL_STATE_OPEN); + test_eq(ch1->scheduler_state, SCHED_CHAN_PENDING); + test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); + test_eq(smartlist_len(channels_pending), 2); + + /* Now, set up the channel_flush_some_cells() mock */ + MOCK(channel_flush_some_cells, channel_flush_some_cells_mock); + /* + * 16 cells on ch1 means it'll completely drain into the 32 cells + * fakechan's num_cells_writeable() returns. + */ + channel_flush_some_cells_mock_set(ch1, 16); + /* + * This one should get sent back to pending, since num_cells_writeable() + * will still return non-zero. + */ + channel_flush_some_cells_mock_set(ch2, 48); + + /* + * And re-run the scheduler_run() loop with non-zero returns from + * channel_flush_some_cells() this time. + */ + UNMOCK(scheduler_run); + + scheduler_run(); + + /* Now re-mock it */ + MOCK(scheduler_run, scheduler_run_noop_mock); + + /* + * ch1 should have gone to SCHED_CHAN_WAITING_FOR_CELLS, with 16 flushed + * and 32 writeable. + */ + test_eq(ch1->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + /* + * ...ch2 should also have gone to SCHED_CHAN_WAITING_FOR_CELLS, with + * channel_more_to_flush() returning false and channel_num_cells_writeable() + * > 0/ + */ + test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + + /* Close */ + channel_mark_for_close(ch1); + test_eq(ch1->state, CHANNEL_STATE_CLOSING); + channel_mark_for_close(ch2); + test_eq(ch2->state, CHANNEL_STATE_CLOSING); + channel_closed(ch1); + test_eq(ch1->state, CHANNEL_STATE_CLOSED); + ch1 = NULL; + channel_closed(ch2); + test_eq(ch2->state, CHANNEL_STATE_CLOSED); + ch2 = NULL; + + /* Shut things down */ + channel_flush_some_cells_mock_free_all(); + channel_free_all(); + scheduler_free_all(); + mock_event_free_all(); + + done: + tor_free(ch1); + tor_free(ch2); + + UNMOCK(channel_flush_some_cells); + UNMOCK(scheduler_compare_channels); + UNMOCK(scheduler_run); + UNMOCK(tor_libevent_get_base); +} + static void test_scheduler_queue_heuristic(void *arg) { @@ -453,6 +755,7 @@ struct testcase_t scheduler_tests[] = { { "compare_channels", test_scheduler_compare_channels, TT_FORK, NULL, NULL }, { "initfree", test_scheduler_initfree, TT_FORK, NULL, NULL }, + { "loop", test_scheduler_loop, TT_FORK, NULL, NULL }, { "queue_heuristic", test_scheduler_queue_heuristic, TT_FORK, NULL, NULL }, END_OF_TESTCASES From 2d171c108191799b63737768bbad104ced0ac4cd Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 23 Sep 2014 07:11:24 -0700 Subject: [PATCH 75/79] Update test_channel.c for recent test suite changes and --enable-mempools support --- src/test/test_channel.c | 427 +++++++++++++++++++++------------------- 1 file changed, 224 insertions(+), 203 deletions(-) diff --git a/src/test/test_channel.c b/src/test/test_channel.c index 9ad271d1f2..49590f52c0 100644 --- a/src/test/test_channel.c +++ b/src/test/test_channel.c @@ -86,7 +86,7 @@ channel_note_destroy_not_pending_mock(channel_t *ch, static const char * chan_test_describe_transport(channel_t *ch) { - test_assert(ch != NULL); + tt_assert(ch != NULL); done: return "Fake channel for unit tests"; @@ -100,7 +100,7 @@ chan_test_describe_transport(channel_t *ch) static void chan_test_channel_dump_statistics_mock(channel_t *chan, int severity) { - test_assert(chan != NULL); + tt_assert(chan != NULL); (void)severity; @@ -125,7 +125,7 @@ chan_test_channel_flush_from_first_active_circuit_mock(channel_t *chan, int result = 0, c = 0; packed_cell_t *cell = NULL; - test_assert(chan != NULL); + tt_assert(chan != NULL); if (test_target_cmux != NULL && test_target_cmux == chan->cmux) { while (c <= max && test_cmux_cells > 0) { @@ -154,7 +154,7 @@ chan_test_circuitmux_num_cells_mock(circuitmux_t *cmux) { unsigned int result = 0; - test_assert(cmux != NULL); + tt_assert(cmux != NULL); if (cmux != NULL) { if (cmux == test_target_cmux) { result = test_cmux_cells; @@ -176,8 +176,8 @@ static void chan_test_cell_handler(channel_t *ch, cell_t *cell) { - test_assert(ch); - test_assert(cell); + tt_assert(ch); + tt_assert(cell); tor_free(cell); ++test_chan_fixed_cells_recved; @@ -193,7 +193,7 @@ chan_test_cell_handler(channel_t *ch, static void chan_test_dumpstats(channel_t *ch, int severity) { - test_assert(ch != NULL); + tt_assert(ch != NULL); (void)severity; @@ -211,8 +211,8 @@ static void chan_test_var_cell_handler(channel_t *ch, var_cell_t *var_cell) { - test_assert(ch); - test_assert(var_cell); + tt_assert(ch); + tt_assert(var_cell); tor_free(var_cell); ++test_chan_var_cells_recved; @@ -224,7 +224,7 @@ chan_test_var_cell_handler(channel_t *ch, static void chan_test_close(channel_t *ch) { - test_assert(ch); + tt_assert(ch); done: return; @@ -237,8 +237,8 @@ chan_test_close(channel_t *ch) static void chan_test_error(channel_t *ch) { - test_assert(ch); - test_assert(!(ch->state == CHANNEL_STATE_CLOSING || + tt_assert(ch); + tt_assert(!(ch->state == CHANNEL_STATE_CLOSING || ch->state == CHANNEL_STATE_ERROR || ch->state == CHANNEL_STATE_CLOSED)); @@ -255,8 +255,8 @@ chan_test_error(channel_t *ch) static void chan_test_finish_close(channel_t *ch) { - test_assert(ch); - test_assert(ch->state == CHANNEL_STATE_CLOSING); + tt_assert(ch); + tt_assert(ch->state == CHANNEL_STATE_CLOSING); channel_closed(ch); @@ -267,8 +267,8 @@ chan_test_finish_close(channel_t *ch) static const char * chan_test_get_remote_descr(channel_t *ch, int flags) { - test_assert(ch); - test_eq(flags & ~(GRD_FLAG_ORIGINAL | GRD_FLAG_ADDR_ONLY), 0); + tt_assert(ch); + tt_int_op(flags & ~(GRD_FLAG_ORIGINAL | GRD_FLAG_ADDR_ONLY), ==, 0); done: return "Fake channel for unit tests; no real endpoint"; @@ -277,7 +277,7 @@ chan_test_get_remote_descr(channel_t *ch, int flags) static double chan_test_get_overhead_estimate(channel_t *ch) { - test_assert(ch); + tt_assert(ch); done: return test_overhead_estimate; @@ -286,8 +286,8 @@ chan_test_get_overhead_estimate(channel_t *ch) static int chan_test_is_canonical(channel_t *ch, int req) { - test_assert(ch != NULL); - test_assert(req == 0 || req == 1); + tt_assert(ch != NULL); + tt_assert(req == 0 || req == 1); done: /* Fake channels are always canonical */ @@ -297,7 +297,7 @@ chan_test_is_canonical(channel_t *ch, int req) static size_t chan_test_num_bytes_queued(channel_t *ch) { - test_assert(ch); + tt_assert(ch); done: return 0; @@ -306,7 +306,7 @@ chan_test_num_bytes_queued(channel_t *ch) static int chan_test_num_cells_writeable(channel_t *ch) { - test_assert(ch); + tt_assert(ch); done: return 32; @@ -317,8 +317,8 @@ chan_test_write_cell(channel_t *ch, cell_t *cell) { int rv = 0; - test_assert(ch); - test_assert(cell); + tt_assert(ch); + tt_assert(cell); if (test_chan_accept_cells) { /* Free the cell and bump the counter */ @@ -338,8 +338,8 @@ chan_test_write_packed_cell(channel_t *ch, { int rv = 0; - test_assert(ch); - test_assert(packed_cell); + tt_assert(ch); + tt_assert(packed_cell); if (test_chan_accept_cells) { /* Free the cell and bump the counter */ @@ -358,8 +358,8 @@ chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell) { int rv = 0; - test_assert(ch); - test_assert(var_cell); + tt_assert(ch); + tt_assert(var_cell); if (test_chan_accept_cells) { /* Free the cell and bump the counter */ @@ -380,7 +380,7 @@ chan_test_write_var_cell(channel_t *ch, var_cell_t *var_cell) void make_fake_cell(cell_t *c) { - test_assert(c != NULL); + tt_assert(c != NULL); c->circ_id = 1; c->command = CELL_RELAY; @@ -397,7 +397,7 @@ make_fake_cell(cell_t *c) void make_fake_var_cell(var_cell_t *c) { - test_assert(c != NULL); + tt_assert(c != NULL); c->circ_id = 1; c->command = CELL_VERSIONS; @@ -513,12 +513,12 @@ test_channel_dumpstats(void *arg) /* Set up a new fake channel */ ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); ch->cmux = circuitmux_alloc(); /* Try to register it */ channel_register(ch); - test_assert(ch->registered); + tt_assert(ch->registered); /* Set up mock */ dump_statistics_mock_target = ch; @@ -530,24 +530,24 @@ test_channel_dumpstats(void *arg) channel_dumpstats(LOG_DEBUG); /* Assert that we hit the mock */ - test_eq(dump_statistics_mock_matches, 1); + tt_int_op(dump_statistics_mock_matches, ==, 1); /* Close the channel */ channel_mark_for_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); /* Try again and hit the finished channel */ channel_dumpstats(LOG_DEBUG); - test_eq(dump_statistics_mock_matches, 2); + tt_int_op(dump_statistics_mock_matches, ==, 2); channel_run_cleanup(); ch = NULL; /* Now we should hit nothing */ channel_dumpstats(LOG_DEBUG); - test_eq(dump_statistics_mock_matches, 2); + tt_int_op(dump_statistics_mock_matches, ==, 2); /* Unmock */ UNMOCK(channel_dump_statistics); @@ -556,14 +556,14 @@ test_channel_dumpstats(void *arg) /* Now make another channel */ ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); ch->cmux = circuitmux_alloc(); channel_register(ch); - test_assert(ch->registered); + tt_assert(ch->registered); /* Lie about its age so dumpstats gets coverage for rate calculations */ ch->timestamp_created = time(NULL) - 30; - test_assert(ch->timestamp_created > 0); - test_assert(time(NULL) > ch->timestamp_created); + tt_assert(ch->timestamp_created > 0); + tt_assert(time(NULL) > ch->timestamp_created); /* Put cells through it both ways to make the counters non-zero */ cell = tor_malloc_zero(sizeof(*cell)); @@ -572,24 +572,24 @@ test_channel_dumpstats(void *arg) old_count = test_cells_written; channel_write_cell(ch, cell); cell = NULL; - test_eq(test_cells_written, old_count + 1); - test_assert(ch->n_bytes_xmitted > 0); - test_assert(ch->n_cells_xmitted > 0); + tt_int_op(test_cells_written, ==, old_count + 1); + tt_assert(ch->n_bytes_xmitted > 0); + tt_assert(ch->n_cells_xmitted > 0); /* Receive path */ channel_set_cell_handlers(ch, chan_test_cell_handler, chan_test_var_cell_handler); - test_eq(channel_get_cell_handler(ch), chan_test_cell_handler); - test_eq(channel_get_var_cell_handler(ch), chan_test_var_cell_handler); + tt_ptr_op(channel_get_cell_handler(ch), ==, chan_test_cell_handler); + tt_ptr_op(channel_get_var_cell_handler(ch), ==, chan_test_var_cell_handler); cell = tor_malloc_zero(sizeof(cell_t)); make_fake_cell(cell); old_count = test_chan_fixed_cells_recved; channel_queue_cell(ch, cell); cell = NULL; - test_eq(test_chan_fixed_cells_recved, old_count + 1); - test_assert(ch->n_bytes_recved > 0); - test_assert(ch->n_cells_recved > 0); + tt_int_op(test_chan_fixed_cells_recved, ==, old_count + 1); + tt_assert(ch->n_bytes_recved > 0); + tt_assert(ch->n_cells_recved > 0); /* Test channel_dump_statistics */ ch->describe_transport = chan_test_describe_transport; @@ -598,13 +598,13 @@ test_channel_dumpstats(void *arg) ch->is_canonical = chan_test_is_canonical; old_count = test_dumpstats_calls; channel_dump_statistics(ch, LOG_DEBUG); - test_eq(test_dumpstats_calls, old_count + 1); + tt_int_op(test_dumpstats_calls, ==, old_count + 1); /* Close the channel */ channel_mark_for_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -629,10 +629,12 @@ test_channel_flush(void *arg) (void)arg; +#ifdef ENABLE_MEMPOOLS init_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); /* Cache the original count */ init_count = test_cells_written; @@ -645,21 +647,21 @@ test_channel_flush(void *arg) make_fake_cell(cell); channel_write_cell(ch, cell); /* It should be queued, so assert that we didn't write it */ - test_eq(test_cells_written, init_count); + tt_int_op(test_cells_written, ==, init_count); /* Queue a var cell */ v_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); make_fake_var_cell(v_cell); channel_write_var_cell(ch, v_cell); /* It should be queued, so assert that we didn't write it */ - test_eq(test_cells_written, init_count); + tt_int_op(test_cells_written, ==, init_count); /* Try a packed cell now */ p_cell = packed_cell_new(); - test_assert(p_cell); + tt_assert(p_cell); channel_write_packed_cell(ch, p_cell); /* It should be queued, so assert that we didn't write it */ - test_eq(test_cells_written, init_count); + tt_int_op(test_cells_written, ==, init_count); /* Now allow writes through again */ test_chan_accept_cells = 1; @@ -668,11 +670,13 @@ test_channel_flush(void *arg) channel_flush_cells(ch); /* All three should have gone through */ - test_eq(test_cells_written, init_count + 3); + tt_int_op(test_cells_written, ==, init_count + 3); done: tor_free(ch); +#ifdef ENABLE_MEMPOOLS free_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ return; } @@ -690,7 +694,9 @@ test_channel_flushmux(void *arg) (void)arg; +#ifdef ENABLE_MEMPOOLS init_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ /* Install mocks we need for this test */ MOCK(channel_flush_from_first_active_circuit, @@ -699,7 +705,7 @@ test_channel_flushmux(void *arg) chan_test_circuitmux_num_cells_mock); ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); ch->cmux = circuitmux_alloc(); old_count = test_cells_written; @@ -712,9 +718,9 @@ test_channel_flushmux(void *arg) result = channel_flush_some_cells(ch, 1); - test_eq(result, 1); - test_eq(test_cells_written, old_count + 1); - test_eq(test_cmux_cells, 0); + tt_int_op(result, ==, 1); + tt_int_op(test_cells_written, ==, old_count + 1); + tt_int_op(test_cmux_cells, ==, 0); /* Now try it without accepting to force them into the queue */ test_chan_accept_cells = 0; @@ -724,19 +730,19 @@ test_channel_flushmux(void *arg) result = channel_flush_some_cells(ch, 1); /* We should not have actually flushed any */ - test_eq(result, 0); - test_eq(test_cells_written, old_count + 1); + tt_int_op(result, ==, 0); + tt_int_op(test_cells_written, ==, old_count + 1); /* But we should have gotten to the fake cellgen loop */ - test_eq(test_cmux_cells, 0); + tt_int_op(test_cmux_cells, ==, 0); /* ...and we should have a queued cell */ q_len_after = chan_cell_queue_len(&(ch->outgoing_queue)); - test_eq(q_len_after, q_len_before + 1); + tt_int_op(q_len_after, ==, q_len_before + 1); /* Now accept cells again and drain the queue */ test_chan_accept_cells = 1; channel_flush_cells(ch); - test_eq(test_cells_written, old_count + 2); - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + tt_int_op(test_cells_written, ==, old_count + 2); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); test_target_cmux = NULL; test_cmux_cells = 0; @@ -749,7 +755,9 @@ test_channel_flushmux(void *arg) test_chan_accept_cells = 0; +#ifdef ENABLE_MEMPOOLS free_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ return; } @@ -776,7 +784,7 @@ test_channel_incoming(void *arg) test_overhead_estimate = 1.0f; ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); /* Start it off in OPENING */ ch->state = CHANNEL_STATE_OPENING; /* We'll need a cmux */ @@ -787,16 +795,16 @@ test_channel_incoming(void *arg) chan_test_cell_handler, chan_test_var_cell_handler); /* Test cell handler getters */ - test_eq(channel_get_cell_handler(ch), chan_test_cell_handler); - test_eq(channel_get_var_cell_handler(ch), chan_test_var_cell_handler); + tt_ptr_op(channel_get_cell_handler(ch), ==, chan_test_cell_handler); + tt_ptr_op(channel_get_var_cell_handler(ch), ==, chan_test_var_cell_handler); /* Try to register it */ channel_register(ch); - test_assert(ch->registered); + tt_assert(ch->registered); /* Open it */ channel_change_state(ch, CHANNEL_STATE_OPEN); - test_eq(ch->state, CHANNEL_STATE_OPEN); + tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); /* Receive a fixed cell */ cell = tor_malloc_zero(sizeof(cell_t)); @@ -804,7 +812,7 @@ test_channel_incoming(void *arg) old_count = test_chan_fixed_cells_recved; channel_queue_cell(ch, cell); cell = NULL; - test_eq(test_chan_fixed_cells_recved, old_count + 1); + tt_int_op(test_chan_fixed_cells_recved, ==, old_count + 1); /* Receive a variable-size cell */ var_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); @@ -812,13 +820,13 @@ test_channel_incoming(void *arg) old_count = test_chan_var_cells_recved; channel_queue_var_cell(ch, var_cell); var_cell = NULL; - test_eq(test_chan_var_cells_recved, old_count + 1); + tt_int_op(test_chan_var_cells_recved, ==, old_count + 1); /* Close it */ channel_mark_for_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -865,7 +873,7 @@ test_channel_lifecycle(void *arg) test_overhead_estimate = 1.0f; ch1 = new_fake_channel(); - test_assert(ch1); + tt_assert(ch1); /* Start it off in OPENING */ ch1->state = CHANNEL_STATE_OPENING; /* We'll need a cmux */ @@ -873,62 +881,67 @@ test_channel_lifecycle(void *arg) /* Try to register it */ channel_register(ch1); - test_assert(ch1->registered); + tt_assert(ch1->registered); /* Try to write a cell through (should queue) */ cell = tor_malloc_zero(sizeof(cell_t)); make_fake_cell(cell); old_count = test_cells_written; channel_write_cell(ch1, cell); - test_eq(old_count, test_cells_written); + tt_int_op(old_count, ==, test_cells_written); /* Move it to OPEN and flush */ channel_change_state(ch1, CHANNEL_STATE_OPEN); /* Queue should drain */ - test_eq(old_count + 1, test_cells_written); + tt_int_op(old_count + 1, ==, test_cells_written); /* Get another one */ ch2 = new_fake_channel(); - test_assert(ch2); + tt_assert(ch2); ch2->state = CHANNEL_STATE_OPENING; ch2->cmux = circuitmux_alloc(); /* Register */ channel_register(ch2); - test_assert(ch2->registered); + tt_assert(ch2->registered); /* Check counters */ - test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count); - test_eq(test_releases_count, init_releases_count); + tt_int_op(test_doesnt_want_writes_count, ==, init_doesnt_want_writes_count); + tt_int_op(test_releases_count, ==, init_releases_count); /* Move ch1 to MAINT */ channel_change_state(ch1, CHANNEL_STATE_MAINT); - test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); - test_eq(test_releases_count, init_releases_count); + tt_int_op(test_doesnt_want_writes_count, ==, + init_doesnt_want_writes_count + 1); + tt_int_op(test_releases_count, ==, init_releases_count); /* Move ch2 to OPEN */ channel_change_state(ch2, CHANNEL_STATE_OPEN); - test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); - test_eq(test_releases_count, init_releases_count); + tt_int_op(test_doesnt_want_writes_count, ==, + init_doesnt_want_writes_count + 1); + tt_int_op(test_releases_count, ==, init_releases_count); /* Move ch1 back to OPEN */ channel_change_state(ch1, CHANNEL_STATE_OPEN); - test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); - test_eq(test_releases_count, init_releases_count); + tt_int_op(test_doesnt_want_writes_count, ==, + init_doesnt_want_writes_count + 1); + tt_int_op(test_releases_count, ==, init_releases_count); /* Mark ch2 for close */ channel_mark_for_close(ch2); - test_eq(ch2->state, CHANNEL_STATE_CLOSING); - test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); - test_eq(test_releases_count, init_releases_count + 1); + tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSING); + tt_int_op(test_doesnt_want_writes_count, ==, + init_doesnt_want_writes_count + 1); + tt_int_op(test_releases_count, ==, init_releases_count + 1); /* Shut down channels */ channel_free_all(); ch1 = ch2 = NULL; - test_eq(test_doesnt_want_writes_count, init_doesnt_want_writes_count + 1); + tt_int_op(test_doesnt_want_writes_count, ==, + init_doesnt_want_writes_count + 1); /* channel_free() calls scheduler_release_channel() */ - test_eq(test_releases_count, init_releases_count + 4); + tt_int_op(test_releases_count, ==, init_releases_count + 4); done: tor_free(ch1); @@ -968,7 +981,7 @@ test_channel_lifecycle_2(void *arg) test_overhead_estimate = 1.0f; ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); /* Start it off in OPENING */ ch->state = CHANNEL_STATE_OPENING; /* The full lifecycle test needs a cmux */ @@ -976,60 +989,60 @@ test_channel_lifecycle_2(void *arg) /* Try to register it */ channel_register(ch); - test_assert(ch->registered); + tt_assert(ch->registered); /* Try to close it */ channel_mark_for_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); /* Finish closing it */ chan_test_finish_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; /* Now try OPENING->OPEN->CLOSING->ERROR */ ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); ch->state = CHANNEL_STATE_OPENING; ch->cmux = circuitmux_alloc(); channel_register(ch); - test_assert(ch->registered); + tt_assert(ch->registered); /* Finish opening it */ channel_change_state(ch, CHANNEL_STATE_OPEN); /* Error exit from lower layer */ chan_test_error(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); chan_test_finish_close(ch); - test_eq(ch->state, CHANNEL_STATE_ERROR); + tt_int_op(ch->state, ==, CHANNEL_STATE_ERROR); channel_run_cleanup(); ch = NULL; /* OPENING->OPEN->MAINT->CLOSING->CLOSED close from maintenance state */ ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); ch->state = CHANNEL_STATE_OPENING; ch->cmux = circuitmux_alloc(); channel_register(ch); - test_assert(ch->registered); + tt_assert(ch->registered); /* Finish opening it */ channel_change_state(ch, CHANNEL_STATE_OPEN); - test_eq(ch->state, CHANNEL_STATE_OPEN); + tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); /* Go to maintenance state */ channel_change_state(ch, CHANNEL_STATE_MAINT); - test_eq(ch->state, CHANNEL_STATE_MAINT); + tt_int_op(ch->state, ==, CHANNEL_STATE_MAINT); /* Lower layer close */ channel_mark_for_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); /* Finish */ chan_test_finish_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; @@ -1038,53 +1051,53 @@ test_channel_lifecycle_2(void *arg) * maintenance state */ ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); ch->state = CHANNEL_STATE_OPENING; ch->cmux = circuitmux_alloc(); channel_register(ch); - test_assert(ch->registered); + tt_assert(ch->registered); /* Finish opening it */ channel_change_state(ch, CHANNEL_STATE_OPEN); - test_eq(ch->state, CHANNEL_STATE_OPEN); + tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); /* Go to maintenance state */ channel_change_state(ch, CHANNEL_STATE_MAINT); - test_eq(ch->state, CHANNEL_STATE_MAINT); + tt_int_op(ch->state, ==, CHANNEL_STATE_MAINT); /* Lower layer close */ channel_close_from_lower_layer(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); /* Finish */ chan_test_finish_close(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSED); channel_run_cleanup(); ch = NULL; /* OPENING->OPEN->MAINT->CLOSING->ERROR */ ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); ch->state = CHANNEL_STATE_OPENING; ch->cmux = circuitmux_alloc(); channel_register(ch); - test_assert(ch->registered); + tt_assert(ch->registered); /* Finish opening it */ channel_change_state(ch, CHANNEL_STATE_OPEN); - test_eq(ch->state, CHANNEL_STATE_OPEN); + tt_int_op(ch->state, ==, CHANNEL_STATE_OPEN); /* Go to maintenance state */ channel_change_state(ch, CHANNEL_STATE_MAINT); - test_eq(ch->state, CHANNEL_STATE_MAINT); + tt_int_op(ch->state, ==, CHANNEL_STATE_MAINT); /* Lower layer close */ chan_test_error(ch); - test_eq(ch->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch->state, ==, CHANNEL_STATE_CLOSING); /* Finish */ chan_test_finish_close(ch); - test_eq(ch->state, CHANNEL_STATE_ERROR); + tt_int_op(ch->state, ==, CHANNEL_STATE_ERROR); channel_run_cleanup(); ch = NULL; @@ -1115,17 +1128,17 @@ test_channel_multi(void *arg) test_overhead_estimate = 1.0f; ch1 = new_fake_channel(); - test_assert(ch1); + tt_assert(ch1); ch2 = new_fake_channel(); - test_assert(ch2); + tt_assert(ch2); /* Initial queue size update */ channel_update_xmit_queue_size(ch1); - test_eq(ch1->bytes_queued_for_xmit, 0); + tt_int_op(ch1->bytes_queued_for_xmit, ==, 0); channel_update_xmit_queue_size(ch2); - test_eq(ch2->bytes_queued_for_xmit, 0); + tt_int_op(ch2->bytes_queued_for_xmit, ==, 0); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 0); + tt_int_op(global_queue_estimate, ==, 0); /* Queue some cells, check queue estimates */ cell = tor_malloc_zero(sizeof(cell_t)); @@ -1138,10 +1151,10 @@ test_channel_multi(void *arg) channel_update_xmit_queue_size(ch1); channel_update_xmit_queue_size(ch2); - test_eq(ch1->bytes_queued_for_xmit, 0); - test_eq(ch2->bytes_queued_for_xmit, 0); + tt_int_op(ch1->bytes_queued_for_xmit, ==, 0); + tt_int_op(ch2->bytes_queued_for_xmit, ==, 0); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 0); + tt_int_op(global_queue_estimate, ==, 0); /* Stop accepting cells at lower layer */ test_chan_accept_cells = 0; @@ -1152,18 +1165,18 @@ test_channel_multi(void *arg) channel_write_cell(ch1, cell); channel_update_xmit_queue_size(ch1); - test_eq(ch1->bytes_queued_for_xmit, 512); + tt_int_op(ch1->bytes_queued_for_xmit, ==, 512); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 512); + tt_int_op(global_queue_estimate, ==, 512); cell = tor_malloc_zero(sizeof(cell_t)); make_fake_cell(cell); channel_write_cell(ch2, cell); channel_update_xmit_queue_size(ch2); - test_eq(ch2->bytes_queued_for_xmit, 512); + tt_int_op(ch2->bytes_queued_for_xmit, ==, 512); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 1024); + tt_int_op(global_queue_estimate, ==, 1024); /* Allow cells through again */ test_chan_accept_cells = 1; @@ -1174,10 +1187,10 @@ test_channel_multi(void *arg) /* Update and check queue sizes */ channel_update_xmit_queue_size(ch1); channel_update_xmit_queue_size(ch2); - test_eq(ch1->bytes_queued_for_xmit, 512); - test_eq(ch2->bytes_queued_for_xmit, 0); + tt_int_op(ch1->bytes_queued_for_xmit, ==, 512); + tt_int_op(ch2->bytes_queued_for_xmit, ==, 0); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 512); + tt_int_op(global_queue_estimate, ==, 512); /* Flush chan 1 */ channel_flush_cells(ch1); @@ -1185,10 +1198,10 @@ test_channel_multi(void *arg) /* Update and check queue sizes */ channel_update_xmit_queue_size(ch1); channel_update_xmit_queue_size(ch2); - test_eq(ch1->bytes_queued_for_xmit, 0); - test_eq(ch2->bytes_queued_for_xmit, 0); + tt_int_op(ch1->bytes_queued_for_xmit, ==, 0); + tt_int_op(ch2->bytes_queued_for_xmit, ==, 0); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 0); + tt_int_op(global_queue_estimate, ==, 0); /* Now block again */ test_chan_accept_cells = 0; @@ -1205,10 +1218,10 @@ test_channel_multi(void *arg) /* Check the estimates */ channel_update_xmit_queue_size(ch1); channel_update_xmit_queue_size(ch2); - test_eq(ch1->bytes_queued_for_xmit, 512); - test_eq(ch2->bytes_queued_for_xmit, 512); + tt_int_op(ch1->bytes_queued_for_xmit, ==, 512); + tt_int_op(ch2->bytes_queued_for_xmit, ==, 512); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 1024); + tt_int_op(global_queue_estimate, ==, 1024); /* Now close channel 2; it should be subtracted from the global queue */ MOCK(scheduler_release_channel, scheduler_release_channel_mock); @@ -1216,7 +1229,7 @@ test_channel_multi(void *arg) UNMOCK(scheduler_release_channel); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 512); + tt_int_op(global_queue_estimate, ==, 512); /* * Since the fake channels aren't registered, channel_free_all() can't @@ -1227,7 +1240,7 @@ test_channel_multi(void *arg) UNMOCK(scheduler_release_channel); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 0); + tt_int_op(global_queue_estimate, ==, 0); /* Now free everything */ MOCK(scheduler_release_channel, scheduler_release_channel_mock); @@ -1262,13 +1275,15 @@ test_channel_queue_impossible(void *arg) (void)arg; +#ifdef ENABLE_MEMPOOLS init_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ packed_cell = packed_cell_new(); - test_assert(packed_cell); + tt_assert(packed_cell); ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); /* We test queueing here; tell it not to accept cells */ test_chan_accept_cells = 0; @@ -1279,7 +1294,7 @@ test_channel_queue_impossible(void *arg) old_count = test_cells_written; /* Assert that the queue is initially empty */ - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); /* Get a fresh cell and write it to the channel*/ cell = tor_malloc_zero(sizeof(cell_t)); @@ -1287,12 +1302,12 @@ test_channel_queue_impossible(void *arg) channel_write_cell(ch, cell); /* Now it should be queued */ - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)),1); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 1); q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); - test_assert(q); + tt_assert(q); if (q) { - test_eq(q->type, CELL_QUEUE_FIXED); - test_eq(q->u.fixed.cell, cell); + tt_int_op(q->type, ==, CELL_QUEUE_FIXED); + tt_ptr_op(q->u.fixed.cell, ==, cell); } /* Do perverse things to it */ tor_free(q->u.fixed.cell); @@ -1304,8 +1319,8 @@ test_channel_queue_impossible(void *arg) */ test_chan_accept_cells = 1; channel_change_state(ch, CHANNEL_STATE_OPEN); - test_assert(test_cells_written == old_count); - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + tt_assert(test_cells_written == old_count); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); /* Same thing but for a var_cell */ @@ -1316,12 +1331,12 @@ test_channel_queue_impossible(void *arg) channel_write_var_cell(ch, var_cell); /* Check that it's queued */ - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 1); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 1); q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); - test_assert(q); + tt_assert(q); if (q) { - test_eq(q->type, CELL_QUEUE_VAR); - test_eq(q->u.var.var_cell, var_cell); + tt_int_op(q->type, ==, CELL_QUEUE_VAR); + tt_ptr_op(q->u.var.var_cell, ==, var_cell); } /* Remove the cell from the queue entry */ @@ -1331,24 +1346,24 @@ test_channel_queue_impossible(void *arg) /* Let it drain and check that the bad entry is discarded */ test_chan_accept_cells = 1; channel_change_state(ch, CHANNEL_STATE_OPEN); - test_assert(test_cells_written == old_count); - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + tt_assert(test_cells_written == old_count); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); /* Same thing with a packed_cell */ test_chan_accept_cells = 0; ch->state = CHANNEL_STATE_MAINT; packed_cell = packed_cell_new(); - test_assert(packed_cell); + tt_assert(packed_cell); channel_write_packed_cell(ch, packed_cell); /* Check that it's queued */ - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 1); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 1); q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); - test_assert(q); + tt_assert(q); if (q) { - test_eq(q->type, CELL_QUEUE_PACKED); - test_eq(q->u.packed.packed_cell, packed_cell); + tt_int_op(q->type, ==, CELL_QUEUE_PACKED); + tt_ptr_op(q->u.packed.packed_cell, ==, packed_cell); } /* Remove the cell from the queue entry */ @@ -1358,8 +1373,8 @@ test_channel_queue_impossible(void *arg) /* Let it drain and check that the bad entry is discarded */ test_chan_accept_cells = 1; channel_change_state(ch, CHANNEL_STATE_OPEN); - test_assert(test_cells_written == old_count); - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + tt_assert(test_cells_written == old_count); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); /* Unknown cell type case */ test_chan_accept_cells = 0; @@ -1369,12 +1384,12 @@ test_channel_queue_impossible(void *arg) channel_write_cell(ch, cell); /* Check that it's queued */ - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)),1); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 1); q = TOR_SIMPLEQ_FIRST(&(ch->outgoing_queue)); - test_assert(q); + tt_assert(q); if (q) { - test_eq(q->type, CELL_QUEUE_FIXED); - test_eq(q->u.fixed.cell, cell); + tt_int_op(q->type, ==, CELL_QUEUE_FIXED); + tt_ptr_op(q->u.fixed.cell, ==, cell); } /* Clobber it, including the queue entry type */ tor_free(q->u.fixed.cell); @@ -1384,12 +1399,14 @@ test_channel_queue_impossible(void *arg) /* Let it drain and check that the bad entry is discarded */ test_chan_accept_cells = 1; channel_change_state(ch, CHANNEL_STATE_OPEN); - test_assert(test_cells_written == old_count); - test_eq(chan_cell_queue_len(&(ch->outgoing_queue)), 0); + tt_assert(test_cells_written == old_count); + tt_int_op(chan_cell_queue_len(&(ch->outgoing_queue)), ==, 0); done: tor_free(ch); +#ifdef ENABLE_MEMPOOLS free_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ /* * Doing that meant that we couldn't correctly adjust the queue size @@ -1412,18 +1429,18 @@ test_channel_queue_size(void *arg) (void)arg; ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); /* Initial queue size update */ channel_update_xmit_queue_size(ch); - test_eq(ch->bytes_queued_for_xmit, 0); + tt_int_op(ch->bytes_queued_for_xmit, ==, 0); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 0); + tt_int_op(global_queue_estimate, ==, 0); /* Test the call-through to our fake lower layer */ n = channel_num_cells_writeable(ch); /* chan_test_num_cells_writeable() always returns 32 */ - test_eq(n, 32); + tt_int_op(n, ==, 32); /* * Now we queue some cells and check that channel_num_cells_writeable() @@ -1442,32 +1459,32 @@ test_channel_queue_size(void *arg) old_count = test_cells_written; channel_write_cell(ch, cell); /* Assert that it got queued, not written through, correctly */ - test_eq(test_cells_written, old_count); + tt_int_op(test_cells_written, ==, old_count); /* Now check chan_test_num_cells_writeable() again */ n = channel_num_cells_writeable(ch); - test_eq(n, 0); /* Should return 0 since we're in CHANNEL_STATE_MAINT */ + tt_int_op(n, ==, 0); /* Should return 0 since we're in CHANNEL_STATE_MAINT */ /* Update queue size estimates */ channel_update_xmit_queue_size(ch); /* One cell, times an overhead factor of 1.0 */ - test_eq(ch->bytes_queued_for_xmit, 512); + tt_int_op(ch->bytes_queued_for_xmit, ==, 512); /* Try a different overhead factor */ test_overhead_estimate = 0.5f; /* This one should be ignored since it's below 1.0 */ channel_update_xmit_queue_size(ch); - test_eq(ch->bytes_queued_for_xmit, 512); + tt_int_op(ch->bytes_queued_for_xmit, ==, 512); /* Now try a larger one */ test_overhead_estimate = 2.0f; channel_update_xmit_queue_size(ch); - test_eq(ch->bytes_queued_for_xmit, 1024); + tt_int_op(ch->bytes_queued_for_xmit, ==, 1024); /* Go back to 1.0 */ test_overhead_estimate = 1.0f; channel_update_xmit_queue_size(ch); - test_eq(ch->bytes_queued_for_xmit, 512); + tt_int_op(ch->bytes_queued_for_xmit, ==, 512); /* Check the global estimate too */ global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 512); + tt_int_op(global_queue_estimate, ==, 512); /* Go to open */ old_count = test_cells_written; @@ -1477,37 +1494,37 @@ test_channel_queue_size(void *arg) * It should try to write, but we aren't accepting cells right now, so * it'll requeue */ - test_eq(test_cells_written, old_count); + tt_int_op(test_cells_written, ==, old_count); /* Check the queue size again */ channel_update_xmit_queue_size(ch); - test_eq(ch->bytes_queued_for_xmit, 512); + tt_int_op(ch->bytes_queued_for_xmit, ==, 512); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 512); + tt_int_op(global_queue_estimate, ==, 512); /* * Now the cell is in the queue, and we're open, so we should get 31 * writeable cells. */ n = channel_num_cells_writeable(ch); - test_eq(n, 31); + tt_int_op(n, ==, 31); /* Accept cells again */ test_chan_accept_cells = 1; /* ...and re-process the queue */ old_count = test_cells_written; channel_flush_cells(ch); - test_eq(test_cells_written, old_count + 1); + tt_int_op(test_cells_written, ==, old_count + 1); /* Should have 32 writeable now */ n = channel_num_cells_writeable(ch); - test_eq(n, 32); + tt_int_op(n, ==, 32); /* Should have queue size estimate of zero */ channel_update_xmit_queue_size(ch); - test_eq(ch->bytes_queued_for_xmit, 0); + tt_int_op(ch->bytes_queued_for_xmit, ==, 0); global_queue_estimate = channel_get_global_queue_estimate(); - test_eq(global_queue_estimate, 0); + tt_int_op(global_queue_estimate, ==, 0); /* Okay, now we're done with this one */ MOCK(scheduler_release_channel, scheduler_release_channel_mock); @@ -1532,13 +1549,15 @@ test_channel_write(void *arg) (void)arg; +#ifdef ENABLE_MEMPOOLS init_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ packed_cell = packed_cell_new(); - test_assert(packed_cell); + tt_assert(packed_cell); ch = new_fake_channel(); - test_assert(ch); + tt_assert(ch); make_fake_cell(cell); make_fake_var_cell(var_cell); @@ -1547,13 +1566,13 @@ test_channel_write(void *arg) old_count = test_cells_written; channel_write_cell(ch, cell); - test_assert(test_cells_written == old_count + 1); + tt_assert(test_cells_written == old_count + 1); channel_write_var_cell(ch, var_cell); - test_assert(test_cells_written == old_count + 2); + tt_assert(test_cells_written == old_count + 2); channel_write_packed_cell(ch, packed_cell); - test_assert(test_cells_written == old_count + 3); + tt_assert(test_cells_written == old_count + 3); /* Now we test queueing; tell it not to accept cells */ test_chan_accept_cells = 0; @@ -1566,7 +1585,7 @@ test_channel_write(void *arg) old_count = test_cells_written; channel_write_cell(ch, cell); - test_assert(test_cells_written == old_count); + tt_assert(test_cells_written == old_count); /* * Now change back to open with channel_change_state() and assert that it @@ -1574,7 +1593,7 @@ test_channel_write(void *arg) */ test_chan_accept_cells = 1; channel_change_state(ch, CHANNEL_STATE_OPEN); - test_assert(test_cells_written == old_count + 1); + tt_assert(test_cells_written == old_count + 1); /* * Check the note destroy case @@ -1589,13 +1608,13 @@ test_channel_write(void *arg) old_count = test_destroy_not_pending_calls; channel_write_cell(ch, cell); - test_assert(test_destroy_not_pending_calls == old_count + 1); + tt_assert(test_destroy_not_pending_calls == old_count + 1); /* Now send a non-destroy and check we don't call it */ cell = tor_malloc_zero(sizeof(cell_t)); make_fake_cell(cell); channel_write_cell(ch, cell); - test_assert(test_destroy_not_pending_calls == old_count + 1); + tt_assert(test_destroy_not_pending_calls == old_count + 1); UNMOCK(channel_note_destroy_not_pending); @@ -1613,18 +1632,20 @@ test_channel_write(void *arg) cell = tor_malloc_zero(sizeof(cell_t)); make_fake_cell(cell); channel_write_cell(ch, cell); - test_assert(test_cells_written == old_count); + tt_assert(test_cells_written == old_count); var_cell = tor_malloc_zero(sizeof(var_cell_t) + CELL_PAYLOAD_SIZE); make_fake_var_cell(var_cell); channel_write_var_cell(ch, var_cell); - test_assert(test_cells_written == old_count); + tt_assert(test_cells_written == old_count); packed_cell = packed_cell_new(); channel_write_packed_cell(ch, packed_cell); - test_assert(test_cells_written == old_count); + tt_assert(test_cells_written == old_count); +#ifdef ENABLE_MEMPOOLS free_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ done: tor_free(ch); From faea058baabec9eeb180a0e1812ca7857f3894e0 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 23 Sep 2014 07:18:57 -0700 Subject: [PATCH 76/79] Update test_channeltls.c for recent test suite changes and --enable-mempools support --- src/test/test_channeltls.c | 44 +++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c index 4dcf4c5579..45e24dfbec 100644 --- a/src/test/test_channeltls.c +++ b/src/test/test_channeltls.c @@ -69,7 +69,7 @@ test_channeltls_create(void *arg) /* Try connecting */ ch = channel_tls_connect(&test_addr, 567, test_digest); - test_assert(ch != NULL); + tt_assert(ch != NULL); done: if (ch) { @@ -118,7 +118,7 @@ test_channeltls_num_bytes_queued(void *arg) /* Try connecting */ ch = channel_tls_connect(&test_addr, 567, test_digest); - test_assert(ch != NULL); + tt_assert(ch != NULL); /* * Next, we have to test ch->num_bytes_queued, which is @@ -128,9 +128,9 @@ test_channeltls_num_bytes_queued(void *arg) * if bufferevents ever work, this will break with them enabled. */ - test_assert(ch->num_bytes_queued != NULL); + tt_assert(ch->num_bytes_queued != NULL); tlschan = BASE_CHAN_TO_TLS(ch); - test_assert(tlschan != NULL); + tt_assert(tlschan != NULL); if (TO_CONN(tlschan->conn)->outbuf == NULL) { /* We need an outbuf to make sure buf_datalen() gets called */ fake_outbuf = 1; @@ -140,7 +140,7 @@ test_channeltls_num_bytes_queued(void *arg) tlschan_buf_datalen_mock_size = 1024; MOCK(buf_datalen, tlschan_buf_datalen_mock); len = ch->num_bytes_queued(ch); - test_eq(len, tlschan_buf_datalen_mock_size); + tt_int_op(len, ==, tlschan_buf_datalen_mock_size); /* * We also cover num_cells_writeable here; since wide_circ_ids = 0 on * the fake tlschans, cell_network_size returns 512, and so with @@ -149,7 +149,7 @@ test_channeltls_num_bytes_queued(void *arg) * - 2 cells. */ n = ch->num_cells_writeable(ch); - test_eq(n, CEIL_DIV(OR_CONN_HIGHWATER, 512) - 2); + tt_int_op(n, ==, CEIL_DIV(OR_CONN_HIGHWATER, 512) - 2); UNMOCK(buf_datalen); tlschan_buf_datalen_mock_target = NULL; tlschan_buf_datalen_mock_size = 0; @@ -204,33 +204,33 @@ test_channeltls_overhead_estimate(void *arg) /* Try connecting */ ch = channel_tls_connect(&test_addr, 567, test_digest); - test_assert(ch != NULL); + tt_assert(ch != NULL); /* First case: silly low ratios should get clamped to 1.0f */ tlschan = BASE_CHAN_TO_TLS(ch); - test_assert(tlschan != NULL); + tt_assert(tlschan != NULL); tlschan->conn->bytes_xmitted = 128; tlschan->conn->bytes_xmitted_by_tls = 64; r = ch->get_overhead_estimate(ch); - test_assert(fabsf(r - 1.0f) < 1E-12); + tt_assert(fabsf(r - 1.0f) < 1E-12); tlschan->conn->bytes_xmitted_by_tls = 127; r = ch->get_overhead_estimate(ch); - test_assert(fabsf(r - 1.0f) < 1E-12); + tt_assert(fabsf(r - 1.0f) < 1E-12); /* Now middle of the range */ tlschan->conn->bytes_xmitted_by_tls = 192; r = ch->get_overhead_estimate(ch); - test_assert(fabsf(r - 1.5f) < 1E-12); + tt_assert(fabsf(r - 1.5f) < 1E-12); /* Now above the 2.0f clamp */ tlschan->conn->bytes_xmitted_by_tls = 257; r = ch->get_overhead_estimate(ch); - test_assert(fabsf(r - 2.0f) < 1E-12); + tt_assert(fabsf(r - 2.0f) < 1E-12); tlschan->conn->bytes_xmitted_by_tls = 512; r = ch->get_overhead_estimate(ch); - test_assert(fabsf(r - 2.0f) < 1E-12); + tt_assert(fabsf(r - 2.0f) < 1E-12); done: if (ch) { @@ -256,7 +256,7 @@ tlschan_buf_datalen_mock(const buf_t *buf) { if (buf != NULL && buf == tlschan_buf_datalen_mock_target) { return tlschan_buf_datalen_mock_size; - }else { + } else { return buf_datalen__real(buf); } } @@ -269,10 +269,10 @@ tlschan_connection_or_connect_mock(const tor_addr_t *addr, { or_connection_t *result = NULL; - test_assert(addr != NULL); - test_assert(port != 0); - test_assert(digest != NULL); - test_assert(tlschan != NULL); + tt_assert(addr != NULL); + tt_assert(port != 0); + tt_assert(digest != NULL); + tt_assert(tlschan != NULL); /* Make a fake orconn */ result = tor_malloc_zero(sizeof(*result)); @@ -297,11 +297,11 @@ tlschan_fake_close_method(channel_t *chan) { channel_tls_t *tlschan = NULL; - test_assert(chan != NULL); - test_eq(chan->magic, TLS_CHAN_MAGIC); + tt_assert(chan != NULL); + tt_int_op(chan->magic, ==, TLS_CHAN_MAGIC); tlschan = BASE_CHAN_TO_TLS(chan); - test_assert(tlschan != NULL); + tt_assert(tlschan != NULL); /* Just free the fake orconn */ tor_free(tlschan->conn); @@ -315,7 +315,7 @@ tlschan_fake_close_method(channel_t *chan) static int tlschan_is_local_addr_mock(const tor_addr_t *addr) { - test_assert(addr != NULL); + tt_assert(addr != NULL); done: return tlschan_local; From 4d20c427b4874a3cb97e4567e4ae54830c5a79be Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 23 Sep 2014 07:22:26 -0700 Subject: [PATCH 77/79] Update test_relay.c for recent test suite changes and --enable-mempools support --- src/test/test_relay.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/test/test_relay.c b/src/test/test_relay.c index 6b9cb33ca3..6907597705 100644 --- a/src/test/test_relay.c +++ b/src/test/test_relay.c @@ -61,14 +61,16 @@ test_relay_append_cell_to_circuit_queue(void *arg) (void)arg; /* We'll need the cell pool for append_cell_to_circuit_queue() to work */ +#ifdef ENABLE_MEMPOOLS init_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ /* Make fake channels to be nchan and pchan for the circuit */ nchan = new_fake_channel(); - test_assert(nchan); + tt_assert(nchan); pchan = new_fake_channel(); - test_assert(pchan); + tt_assert(pchan); /* We'll need chans with working cmuxes */ nchan->cmux = circuitmux_alloc(); @@ -76,7 +78,7 @@ test_relay_append_cell_to_circuit_queue(void *arg) /* Make a fake orcirc */ orcirc = new_fake_orcirc(nchan, pchan); - test_assert(orcirc); + tt_assert(orcirc); /* Make a cell */ cell = tor_malloc_zero(sizeof(cell_t)); @@ -90,14 +92,14 @@ test_relay_append_cell_to_circuit_queue(void *arg) append_cell_to_circuit_queue(TO_CIRCUIT(orcirc), nchan, cell, CELL_DIRECTION_OUT, 0); new_count = get_mock_scheduler_has_waiting_cells_count(); - test_eq(new_count, old_count + 1); + tt_int_op(new_count, ==, old_count + 1); /* Now try the reverse direction */ old_count = get_mock_scheduler_has_waiting_cells_count(); append_cell_to_circuit_queue(TO_CIRCUIT(orcirc), pchan, cell, CELL_DIRECTION_IN, 0); new_count = get_mock_scheduler_has_waiting_cells_count(); - test_eq(new_count, old_count + 1); + tt_int_op(new_count, ==, old_count + 1); UNMOCK(scheduler_channel_has_waiting_cells); @@ -117,7 +119,9 @@ test_relay_append_cell_to_circuit_queue(void *arg) tor_free(nchan); if (pchan && pchan->cmux) circuitmux_free(pchan->cmux); tor_free(pchan); +#ifdef ENABLE_MEMPOOLS free_cell_pool(); +#endif /* ENABLE_MEMPOOLS */ return; } From a28cfa128f55c64cfd9562dff7e3b5515d1615ad Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 23 Sep 2014 21:03:09 -0700 Subject: [PATCH 78/79] Update test_relay.c for recent test suite changes and --enable-mempools support --- src/test/test_scheduler.c | 162 +++++++++++++++++++------------------- 1 file changed, 81 insertions(+), 81 deletions(-) diff --git a/src/test/test_scheduler.c b/src/test/test_scheduler.c index 79fc453bd3..da5c4e8fa0 100644 --- a/src/test/test_scheduler.c +++ b/src/test/test_scheduler.c @@ -80,14 +80,14 @@ static void test_scheduler_queue_heuristic(void *arg); static void mock_event_free_all(void) { - test_assert(mock_event_base != NULL); + tt_assert(mock_event_base != NULL); if (mock_event_base) { event_base_free(mock_event_base); mock_event_base = NULL; } - test_eq(mock_event_base, NULL); + tt_ptr_op(mock_event_base, ==, NULL); done: return; @@ -100,7 +100,7 @@ mock_event_init(void) struct event_config *cfg = NULL; #endif - test_eq(mock_event_base, NULL); + tt_ptr_op(mock_event_base, ==, NULL); /* * Really cut down from tor_libevent_initialize of @@ -122,7 +122,7 @@ mock_event_init(void) #endif } - test_assert(mock_event_base != NULL); + tt_assert(mock_event_base != NULL); done: return; @@ -197,7 +197,7 @@ channel_flush_some_cells_mock(channel_t *chan, ssize_t num_cells) char unlimited = 0; flush_mock_channel_t *found = NULL; - test_assert(chan != NULL); + tt_assert(chan != NULL); if (chan) { if (num_cells < 0) { num_cells = 0; @@ -250,8 +250,8 @@ circuitmux_compare_muxes_mock(circuitmux_t *cmux_1, { int result = 0; - test_assert(cmux_1 != NULL); - test_assert(cmux_2 != NULL); + tt_assert(cmux_1 != NULL); + tt_assert(cmux_2 != NULL); if (cmux_1 != cmux_2) { if (cmux_1 == mock_ccm_tgt_1 && cmux_2 == mock_ccm_tgt_2) result = -1; @@ -277,7 +277,7 @@ circuitmux_get_policy_mock(circuitmux_t *cmux) { const circuitmux_policy_t *result = NULL; - test_assert(cmux != NULL); + tt_assert(cmux != NULL); if (cmux) { if (cmux == mock_cgp_tgt_1) result = mock_cgp_val_1; else if (cmux == mock_cgp_tgt_2) result = mock_cgp_val_2; @@ -342,11 +342,11 @@ test_scheduler_channel_states(void *arg) */ MOCK(scheduler_run, scheduler_run_noop_mock); - test_eq(smartlist_len(channels_pending), 0); + tt_int_op(smartlist_len(channels_pending), ==, 0); /* Set up a fake channel */ ch1 = new_fake_channel(); - test_assert(ch1); + tt_assert(ch1); /* Start it off in OPENING */ ch1->state = CHANNEL_STATE_OPENING; @@ -354,70 +354,70 @@ test_scheduler_channel_states(void *arg) ch1->cmux = circuitmux_alloc(); /* Try to register it */ channel_register(ch1); - test_assert(ch1->registered); + tt_assert(ch1->registered); /* It should start off in SCHED_CHAN_IDLE */ - test_eq(ch1->scheduler_state, SCHED_CHAN_IDLE); + tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_IDLE); /* Now get another one */ ch2 = new_fake_channel(); - test_assert(ch2); + tt_assert(ch2); ch2->state = CHANNEL_STATE_OPENING; ch2->cmux = circuitmux_alloc(); channel_register(ch2); - test_assert(ch2->registered); + tt_assert(ch2->registered); /* Send it to SCHED_CHAN_WAITING_TO_WRITE */ scheduler_channel_has_waiting_cells(ch1); - test_eq(ch1->scheduler_state, SCHED_CHAN_WAITING_TO_WRITE); + tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_WAITING_TO_WRITE); /* This should send it to SCHED_CHAN_PENDING */ scheduler_channel_wants_writes(ch1); - test_eq(ch1->scheduler_state, SCHED_CHAN_PENDING); - test_eq(smartlist_len(channels_pending), 1); + tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), ==, 1); /* Now send ch2 to SCHED_CHAN_WAITING_FOR_CELLS */ scheduler_channel_wants_writes(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); /* Drop ch2 back to idle */ scheduler_channel_doesnt_want_writes(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_IDLE); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_IDLE); /* ...and back to SCHED_CHAN_WAITING_FOR_CELLS */ scheduler_channel_wants_writes(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); /* ...and this should kick ch2 into SCHED_CHAN_PENDING */ scheduler_channel_has_waiting_cells(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); - test_eq(smartlist_len(channels_pending), 2); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), ==, 2); /* This should send ch2 to SCHED_CHAN_WAITING_TO_WRITE */ scheduler_channel_doesnt_want_writes(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_TO_WRITE); - test_eq(smartlist_len(channels_pending), 1); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_TO_WRITE); + tt_int_op(smartlist_len(channels_pending), ==, 1); /* ...and back to SCHED_CHAN_PENDING */ scheduler_channel_wants_writes(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); - test_eq(smartlist_len(channels_pending), 2); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), ==, 2); /* Now we exercise scheduler_touch_channel */ old_count = scheduler_compare_channels_mock_ctr; scheduler_touch_channel(ch1); - test_assert(scheduler_compare_channels_mock_ctr > old_count); + tt_assert(scheduler_compare_channels_mock_ctr > old_count); /* Close */ channel_mark_for_close(ch1); - test_eq(ch1->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch1->state, ==, CHANNEL_STATE_CLOSING); channel_mark_for_close(ch2); - test_eq(ch2->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSING); channel_closed(ch1); - test_eq(ch1->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch1->state, ==, CHANNEL_STATE_CLOSED); ch1 = NULL; channel_closed(ch2); - test_eq(ch2->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSED); ch2 = NULL; /* Shut things down */ @@ -474,20 +474,20 @@ test_scheduler_compare_channels(void *arg) /* Equal-channel case */ result = scheduler_compare_channels(&c1, &c1); - test_eq(result, 0); + tt_int_op(result, ==, 0); /* Distinct channels, distinct policies */ result = scheduler_compare_channels(&c1, &c2); - test_eq(result, -1); + tt_int_op(result, ==, -1); result = scheduler_compare_channels(&c2, &c1); - test_eq(result, 1); + tt_int_op(result, ==, 1); /* Distinct channels, same policy */ mock_cgp_val_2 = mock_cgp_val_1; result = scheduler_compare_channels(&c1, &c2); - test_eq(result, -1); + tt_int_op(result, ==, -1); result = scheduler_compare_channels(&c2, &c1); - test_eq(result, 1); + tt_int_op(result, ==, 1); done: @@ -512,24 +512,24 @@ test_scheduler_initfree(void *arg) { (void)arg; - test_eq(channels_pending, NULL); - test_eq(run_sched_ev, NULL); + tt_ptr_op(channels_pending, ==, NULL); + tt_ptr_op(run_sched_ev, ==, NULL); mock_event_init(); MOCK(tor_libevent_get_base, tor_libevent_get_base_mock); scheduler_init(); - test_assert(channels_pending != NULL); - test_assert(run_sched_ev != NULL); + tt_assert(channels_pending != NULL); + tt_assert(run_sched_ev != NULL); scheduler_free_all(); UNMOCK(tor_libevent_get_base); mock_event_free_all(); - test_eq(channels_pending, NULL); - test_eq(run_sched_ev, NULL); + tt_ptr_op(channels_pending, ==, NULL); + tt_ptr_op(run_sched_ev, ==, NULL); done: return; @@ -558,11 +558,11 @@ test_scheduler_loop(void *arg) */ MOCK(scheduler_run, scheduler_run_noop_mock); - test_eq(smartlist_len(channels_pending), 0); + tt_int_op(smartlist_len(channels_pending), ==, 0); /* Set up a fake channel */ ch1 = new_fake_channel(); - test_assert(ch1); + tt_assert(ch1); /* Start it off in OPENING */ ch1->state = CHANNEL_STATE_OPENING; @@ -570,53 +570,53 @@ test_scheduler_loop(void *arg) ch1->cmux = circuitmux_alloc(); /* Try to register it */ channel_register(ch1); - test_assert(ch1->registered); + tt_assert(ch1->registered); /* Finish opening it */ channel_change_state(ch1, CHANNEL_STATE_OPEN); /* It should start off in SCHED_CHAN_IDLE */ - test_eq(ch1->scheduler_state, SCHED_CHAN_IDLE); + tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_IDLE); /* Now get another one */ ch2 = new_fake_channel(); - test_assert(ch2); + tt_assert(ch2); ch2->state = CHANNEL_STATE_OPENING; ch2->cmux = circuitmux_alloc(); channel_register(ch2); - test_assert(ch2->registered); + tt_assert(ch2->registered); /* * Don't open ch2; then channel_num_cells_writeable() will return * zero and we'll get coverage of that exception case in scheduler_run() */ - test_eq(ch1->state, CHANNEL_STATE_OPEN); - test_eq(ch2->state, CHANNEL_STATE_OPENING); + tt_int_op(ch1->state, ==, CHANNEL_STATE_OPEN); + tt_int_op(ch2->state, ==, CHANNEL_STATE_OPENING); /* Send it to SCHED_CHAN_WAITING_TO_WRITE */ scheduler_channel_has_waiting_cells(ch1); - test_eq(ch1->scheduler_state, SCHED_CHAN_WAITING_TO_WRITE); + tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_WAITING_TO_WRITE); /* This should send it to SCHED_CHAN_PENDING */ scheduler_channel_wants_writes(ch1); - test_eq(ch1->scheduler_state, SCHED_CHAN_PENDING); - test_eq(smartlist_len(channels_pending), 1); + tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), ==, 1); /* Now send ch2 to SCHED_CHAN_WAITING_FOR_CELLS */ scheduler_channel_wants_writes(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); /* Drop ch2 back to idle */ scheduler_channel_doesnt_want_writes(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_IDLE); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_IDLE); /* ...and back to SCHED_CHAN_WAITING_FOR_CELLS */ scheduler_channel_wants_writes(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); /* ...and this should kick ch2 into SCHED_CHAN_PENDING */ scheduler_channel_has_waiting_cells(ch2); - test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); - test_eq(smartlist_len(channels_pending), 2); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), ==, 2); /* * Now we've got two pending channels and need to fire off @@ -634,11 +634,11 @@ test_scheduler_loop(void *arg) * Assert that they're still in the states we left and aren't still * pending */ - test_eq(ch1->state, CHANNEL_STATE_OPEN); - test_eq(ch2->state, CHANNEL_STATE_OPENING); - test_assert(ch1->scheduler_state != SCHED_CHAN_PENDING); - test_assert(ch2->scheduler_state != SCHED_CHAN_PENDING); - test_eq(smartlist_len(channels_pending), 0); + tt_int_op(ch1->state, ==, CHANNEL_STATE_OPEN); + tt_int_op(ch2->state, ==, CHANNEL_STATE_OPENING); + tt_assert(ch1->scheduler_state != SCHED_CHAN_PENDING); + tt_assert(ch2->scheduler_state != SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), ==, 0); /* Now, finish opening ch2, and get both back to pending */ channel_change_state(ch2, CHANNEL_STATE_OPEN); @@ -646,11 +646,11 @@ test_scheduler_loop(void *arg) scheduler_channel_wants_writes(ch2); scheduler_channel_has_waiting_cells(ch1); scheduler_channel_has_waiting_cells(ch2); - test_eq(ch1->state, CHANNEL_STATE_OPEN); - test_eq(ch2->state, CHANNEL_STATE_OPEN); - test_eq(ch1->scheduler_state, SCHED_CHAN_PENDING); - test_eq(ch2->scheduler_state, SCHED_CHAN_PENDING); - test_eq(smartlist_len(channels_pending), 2); + tt_int_op(ch1->state, ==, CHANNEL_STATE_OPEN); + tt_int_op(ch2->state, ==, CHANNEL_STATE_OPEN); + tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_PENDING); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_PENDING); + tt_int_op(smartlist_len(channels_pending), ==, 2); /* Now, set up the channel_flush_some_cells() mock */ MOCK(channel_flush_some_cells, channel_flush_some_cells_mock); @@ -680,24 +680,24 @@ test_scheduler_loop(void *arg) * ch1 should have gone to SCHED_CHAN_WAITING_FOR_CELLS, with 16 flushed * and 32 writeable. */ - test_eq(ch1->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch1->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); /* * ...ch2 should also have gone to SCHED_CHAN_WAITING_FOR_CELLS, with * channel_more_to_flush() returning false and channel_num_cells_writeable() * > 0/ */ - test_eq(ch2->scheduler_state, SCHED_CHAN_WAITING_FOR_CELLS); + tt_int_op(ch2->scheduler_state, ==, SCHED_CHAN_WAITING_FOR_CELLS); /* Close */ channel_mark_for_close(ch1); - test_eq(ch1->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch1->state, ==, CHANNEL_STATE_CLOSING); channel_mark_for_close(ch2); - test_eq(ch2->state, CHANNEL_STATE_CLOSING); + tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSING); channel_closed(ch1); - test_eq(ch1->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch1->state, ==, CHANNEL_STATE_CLOSED); ch1 = NULL; channel_closed(ch2); - test_eq(ch2->state, CHANNEL_STATE_CLOSED); + tt_int_op(ch2->state, ==, CHANNEL_STATE_CLOSED); ch2 = NULL; /* Shut things down */ @@ -729,22 +729,22 @@ test_scheduler_queue_heuristic(void *arg) /* Not yet inited case */ scheduler_update_queue_heuristic(now - 180); - test_eq(queue_heuristic, 0); - test_eq(queue_heuristic_timestamp, now - 180); + tt_int_op(queue_heuristic, ==, 0); + tt_int_op(queue_heuristic_timestamp, ==, now - 180); queue_heuristic = 1000000000L; queue_heuristic_timestamp = now - 120; scheduler_update_queue_heuristic(now - 119); - test_eq(queue_heuristic, 500000000L); - test_eq(queue_heuristic_timestamp, now - 119); + tt_int_op(queue_heuristic, ==, 500000000L); + tt_int_op(queue_heuristic_timestamp, ==, now - 119); scheduler_update_queue_heuristic(now - 116); - test_eq(queue_heuristic, 62500000L); - test_eq(queue_heuristic_timestamp, now - 116); + tt_int_op(queue_heuristic, ==, 62500000L); + tt_int_op(queue_heuristic_timestamp, ==, now - 116); qh = scheduler_get_queue_heuristic(); - test_eq(qh, 0); + tt_int_op(qh, ==, 0); done: return; From 12b6c7df4aaf3224bc5649ef69a06dccc58ae961 Mon Sep 17 00:00:00 2001 From: Andrea Shepard Date: Tue, 7 Oct 2014 09:53:57 -0700 Subject: [PATCH 79/79] Make queue thresholds and flush size for global scheduler into config options --- src/or/config.c | 22 ++++++++++++++++++++++ src/or/or.h | 13 +++++++++++++ src/or/scheduler.c | 38 +++++++++++++++++++++++++++++--------- src/or/scheduler.h | 3 +++ 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index 86d7c6e502..c12437232d 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -367,6 +367,9 @@ static config_var_t option_vars_[] = { V(ServerDNSSearchDomains, BOOL, "0"), V(ServerDNSTestAddresses, CSV, "www.google.com,www.mit.edu,www.yahoo.com,www.slashdot.org"), + V(SchedulerLowWaterMark, MEMUNIT, "16 kB"), + V(SchedulerHighWaterMark, MEMUNIT, "32 kB"), + V(SchedulerMaxFlushCells, UINT, "16"), V(ShutdownWaitLength, INTERVAL, "30 seconds"), V(SocksListenAddress, LINELIST, NULL), V(SocksPolicy, LINELIST, NULL), @@ -1522,6 +1525,25 @@ options_act(const or_options_t *old_options) return -1; } + /* Set up scheduler thresholds */ + if (options->SchedulerLowWaterMark > 0 && + options->SchedulerHighWaterMark > options->SchedulerLowWaterMark) { + scheduler_set_watermarks(options->SchedulerLowWaterMark, + options->SchedulerHighWaterMark, + (options->SchedulerMaxFlushCells > 0) ? + options->SchedulerMaxFlushCells : 16); + } else { + if (options->SchedulerLowWaterMark == 0) { + log_warn(LD_GENERAL, "Bad SchedulerLowWaterMark option"); + } + + if (options->SchedulerHighWaterMark <= options->SchedulerLowWaterMark) { + log_warn(LD_GENERAL, "Bad SchedulerHighWaterMark option"); + } + + return -1; + } + /* Set up accounting */ if (accounting_parse_options(options, 0)<0) { log_warn(LD_CONFIG,"Error in accounting options"); diff --git a/src/or/or.h b/src/or/or.h index 320931d471..25df54a669 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4229,6 +4229,19 @@ typedef struct { /** Should we send the timestamps that pre-023 hidden services want? */ int Support022HiddenServices; + + /** Low-water mark for global scheduler - start sending when estimated + * queued size falls below this threshold. + */ + uint32_t SchedulerLowWaterMark; + /** High-water mark for global scheduler - stop sending when estimated + * queued size exceeds this threshold. + */ + uint32_t SchedulerHighWaterMark; + /** Flush size for global scheduler - flush this many cells at a time + * when sending. + */ + unsigned int SchedulerMaxFlushCells; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ diff --git a/src/or/scheduler.c b/src/or/scheduler.c index c161393b5a..c2ede846bf 100644 --- a/src/or/scheduler.c +++ b/src/or/scheduler.c @@ -21,16 +21,20 @@ #include #endif -#define SCHED_Q_LOW_WATER 16384 -#define SCHED_Q_HIGH_WATER (2 * SCHED_Q_LOW_WATER) +/* + * Scheduler high/low watermarks + */ + +static uint32_t sched_q_low_water = 16384; +static uint32_t sched_q_high_water = 32768; /* * Maximum cells to flush in a single call to channel_flush_some_cells(); * setting this low means more calls, but too high and we could overshoot - * SCHED_Q_HIGH_WATER. + * sched_q_high_water. */ -#define SCHED_MAX_FLUSH_CELLS 16 +static uint32_t sched_max_flush_cells = 16; /* * Write scheduling works by keeping track of which channels can @@ -343,7 +347,7 @@ scheduler_more_work(void) { tor_assert(channels_pending); - return ((scheduler_get_queue_heuristic() < SCHED_Q_LOW_WATER) && + return ((scheduler_get_queue_heuristic() < sched_q_low_water) && ((smartlist_len(channels_pending) > 0))) ? 1 : 0; } @@ -387,12 +391,12 @@ scheduler_run, (void)) log_debug(LD_SCHED, "We have a chance to run the scheduler"); - if (scheduler_get_queue_heuristic() < SCHED_Q_LOW_WATER) { + if (scheduler_get_queue_heuristic() < sched_q_low_water) { n_chans_before = smartlist_len(channels_pending); q_len_before = channel_get_global_queue_estimate(); q_heur_before = scheduler_get_queue_heuristic(); - while (scheduler_get_queue_heuristic() <= SCHED_Q_HIGH_WATER && + while (scheduler_get_queue_heuristic() <= sched_q_high_water && smartlist_len(channels_pending) > 0) { /* Pop off a channel */ chan = smartlist_pqueue_pop(channels_pending, @@ -410,10 +414,10 @@ scheduler_run, (void)) flushed = 0; while (flushed < n_cells && - scheduler_get_queue_heuristic() <= SCHED_Q_HIGH_WATER) { + scheduler_get_queue_heuristic() <= sched_q_high_water) { flushed_this_time = channel_flush_some_cells(chan, - MIN(SCHED_MAX_FLUSH_CELLS, + MIN(sched_max_flush_cells, n_cells - flushed)); if (flushed_this_time <= 0) break; flushed += flushed_this_time; @@ -686,3 +690,19 @@ scheduler_update_queue_heuristic(time_t now) /* else no update needed, or time went backward */ } +/** + * Set scheduler watermarks and flush size + */ + +void +scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush) +{ + /* Sanity assertions - caller should ensure these are true */ + tor_assert(lo > 0); + tor_assert(hi > lo); + tor_assert(max_flush > 0); + + sched_q_low_water = lo; + sched_q_high_water = hi; + sched_max_flush_cells = max_flush; +} diff --git a/src/or/scheduler.h b/src/or/scheduler.h index 8854d5a508..404776b18b 100644 --- a/src/or/scheduler.h +++ b/src/or/scheduler.h @@ -34,6 +34,9 @@ void scheduler_adjust_queue_size(channel_t *chan, char dir, uint64_t adj); /* Notify scheduler that a channel's queue position may have changed */ void scheduler_touch_channel(channel_t *chan); +/* Adjust the watermarks from config file*/ +void scheduler_set_watermarks(uint32_t lo, uint32_t hi, uint32_t max_flush); + /* Things only scheduler.c and its test suite should see */ #ifdef SCHEDULER_PRIVATE_