Selkies
Developer Referencestream_server

TransferPacer

File-transfer pacing shared across all transfers of one server.

Two modes. A static cap (rate_bps) exists for links whose rate the operator already knows. Without one, the pacer instead holds every download inside a shared allowance that adapts to the bottleneck: the download socket's unsent-queue depth is the gauge (RTT inflation where the queue ioctl is unavailable), so a rate that builds a queue (bufferbloat = the video stream stalls) is walked back, and one that drains cleanly earns headroom. No link estimate is needed. A connection offering neither gauge gives the adaptive mode nothing to react to, so it is left unpaced rather than throttled blindly; the static cap still applies.

Transfers share ONE rate across concurrent downloads and uploads on purpose: the link sees one source regardless of how many sockets a browser opens. Upload reads have no usable gauge on their own socket — the client's uplink queue stands in the client's kernel, invisible to this side — so on this shared pacer they ride only the static-cap leg, ungauged (see connection_state); their congestion control is a second, upload-only pacer fed UplinkGauge verdicts through pace_verdict.

Attributes

attributestatic_bps
= static_bps
attributeadaptive
= adaptive
attributerate_bps
= static_bps or 256 * 1024
attributeactivebool

Functions

func__init__(self, static_bps=0, adaptive=False) -> None
Source Code
def __init__(self, static_bps: int = 0, adaptive: bool = False) -> None:
    self.static_bps = static_bps
    self.adaptive = adaptive
    self.rate_bps = static_bps or 256 * 1024
    self._tokens = self.rate_bps * 0.5
    self._ts = time.monotonic()
    self._congested = False
    self._probe_ceiling = None
    self._slow_start = True
    self._hold_until = 0.0
paramself
paramstatic_bpsint
= 0
paramadaptivebool
= False

Returns

None
funcconnection_state(self, gauged=True) -> Dict[str, Any]

Per-transfer gauge state. The RTT floor is a property of one connection's path: sharing it would let a nearby client's short base RTT make a distant client's read as permanent congestion. Upload reads pass gauged=False: with no honest congestion signal they pace against the static cap alone and pass untouched in adaptive-only mode, like any other gaugeless connection.

Source Code
def connection_state(self, gauged: bool = True) -> Dict[str, Any]:
    """Per-transfer gauge state. The RTT floor is a property of one
    connection's path: sharing it would let a nearby client's short base
    RTT make a distant client's read as permanent congestion. Upload
    reads pass gauged=False: with no honest congestion signal they pace
    against the static cap alone and pass untouched in adaptive-only
    mode, like any other gaugeless connection."""
    return {"rtt_floor_us": None, "gauged": gauged}
paramself
paramgaugedbool
= True

Returns

typing.Dict[str, typing.Any]
funcpace(self, sock, nbytes, conn) -> None

Sample the gauge and sleep off what nbytes overdraws.

