| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
| |
libpure-link.a holds them; pure-serve and the M5 harness link it instead
of naming its source files.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A record over a piece (1,000 bytes) went in pieces even when it fit the
packet whole: a record of 1,013 bytes went as two RECORD frames, and the
receiver gathered it the slow way (an allocation, pieces, then onto the
line). A record none of whose pieces went yet now goes whole in a RECORDS
frame whenever it fits what is left of the packet; its pieces are marked
sent together. A record too large for a packet, or one resent after
some of its pieces were lost, still goes in pieces, and a receiver takes
a record whole or in pieces alike.
The transport alone takes 2.85 us a record of 1,013 bytes, not 3.18
(quinn: 3.90). transport.c now checks every record delivered: in order,
once each, its bytes as sent. None was wrong in a network namespace with
1 ms of delay, 3 % loss and 10 % reordering, with records of 61, 1,013
and 16,013 bytes.
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
A queued record had an allocation of its own for its bytes and its
pieces' states, freed when the receiver stored it. Records come and go in
order, so their bytes now go one after another into a ring, never cut by
its end, and go back as the ring's head moves past the ones stored; a
ring too small grows to twice its size, the records moved into it in
order. A record of one piece keeps its piece's state in itself.
The transport alone drains 200,000 records of 61 bytes at 5.3 to 6.5
million a second; per record the link takes 0.137 us, the listener 0.14:
0.28 in all, against quinn's 0.26 a message over one stream.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
A receiver copied each record into an allocation of its own, copied them
all again into a delivery, and freed each once stored: about 110 ns a
record of 61 bytes. Once the last number heard is known, whole records
that come in order now go onto the stream's line as a delivery carries
them (u32 size, bytes). The line is handed over as it is, and let go by
moving its head; its front is given back when there is no room at its
end. Records that come early or in pieces, or before the last heard is
known, still wait in held, and move onto the line once it reaches them.
The transport alone: the listener took 0.20 us a record of 61 bytes, now
0.15; with the link, 0.31 us a record in all, against quinn's 0.26.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
changes
A listener's socket now asks for GRO: the kernel hands over the datagrams
of one sender that came one after another as one buffer, each the
segment's size but the last, and on the loopback a burst sent with GSO
comes whole instead of cut into datagrams. The listener takes up to eight
such buffers at once. It gives its AES-GCM context a key only when the
packet's run has another than the one it holds, for opening and for
sealing answers.
The transport alone drained 200,000 records of 61 bytes at 4.2 million a
second (0.454 us a record, both sides); now at 4.9 million (0.394 us).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
A RECORD frame carries a number, a size, an offset and a length before
each record's bytes, 19 bytes: a packet held 13.7 records of 61 bytes. A
RECORDS frame (0x06) carries the first record's number and a count, then
whole records whose numbers follow on, each its size as a varint and its
bytes: 17.2 a packet. A piece of a record larger than a piece still goes
in a RECORD frame; a receiver takes both.
The transport alone (research/experiments/019-idle-links/transport.c)
drains 200,000 records of 61 bytes in 11,600 packets instead of 14,600;
per record the link takes 0.217 us and the listener 0.23, 0.447 us in all,
against quinn's 0.43 a message over one stream.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Every queued record wrote the link's eventfd, a system call each, and woke
the thread to send a packet of a few records. A record queued now wakes
the thread only when it sleeps and nobody woke it yet; one queued while it
sends marks the link, and the thread looks again before it sleeps. With
records queued one at a time as fast as a thread can, 200,000 small ones
went at 400k a second, and now at 730k; queuing one costs 0.43 us, not
1.0.
research/experiments/019-idle-links/transport.c measures the transport
alone: a link and a listener in one process, no app, no store. A backlog
of 200,000 records of 61 bytes drains at 3 to 4 million a second, 0.24 us
a record on each side.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A backlog made the link slow in its square: the sender looked at every
queued record's pieces twice a turn to know whether any waited to be
stored, and the receiver moved all its held records down each time it let
the first one go.
- The sender counts the pieces received and not stored, and keeps where
the unsent ones begin; every change of a piece's state goes through one
place that keeps both.
- The receiver keeps its held records from a head index. One in order goes
last, and one out of order finds its place by halving. The record's bytes
and its pieces' marks are one allocation.
- Packets go in bursts with UDP GSO, 54 at a time: 0.45 us a packet on this
Pi, against 2.4 us for a send each. A full packet is padded to a whole
datagram (PADDING frames, as QUIC does). Datagrams are taken 64 at a time
(recvmmsg).
Twenty thousand saved sends of 61 bytes drained at 370k a second; now at
990k. Per record, the sender's link thread took 1.1 us and now takes 0.5;
the receiver's took 2.4 and now takes 0.5.
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
A listener lets a quiet sender's secret go with its active state, so a
node started again made one X25519 for every sender that came back (169
us on this Pi, most of a wake's CPU): asymmetric work in proportion to the
links, which design 011 counts as a reason to design again. The secret is
now remembered in a file beside the key (KEY.links, 0600, begun again for
another node's key): a place of 64 bytes a sender in the order allowed,
mapped, so a sender at rest takes no memory for it. A place is the first
28 bytes of the sender's key, a check (FNV-1a of the key and the secret)
and the secret, written secret first and key last; a place whose check
fails is made again. The UDP link checks see a receiver started again
make no X25519.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Each run a listener knew held two OpenSSL contexts. With 16 KiB pages,
what those allocations left behind when idle senders went kept 42 MB of
free heap resident after 10,000 senders had come and gone (in use: 0.6
MB). A run now keeps its keys (16 bytes each way and the nonce bases),
and the listener seals and opens with one context, giving it each
packet's key: about 130 ns a packet on this Pi. Two runs a sender, not
four; a sender's address as an IPv6 one (an IPv4 one fits). What idle
senders held goes back to the system once 1,024 have gone, or 10 s after
any went (malloc_trim). HKDF is fetched once, not at every derivation.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A sender allowed is now a peer of 48 bytes (its key, its key's id, its
sources), found through an index by key id; ids are 32 bits, so keys that
share one are each tried. What a sending peer needs besides (the secret
its key shares with the node's, its runs' keys and addresses, its
streams) is made at its first packet and let go once it has been quiet
for the idle period (--link-idle-ms, 30 s at first) with nothing of it
waiting: the next packet makes it again, one X25519, no handshake. The
listener hands over and answers only the streams and runs that changed,
not every one at each turn. --senders FILE allows senders a line each.
The UDP link checks take their sender from such a file among a thousand
others, let it go after 400 ms of quiet, and see an order after a longer
pause arrive all the same.
|
|
|
pure-serve --send OUTPUT=pure://host:port/INPUT sends an output's saved
sends to another node's --link-port with no handshake: the first packet
carries records. Keys come from the two nodes' keys (X25519, HKDF-SHA256,
AES-128-GCM), with a random run in each packet's header so no nonce
repeats across restarts; nothing is stored for them. What lasts is the
apps' own: the sender's outbox and the receiver's heard numbers
(pure_app_heard, new). The sender detects losses as RFC 9002 says, with
NewReno, and trims a send once the receiver says it stored it; the
receiver hands the owner whole records in order, which it applies from
the one after the source's last heard number, stopping at a gap, and
answers how far it stored with every acknowledgment. Stored bytes are
the bytes on the wire: an output and an input of one shape need no JSON.
server_links_udp kills the receiver, the sender, then both (-9) while
four clients buy: every saved order is applied once, with no handshake
(five runs, 10k-11k orders each). server_links_udp_lossy does it again in
a namespace of its own with the link's packets delayed, lost (3 %) and
reordered (10 %) by netem (three runs, 11k-12k orders).
|