webrtc_m130/net/dcsctp/tx/send_queue.h
Victor Boivie d7fd0f9744 dcsctp: Handle rapid closing of streams
When streams were to be reset, but there was already an ongoing
stream reset command in-flight, those streams wouldn't be properly
reset. When multiple streams were reset close to each other (within
an RTT), some streams would not have their SSNs reset, which resulted
in the stream resuming the SSN sequence. This could result in ordered
streams not delivering all messages as the receiver wouldn't deliver any
messages with SSN different from the expected SSN=0.

In WebRTC data channels, this would be triggered if multiple channels
were closed at roughly the same time, then re-opened, and continued
to be used in ordered mode. Unordered messages would still be delivered,
but the stream state could be wrong as the DATA_CHANNEL_ACK message is
sent ordered, and possibly not delivered.

There were unit tests for this, but not on the socket level using
real components, but just on the stream reset handler using mocks,
where this issue wasn't found. Also, those mocks didn't validate that
the correct parameters were provided, so that's fixed now.

The root cause was the PrepareResetStreams was only called if there
wasn't an ongoing stream reset operation in progress. One may try to
solve it by always calling PrepareResetStreams also when there is an
ongoing request, or to call it when the request has finished. One would
then realize that when the response of the outgoing stream request is
received, and CommitResetStreams is called, it would reset all paused
and (prepared) to-be-reset streams - not just the ones in the outgoing
stream request.

One cause of this was the lack of a single source of truth of the stream
states. The SendQueue kept track of which streams that were paused, but
the stream reset handler kept track of which streams that were
resetting. As that's error prone, this CL moves the source of truth
completely to the SendQueue and defining explicit stream pause states. A
stream can be in one of these possible states:

  * Not paused. This is the default for an active stream.
  * Pending to be paused. This is when it's about to be reset, but
    there is a message that has been partly sent, with fragments
    remaining to be sent before it can be paused.
  * Paused, with no partly sent message. In this state, it's ready to
    be reset.
  * Resetting. A stream transitions into this state when it has been
    paused and has been included in an outgoing stream reset request.
    When this request has been responded to, the stream can really be
    reset (SSN=0, MID=0).

This CL also improves logging, and adds socket tests to catch this
issue.

Bug: webrtc:13994, chromium:1320194
Change-Id: I883570d1f277bc01e52b1afad62d6be2aca930a2
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/261180
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#36771}
2022-05-05 07:30:58 +00:00

133 lines
5.7 KiB
C++

/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_TX_SEND_QUEUE_H_
#define NET_DCSCTP_TX_SEND_QUEUE_H_
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "net/dcsctp/common/internal_types.h"
#include "net/dcsctp/packet/data.h"
#include "net/dcsctp/public/types.h"
namespace dcsctp {
class SendQueue {
public:
// Container for a data chunk that is produced by the SendQueue
struct DataToSend {
explicit DataToSend(Data data) : data(std::move(data)) {}
// The data to send, including all parameters.
Data data;
// Partial reliability - RFC3758
MaxRetransmits max_retransmissions = MaxRetransmits::NoLimit();
TimeMs expires_at = TimeMs::InfiniteFuture();
};
virtual ~SendQueue() = default;
// TODO(boivie): This interface is obviously missing an "Add" function, but
// that is postponed a bit until the story around how to model message
// prioritization, which is important for any advanced stream scheduler, is
// further clarified.
// Produce a chunk to be sent.
//
// `max_size` refers to how many payload bytes that may be produced, not
// including any headers.
virtual absl::optional<DataToSend> Produce(TimeMs now, size_t max_size) = 0;
// Discards a partially sent message identified by the parameters `unordered`,
// `stream_id` and `message_id`. The `message_id` comes from the returned
// information when having called `Produce`. A partially sent message means
// that it has had at least one fragment of it returned when `Produce` was
// called prior to calling this method).
//
// This is used when a message has been found to be expired (by the partial
// reliability extension), and the retransmission queue will signal the
// receiver that any partially received message fragments should be skipped.
// This means that any remaining fragments in the Send Queue must be removed
// as well so that they are not sent.
//
// This function returns true if this message had unsent fragments still in
// the queue that were discarded, and false if there were no such fragments.
virtual bool Discard(IsUnordered unordered,
StreamID stream_id,
MID message_id) = 0;
// Prepares the stream to be reset. This is used to close a WebRTC data
// channel and will be signaled to the other side.
//
// Concretely, it discards all whole (not partly sent) messages in the given
// stream and pauses that stream so that future added messages aren't
// produced until `ResumeStreams` is called.
//
// TODO(boivie): Investigate if it really should discard any message at all.
// RFC8831 only mentions that "[RFC6525] also guarantees that all the messages
// are delivered (or abandoned) before the stream is reset."
//
// This method can be called multiple times to add more streams to be
// reset, and paused while they are resetting. This is the first part of the
// two-phase commit protocol to reset streams, where the caller completes the
// procedure by either calling `CommitResetStreams` or `RollbackResetStreams`.
virtual void PrepareResetStream(StreamID stream_id) = 0;
// Indicates if there are any streams that are ready to be reset.
virtual bool HasStreamsReadyToBeReset() const = 0;
// Returns a list of streams that are ready to be included in an outgoing
// stream reset request. Any streams that are returned here must be included
// in an outgoing stream reset request, and there must not be concurrent
// requests. Before calling this method again, you must have called
virtual std::vector<StreamID> GetStreamsReadyToBeReset() = 0;
// Called to commit to reset the streams returned by
// `GetStreamsReadyToBeReset`. It will reset the stream sequence numbers
// (SSNs) and message identifiers (MIDs) and resume the paused streams.
virtual void CommitResetStreams() = 0;
// Called to abort the resetting of streams returned by
// `GetStreamsReadyToBeReset`. Will resume the paused streams without
// resetting the stream sequence numbers (SSNs) or message identifiers (MIDs).
// Note that the non-partial messages that were discarded when calling
// `PrepareResetStreams` will not be recovered, to better match the intention
// from the sender to "close the channel".
virtual void RollbackResetStreams() = 0;
// Resets all message identifier counters (MID, SSN) and makes all partially
// messages be ready to be re-sent in full. This is used when the peer has
// been detected to have restarted and is used to try to minimize the amount
// of data loss. However, data loss cannot be completely guaranteed when a
// peer restarts.
virtual void Reset() = 0;
// Returns the amount of buffered data. This doesn't include packets that are
// e.g. inflight.
virtual size_t buffered_amount(StreamID stream_id) const = 0;
// Returns the total amount of buffer data, for all streams.
virtual size_t total_buffered_amount() const = 0;
// Returns the limit for the `OnBufferedAmountLow` event. Default value is 0.
virtual size_t buffered_amount_low_threshold(StreamID stream_id) const = 0;
// Sets a limit for the `OnBufferedAmountLow` event.
virtual void SetBufferedAmountLowThreshold(StreamID stream_id,
size_t bytes) = 0;
};
} // namespace dcsctp
#endif // NET_DCSCTP_TX_SEND_QUEUE_H_