After a long idle gap the remembered rate is stale, so the multiplicative ramp is re-entered (TCP's restart after idle): a link that got faster meanwhile is rediscovered in chunks, not minutes, and one that got slower is cut by the first gauge sample. The deficit is slept off here and paid back by the next call's elapsed-time refill; zeroing the balance after the sleep would credit the slept interval twice and double the delivered rate.

Source Code
async def pace(self, sock: Any, nbytes: int, conn: Dict[str, Any]) -> None:
    """Sample the gauge and sleep off what `nbytes` overdraws.

    After a long idle gap the remembered rate is stale, so the
    multiplicative ramp is re-entered (TCP's restart after idle): a link
    that got faster meanwhile is rediscovered in chunks, not minutes, and
    one that got slower is cut by the first gauge sample. The deficit is
    slept off here and paid back by the next call's elapsed-time refill;
    zeroing the balance after the sleep would credit the slept interval
    twice and double the delivered rate.
    """
    if not self.active:
        return
    if self.adaptive and conn["gauged"]:
        outq = _sock_unsent_bytes(sock) if sock is not None else None
        if outq is not None:
            self._gauge_backoff(
                congested=outq > 192 * 1024, clear=outq < 96 * 1024, cut=0.6)
        else:
            rtt = _sock_rtt_us(sock) if sock is not None else None
            if rtt:
                floor = conn["rtt_floor_us"] = (
                    rtt if conn["rtt_floor_us"] is None
                    else min(conn["rtt_floor_us"], rtt)
                )
                self._gauge_backoff(
                    congested=rtt > floor + 8000, clear=True, cut=0.5)
            else:
                conn["gauged"] = False
    if self.adaptive and not conn["gauged"] and not self.static_bps:
        return
    await self._bucket(nbytes)
paramself
paramsockAny
paramnbytesint
paramconnDict[str, Any]

Returns

None
funcpace_verdict(self, nbytes, congested) -> None

Adaptive leg for a transfer gauged by its caller.

Uploads use this: their congestion lives on the client's uplink, readable only over the session socket (UplinkGauge), so the caller supplies the verdict this side's probes cannot. congested=None (no fresh sample) holds the rate and still drains the bucket; True/False are one _gauge_backoff step. The cut is gentler than the socket gauges' — a delay verdict fires at a bounded queue where a loss-like signal means one already overflowed — and the growth step is proportional rather than the fixed 8 KiB: verdicts arrive at the gauge's ping cadence, a few per second where the download gauges sample per chunk, and a fixed step at that cadence would take minutes to recover a fast link's post-cut rate. Together they hold the AIMD sawtooth's duty cycle near the line instead of near half of it.

Source Code
async def pace_verdict(self, nbytes: int, congested: Optional[bool]) -> None:
    """Adaptive leg for a transfer gauged by its caller.

    Uploads use this: their congestion lives on the client's uplink,
    readable only over the session socket (`UplinkGauge`), so the caller
    supplies the verdict this side's probes cannot. ``congested=None``
    (no fresh sample) holds the rate and still drains the bucket;
    True/False are one `_gauge_backoff` step. The cut is gentler than the
    socket gauges' — a delay verdict fires at a bounded queue where a
    loss-like signal means one already overflowed — and the growth step
    is proportional rather than the fixed 8 KiB: verdicts arrive at the
    gauge's ping cadence, a few per second where the download gauges
    sample per chunk, and a fixed step at that cadence would take minutes
    to recover a fast link's post-cut rate. Together they hold the AIMD
    sawtooth's duty cycle near the line instead of near half of it.
    """
    if not self.active:
        return
    if self.adaptive and congested is not None:
        step = max(8 * 1024, int(self.rate_bps * 0.03))
        self._gauge_backoff(
            congested=congested, clear=not congested, cut=0.65, step=step)
    await self._bucket(nbytes)
paramself
paramnbytesint
paramcongestedOptional[bool]

Returns

None
func_bucket(self, nbytes) -> None

Drain nbytes from the token bucket, sleeping off any overdraw.

Source Code
async def _bucket(self, nbytes: int) -> None:
    """Drain `nbytes` from the token bucket, sleeping off any overdraw."""
    now = time.monotonic()
    if self.adaptive and now - self._ts > 10:
        self._slow_start = True
    limit = min(self.rate_bps, self._ceiling)
    self._tokens = min(limit * 0.5, self._tokens + (now - self._ts) * limit)
    self._ts = now
    self._tokens -= nbytes
    if self._tokens < 0:
        await asyncio.sleep(-self._tokens / limit)
paramself
paramnbytesint

Returns

None
func_gauge_backoff(self, congested, clear, cut, step=8 * 1024) -> None

One congestion-control step on the shared allowance: a congested sample multiplies the rate down; a clear one probes upward — multiplicatively while no congestion has ever been seen (the initial ramp toward an unknown link rate), additively by step after (fine-grained probing near the working point, TCP's post-ssthresh split; the caller sizes the step to its sample cadence).

The recovery ceiling arms ONCE per congestion epoch, from the rate at the epoch's first congested sample (ssthresh semantics): arming it per chunk lets a sustained spike ratchet the ceiling toward the floor, and computing it from the post-backoff rate pins recovery below the rate itself. Reaching the ceiling releases it so clear stretches keep probing past the last congested rate; that sawtooth is what keeps a link that gets faster later reachable.

A cut also pauses growth for a drain window: resuming on the first clear sample keeps the bottleneck queue standing, and the cut never relieves the stream sharing the link. The epoch closes only on a clear sample past that window: a clear inside the hold still reflects the pre-cut queue draining, and ending the epoch there would let an oscillating gauge re-arm the ceiling from each freshly cut rate — the same ratchet, one flap at a time.

Source Code
def _gauge_backoff(self, congested: bool, clear: bool, cut: float,
                   step: int = 8 * 1024) -> None:
    """One congestion-control step on the shared allowance: a congested
    sample multiplies the rate down; a clear one probes upward —
    multiplicatively while no congestion has ever been seen (the initial
    ramp toward an unknown link rate), additively by ``step`` after
    (fine-grained probing near the working point, TCP's post-ssthresh
    split; the caller sizes the step to its sample cadence).

    The recovery ceiling arms ONCE per congestion epoch, from the rate at
    the epoch's first congested sample (ssthresh semantics): arming it per
    chunk lets a sustained spike ratchet the ceiling toward the floor, and
    computing it from the post-backoff rate pins recovery below the rate
    itself. Reaching the ceiling releases it so clear stretches keep
    probing past the last congested rate; that sawtooth is what keeps a
    link that gets faster later reachable.

    A cut also pauses growth for a drain window: resuming on the first
    clear sample keeps the bottleneck queue standing, and the cut never
    relieves the stream sharing the link. The epoch closes only on a
    clear sample past that window: a clear inside the hold still reflects
    the pre-cut queue draining, and ending the epoch there would let an
    oscillating gauge re-arm the ceiling from each freshly cut rate — the
    same ratchet, one flap at a time."""
    if congested:
        self._slow_start = False
        if not self._congested:
            self._congested = True
            self._probe_ceiling = max(self.rate_bps, 2 * self._RATE_FLOOR)
        self.rate_bps = max(self.rate_bps * cut, self._RATE_FLOOR)
        self._hold_until = time.monotonic() + 1.5
        return
    if not clear:
        return
    if time.monotonic() < self._hold_until:
        return
    self._congested = False
    ceiling = self._probe_ceiling
    if ceiling is not None and self.rate_bps >= ceiling:
        self._probe_ceiling = ceiling = None
    bound = min(
        ceiling if ceiling is not None else self.rate_bps * 4,
        self._ceiling,
    )
    if self._slow_start:
        self.rate_bps = min(self.rate_bps * 2, bound)
    else:
        self.rate_bps = min(self.rate_bps + step, bound)
paramself
paramcongestedbool
paramclearbool
paramcutfloat
paramstepint
= 8 * 1024

Returns

None

On this page

Edit on GitHub