1
0
Fork 0
Univerxel/deps/picoquic/fastcc.c

328 lines
13 KiB
C

/*
* Author: Christian Huitema
* Copyright (c) 2019, Private Octopus, Inc.
* All rights reserved.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Private Octopus, Inc. BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "picoquic_internal.h"
#include <stdlib.h>
#include <string.h>
#include "cc_common.h"
#define FASTCC_MIN_ACK_DELAY_FOR_BANDWIDTH 5000
#define FASTCC_BANDWIDTH_FRACTION 0.5
#define FASTCC_REPEAT_THRESHOLD 4
#define FASTCC_BETA 0.125
#define FASTCC_BETA_HEAVY_LOSS 0.5
#define FASTCC_EVAL_ALPHA 0.25
#define FASTCC_DELAY_THRESHOLD_MAX 25000
#define FASTCC_NB_PERIOD 6
#define FASTCC_PERIOD 1000000
typedef enum {
picoquic_fastcc_initial = 0,
picoquic_fastcc_eval,
picoquic_fastcc_freeze
} picoquic_fastcc_alg_state_t;
typedef struct st_picoquic_fastcc_state_t {
picoquic_fastcc_alg_state_t alg_state;
uint64_t end_of_freeze; /* When to exit the freeze state */
uint64_t last_ack_time;
uint64_t ack_interval;
uint64_t nb_bytes_ack;
uint64_t nb_bytes_ack_since_rtt; /* accumulate byte count until RTT measured */
uint64_t end_of_epoch;
uint64_t recovery_sequence;
uint64_t rtt_min;
uint64_t delay_threshold;
uint64_t rolling_rtt_min; /* Min RTT measured for this epoch */
uint64_t last_rtt_min[FASTCC_NB_PERIOD];
int nb_cc_events;
unsigned int last_freeze_was_timeout : 1;
unsigned int last_freeze_was_not_delay : 1;
unsigned int rtt_min_is_trusted : 1;
picoquic_min_max_rtt_t rtt_filter;
} picoquic_fastcc_state_t;
uint64_t picoquic_fastcc_delay_threshold(uint64_t rtt_min)
{
uint64_t delay = rtt_min / 8;
if (delay > FASTCC_DELAY_THRESHOLD_MAX) {
delay = FASTCC_DELAY_THRESHOLD_MAX;
}
return delay;
}
void picoquic_fastcc_reset(picoquic_fastcc_state_t* fastcc_state, picoquic_path_t* path_x, uint64_t current_time)
{
memset(fastcc_state, 0, sizeof(picoquic_fastcc_state_t));
fastcc_state->alg_state = picoquic_fastcc_initial;
fastcc_state->rtt_min = path_x->smoothed_rtt;
fastcc_state->rolling_rtt_min = fastcc_state->rtt_min;
fastcc_state->delay_threshold = picoquic_fastcc_delay_threshold(fastcc_state->rtt_min);
fastcc_state->end_of_epoch = current_time + FASTCC_PERIOD;
path_x->cwin = PICOQUIC_CWIN_INITIAL;
}
void picoquic_fastcc_init(picoquic_path_t* path_x, uint64_t current_time)
{
/* Initialize the state of the congestion control algorithm */
picoquic_fastcc_state_t* fastcc_state = path_x->congestion_alg_state;
if (fastcc_state == NULL) {
fastcc_state = (picoquic_fastcc_state_t*)malloc(sizeof(picoquic_fastcc_state_t));
}
if (fastcc_state != NULL) {
memset(fastcc_state, 0, sizeof(picoquic_fastcc_state_t));
fastcc_state->alg_state = picoquic_fastcc_initial;
fastcc_state->rtt_min = path_x->smoothed_rtt;
fastcc_state->rolling_rtt_min = fastcc_state->rtt_min;
fastcc_state->delay_threshold = picoquic_fastcc_delay_threshold(fastcc_state->rtt_min);
fastcc_state->end_of_epoch = current_time + FASTCC_PERIOD;
path_x->cwin = PICOQUIC_CWIN_INITIAL;
}
path_x->congestion_alg_state = (void*)fastcc_state;
}
/* Reaction to ECN/CE or sustained losses.
* This is more or less the same code as added to bbr.
*
* This code is called if an ECN/EC event is received, or a timeout
* event, or a lost event indicating a high loss rate
*/
static void fastcc_notify_congestion(
picoquic_cnx_t* cnx,
picoquic_path_t* path_x,
picoquic_fastcc_state_t* fastcc_state,
uint64_t current_time,
int is_delay,
int is_timeout)
{
if (fastcc_state->alg_state == picoquic_fastcc_freeze &&
(!is_timeout || !fastcc_state->last_freeze_was_timeout) &&
(!is_delay || !fastcc_state->last_freeze_was_not_delay)) {
/* Do not treat additional events during same freeze interval */
return;
}
fastcc_state->last_freeze_was_not_delay = !is_delay;
fastcc_state->last_freeze_was_timeout = is_timeout;
fastcc_state->alg_state = picoquic_fastcc_freeze;
fastcc_state->end_of_freeze = current_time + fastcc_state->rtt_min;
fastcc_state->recovery_sequence = picoquic_cc_get_sequence_number(cnx);
fastcc_state->nb_cc_events = 0;
if (is_delay) {
path_x->cwin -= (uint64_t)(FASTCC_BETA * (double)path_x->cwin);
}
else {
path_x->cwin = path_x->cwin / 2;
}
if (is_timeout || path_x->cwin < PICOQUIC_CWIN_MINIMUM) {
path_x->cwin = PICOQUIC_CWIN_MINIMUM;
}
picoquic_update_pacing_data(cnx, path_x, 0);
}
/*
* Properly implementing fastcc requires managing a number of
* signals, such as packet losses or acknowledgements. We attempt
* to condensate all that in a single API, which could be shared
* by many different congestion control algorithms.
*/
void picoquic_fastcc_notify(
picoquic_cnx_t* cnx, picoquic_path_t* path_x,
picoquic_congestion_notification_t notification,
uint64_t rtt_measurement,
uint64_t one_way_delay,
uint64_t nb_bytes_acknowledged,
uint64_t lost_packet_number,
uint64_t current_time)
{
#ifdef _WINDOWS
UNREFERENCED_PARAMETER(lost_packet_number);
#endif
picoquic_fastcc_state_t* fastcc_state = (picoquic_fastcc_state_t*)path_x->congestion_alg_state;
if (fastcc_state != NULL) {
if (fastcc_state->alg_state == picoquic_fastcc_freeze &&
(current_time > fastcc_state->end_of_freeze ||
fastcc_state->recovery_sequence <= picoquic_cc_get_ack_number(cnx))) {
if (fastcc_state->last_freeze_was_timeout) {
fastcc_state->alg_state = picoquic_fastcc_initial;
}
else {
fastcc_state->alg_state = picoquic_fastcc_eval;
}
fastcc_state->last_freeze_was_not_delay = 0;
fastcc_state->last_freeze_was_timeout = 0;
fastcc_state->nb_cc_events = 0;
fastcc_state->nb_bytes_ack_since_rtt = 0;
}
switch (notification) {
case picoquic_congestion_notification_acknowledgement:
if (fastcc_state->alg_state != picoquic_fastcc_freeze) {
/* Count the bytes since last RTT measurement */
fastcc_state->nb_bytes_ack_since_rtt += nb_bytes_acknowledged;
/* Compute pacing data. */
picoquic_update_pacing_data(cnx, path_x, 0);
}
break;
case picoquic_congestion_notification_ecn_ec:
fastcc_notify_congestion(cnx, path_x, fastcc_state, current_time, 0, 0);
break;
case picoquic_congestion_notification_repeat:
case picoquic_congestion_notification_timeout:
if (picoquic_hystart_loss_test(&fastcc_state->rtt_filter, notification, lost_packet_number)) {
fastcc_notify_congestion(cnx, path_x, fastcc_state, current_time, 0,
(notification == picoquic_congestion_notification_timeout) ? 1 : 0);
}
break;
case picoquic_congestion_notification_spurious_repeat:
if (fastcc_state->nb_cc_events > 0) {
fastcc_state->nb_cc_events--;
}
break;
case picoquic_congestion_notification_rtt_measurement:
{
uint64_t delta_rtt = 0;
picoquic_filter_rtt_min_max(&fastcc_state->rtt_filter, rtt_measurement);
if (fastcc_state->rtt_filter.is_init) {
/* We use the maximum of the last samples as the candidate for the
* min RTT, in order to filter the rtt jitter */
if (current_time > fastcc_state->end_of_epoch) {
/* If end of epoch, reset the min RTT to min of remembered periods,
* and roll the period. */
fastcc_state->rtt_min = UINT64_MAX;
for (int i = FASTCC_NB_PERIOD - 1; i > 0; i--) {
fastcc_state->last_rtt_min[i] = fastcc_state->last_rtt_min[i - 1];
if (fastcc_state->last_rtt_min[i] > 0 &&
fastcc_state->last_rtt_min[i] < fastcc_state->rtt_min) {
fastcc_state->rtt_min = fastcc_state->last_rtt_min[i];
}
}
fastcc_state->delay_threshold = picoquic_fastcc_delay_threshold(fastcc_state->rtt_min);
fastcc_state->last_rtt_min[0] = fastcc_state->rolling_rtt_min;
fastcc_state->rolling_rtt_min = fastcc_state->rtt_filter.sample_max;
fastcc_state->end_of_epoch = current_time + FASTCC_PERIOD;
}
else if (fastcc_state->rtt_filter.sample_max < fastcc_state->rolling_rtt_min || fastcc_state->rolling_rtt_min == 0) {
/* If not end of epoch, update the rolling minimum */
fastcc_state->rolling_rtt_min = fastcc_state->rtt_filter.sample_max;
if (fastcc_state->rolling_rtt_min < fastcc_state->rtt_min) {
fastcc_state->rtt_min = fastcc_state->rolling_rtt_min;
}
}
}
if (fastcc_state->alg_state != picoquic_fastcc_freeze) {
if (rtt_measurement < fastcc_state->rtt_min) {
fastcc_state->delay_threshold = picoquic_fastcc_delay_threshold(fastcc_state->rtt_min);
}
else if (fastcc_state->rtt_min_is_trusted){
delta_rtt = rtt_measurement - fastcc_state->rtt_min;
}
else {
fastcc_state->rtt_min = rtt_measurement;
fastcc_state->rolling_rtt_min = rtt_measurement;
fastcc_state->rtt_min_is_trusted = 1;
delta_rtt = 0;
}
if (delta_rtt < fastcc_state->delay_threshold) {
double alpha = 1.0;
fastcc_state->nb_cc_events = 0;
if (fastcc_state->alg_state != picoquic_fastcc_initial) {
alpha -= ((double)delta_rtt / (double)fastcc_state->delay_threshold);
alpha *= FASTCC_EVAL_ALPHA;
}
/* Increase the window if it is not frozen */
if (path_x->last_time_acked_data_frame_sent > path_x->last_sender_limited_time) {
path_x->cwin += (uint64_t)(alpha * (double)fastcc_state->nb_bytes_ack_since_rtt);
}
fastcc_state->nb_bytes_ack_since_rtt = 0;
}
else {
/* May well be congested */
fastcc_state->nb_cc_events++;
if (fastcc_state->nb_cc_events >= FASTCC_REPEAT_THRESHOLD) {
/* Too many events, reduce the window */
fastcc_notify_congestion(cnx, path_x, fastcc_state, current_time, 1, 0);
}
}
}
}
break;
case picoquic_congestion_notification_cwin_blocked:
break;
case picoquic_congestion_notification_reset:
picoquic_fastcc_reset(fastcc_state, path_x, current_time);
break;
default:
/* ignore */
break;
}
}
}
/* Release the state of the congestion control algorithm */
void picoquic_fastcc_delete(picoquic_path_t* path_x)
{
if (path_x->congestion_alg_state != NULL) {
free(path_x->congestion_alg_state);
path_x->congestion_alg_state = NULL;
}
}
/* Observe the state of congestion control */
void picoquic_fastcc_observe(picoquic_path_t* path_x, uint64_t* cc_state, uint64_t* cc_param)
{
picoquic_fastcc_state_t* fastcc_state = (picoquic_fastcc_state_t*)path_x->congestion_alg_state;
*cc_state = (uint64_t)fastcc_state->alg_state;
*cc_param = fastcc_state->rolling_rtt_min;
}
/* Definition record for the FAST CC algorithm */
#define picoquic_fastcc_ID "fast"
picoquic_congestion_algorithm_t picoquic_fastcc_algorithm_struct = {
picoquic_fastcc_ID, 4,
picoquic_fastcc_init,
picoquic_fastcc_notify,
picoquic_fastcc_delete,
picoquic_fastcc_observe
};
picoquic_congestion_algorithm_t* picoquic_fastcc_algorithm = &picoquic_fastcc_algorithm_struct;