Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ticket25573 v2 #270

Closed
Closed

Conversation

Labels
None yet
Projects
None yet
3 participants
@mikeperry-tor
Copy link
Contributor

@mikeperry-tor mikeperry-tor commented Aug 9, 2018

Broken into functional commits.

We allow their CONNECTEDs, RESOLVEDs, ENDs, SENDMEs, and DATA cells to not
count as dropped until the windows are empty, or we get an END.

This commit does not change behavior. It only changes CIRC_BW event field
values.
This commit does not change behavior. It only changes CIRC_BW field values.
Avoid data corrupton by avoiding mixing up old stream ids with new ones.

This commit changes client behavior.
TRUNCATED cells were ignored while in path bias. Now they are obeyed, and
cause us to tear down the circuit. The actual impact is minimal, since we
would just wait around for a probe that would never arrive before.

This commit changes client behavior.
@coveralls
Copy link

@coveralls coveralls commented Aug 9, 2018

Coverage Status

Coverage decreased (-0.1%) to 59.82% when pulling b8ae08c on mikeperry-tor:ticket25573-v2 into 0f0dac0 on torproject:maint-0.3.4.

Copy link
Contributor

@teor2345 teor2345 left a comment

This is my review of "Ticket #25573: Track half-closed stream ids". (There is a lot of code here, so I'll do a review for each group of similar commits.)

In general, I think this code would be simpler and more performant if it used a hash table, rather than a smartlist. Using a hash table would make it easier to check correctness, too.

half_conn->connected_pending = conn->base_.state ==
AP_CONN_STATE_CONNECT_WAIT;

/* Data should only arrive if we're not waiting on a resolved.
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

Typo: "resolved cell"?

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 20, 2018

Fixed in 38dde96

if (!half_conns)
return 0;

SMARTLIST_FOREACH_BEGIN(half_conns, half_edge_t *, half_conn) {
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

How large do we expect half_conns to get?
Would it be better implemented as a hash table?

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 20, 2018

The half_conns list should be quite small, and is checked infrequently. Connections only get added when a client hangs up on a TCP connection early, before getting the END cell from the other side. This typically only happens in cases where the user interrupts client action by a cancel/stop/kill at the application layer.

It would take a very broken client to fill the half_closed list, and even if the half_closed connection table manages to get full, it will be at most 65535 uint16_t equality comparisons to go through it. And it will only be checked when using that same circuit for a new stream, or when getting stray data back with stale stream ids on that circuit.

I actually think that the additional memory allocation, indirection, indirect calls, and hashing overhead involved in a hash table will make it considerably more expensive to use for these small sizes, especially since we're only comparing int16_t values in the search.

All of this made me opt for the simple list for this (which is also what we use for our opened streams). The simple list also felt easier to check for lifetime and correctness issues. (Though the refactoring you suggested does make this even easier).

/**
* Check if this stream_id is in a half-closed state. If so,
* check if it still has data cells pending, and decrement that
* window if so.
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

Conceptually, these seem like separate functions:

  1. find the half-closed state for this stream
  2. check if it still has data cells pending
  3. decrement that window

You perform step 1 in all the connection_half_edge_is_valid_*() functions, and duplicate all that code. Consider using a macro or function pointer?

Maybe it's ok to duplicate the code as an optimisation. But using a hash table, the search code would be simpler.

Also, this function should be named after an action, not a question. Here's one possible name:

  • connection_half_edge_reduce_data_window

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 20, 2018

I like the idea of factoring out the search into a separate function. I did this in 8257aab.

It seems safer to me for callers to use explicit named functions for each of the window checks, rather than making them pass in a pointer to an int or something. This way, caller misuse is more easily spotted, and we don't have to worry about dangling pointers.

In my mind, it makes sense to have these calls be questions rather than action statements. This is consistent with the philosophy I used for the patch. None of these functions perform any actions that alter behavior. They only tell the caller if the cell counts as valid data for the control port statistics, so that the caller can count it (or not).

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 20, 2018

(e08e36f has an additional fixup to make the search function take a const list).

Copy link
Contributor

@teor2345 teor2345 Aug 28, 2018

Thanks, the refactored code looks much better.

*/
static void
connection_half_edge_add(edge_connection_t *conn,
origin_circuit_t *circ)

This comment has been hidden.

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 20, 2018

I added this in 8257aab.

*/
int
connection_half_edge_is_valid_data(smartlist_t *half_conns,
streamid_t stream_id)
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

I don't know if you'll want to const smartlist_t *half_conns.

Technically, we don't modify it, but we do modify one of the items' attributes.

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 20, 2018

I guess this is consistent with the "these are verfication functions that don't alter behavior" notion. I made the containers const in b75fcb5

src/or/relay.c Outdated
if (connection_half_edge_is_valid_data(ocirc->half_streams,
rh.stream_id)) {
circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh.length);
log_info(domain,"data cell valid on half-closed stream.");
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

Consider logging the stream id

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 20, 2018

Ok logged both stream id and circ id in 85f86d0 for all of your comments below.

src/or/relay.c Outdated
rh.stream_id)) {

circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh.length);
log_info(domain,"end cell (%s) valid on half-closed stream.",
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

Consider logging the stream id

src/or/relay.c Outdated
if (connection_half_edge_is_valid_connected(ocirc->half_streams,
rh.stream_id)) {
circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh.length);
log_info(domain,"connected cell valid on half-closed stream.");
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

Consider logging the stream id

src/or/relay.c Outdated
if (connection_half_edge_is_valid_sendme(ocirc->half_streams,
rh.stream_id)) {
circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh.length);
log_info(domain,"sendme cell valid on half-closed stream.");
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

Consider logging the stream id

src/or/relay.c Outdated
if (connection_half_edge_is_valid_resolved(ocirc->half_streams,
rh.stream_id)) {
circuit_read_valid_data(TO_ORIGIN_CIRCUIT(circ), rh.length);
log_info(domain,"resolved cell valid on half-closed stream.");
Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

Consider logging the stream id

@@ -154,18 +180,299 @@ test_circbw_relay(void *arg)
tt_int_op(circ->n_overhead_read_circ_bw, OP_EQ, overhead); \
} while (0)

static int
subtest_circbw_halfclosed(origin_circuit_t *circ, streamid_t init_id)

This comment has been hidden.

Copy link
Contributor

@teor2345 teor2345 Aug 16, 2018

Consider testing what happens when the stream ids wrap:
#270 (comment)

Copy link
Contributor

@teor2345 teor2345 Aug 28, 2018

The duplicate issue is resolved by 7afb6f0.

@mikeperry-tor, do you test what happens when stream ids wrap? Do you think that's important now with all the other changes?

@@ -2767,6 +2767,15 @@ get_unique_stream_id_by_circ(origin_circuit_t *circ)
for (tmpconn = circ->p_streams; tmpconn; tmpconn=tmpconn->next_stream)
if (tmpconn->stream_id == test_stream_id)
goto again;

if (circ->half_streams) {
SMARTLIST_FOREACH_BEGIN(circ->half_streams, half_edge_t *, half_conn) {

This comment has been hidden.

This comment has been hidden.

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 20, 2018

This loop will only run when we open a new stream on a circuit that had lots of half-closed streams. I think this is unlikely, and even worst case is only 65535 uint16_t compares, as per above. (Also we chose to use a O(N) search on the opened connections linked list for the same reasons).

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 25, 2018

Ok, I decided to sort the list, so we can bsearch them on the common operations of cell arrival, and also guard against duplicate stream ids. I added tests for duplicates and list management correctness as well. I still think this is better than a hashtable, because for small list sizes, the memory allocation, hashing, and fragmentation overhead of a hashtable strikes me as not worth it.

Even on a full list of 65k half-closed connections (which should never happen), the slow operation (adding a new half-closed id, which triggers a sort), takes under 10ms on my 2015-era laptop. Since add only happens on EOF, even in this worst case, this will not directly affect user experience or page load.

7afb6f0

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 25, 2018

Oh duh, we don't have a smartlist insert function to insert in order, but we have a bsearch to search for the index, and we can insert and delete at that index. I should be using that instead of sorting (and also instead of remove_keeporder). I will have a new commit for this in a bit.

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 25, 2018

Ok sorry for the briandamage. I pushed a fixup to the original branch to use bsearch_idx at bcedf53

If you prefer, a commit of the two that is squashed is e60a239

Copy link
Contributor Author

@mikeperry-tor mikeperry-tor Aug 25, 2018

Arg. I forgot to run the tests apparently. One more fixup: 60478b1

The commit with all three of these fixups is 0b45fbd

mikeperry-tor added 15 commits Aug 17, 2018
Refactor search code out of functions.
Group window-related functions together, and move the two half-closed
termination cases after them.
Header file typo/minor type fixes.
Log stream and circ ids of half-closed cells.
Make half-conn search take a const.
More consts. Technically I guess the container is const, even though
we modify its elements' attributes.
It does not modify the actual list.
It should not be the case that this list gets large enough to matter, but
sorting it also makes it easier to ensure against duplicate elements.

A sorted list is preferable to a hashtable because this set should be very
small. There should only be a handful of half-closed connections on a circuit,
if that. This makes the memory allocation, memory fragmentation, and hashing
overhead of the hash table not worth it.
Also use bsearch_idx to get the index to add/delete at.
…w one

Use search function in get_unique_stream_id_by_circ().
…w one

Test various types of streamid wrapping.
@mikeperry-tor
Copy link
Contributor Author

@mikeperry-tor mikeperry-tor commented Aug 29, 2018

Closing this pull request in favor of #296.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment