improve(dgram): supersede stale UDP output
- commit
- c5e603fcf6475a70ed717e0528d3bda4dffd0930
- author
- brice · · 2026-07-09 16:52 UTC
- parent
- 5d4ffc4
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
4 files · +185 -53
M internal/adapters/dgram/transport.go +110 -37
| @@ -172,18 +172,20 @@ type Transport struct { | |||
| 172 | 172 | ||
| 173 | 173 | writeMu sync.Mutex | |
| 174 | 174 | ||
| 175 | - | deliverMu sync.Mutex | |
| 176 | - | deliverCond *sync.Cond | |
| 177 | - | nextRecvSeq uint64 | |
| 178 | - | recvBuf map[uint64]ports.Frame | |
| 175 | + | deliverMu sync.Mutex | |
| 176 | + | deliverCond *sync.Cond | |
| 177 | + | nextRecvSeq uint64 | |
| 178 | + | recvBuf map[uint64]ports.Frame | |
| 179 | + | recvSkipThrough uint64 | |
| 179 | 180 | } | |
| 180 | 181 | ||
| 181 | 182 | type pending struct { | |
| 182 | - | frame ports.Frame | |
| 183 | - | first time.Time | |
| 184 | - | last time.Time | |
| 185 | - | attempts int | |
| 186 | - | retransmitted bool | |
| 183 | + | frame ports.Frame | |
| 184 | + | first time.Time | |
| 185 | + | last time.Time | |
| 186 | + | attempts int | |
| 187 | + | retransmitted bool | |
| 188 | + | outputSkipThrough uint64 | |
| 187 | 189 | } | |
| 188 | 190 | ||
| 189 | 191 | func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) { | |
| @@ -245,13 +247,12 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD | |||
| 245 | 247 | func (t *Transport) DatagramTransport() {} | |
| 246 | 248 | ||
| 247 | 249 | func (t *Transport) Send(f ports.Frame) error { | |
| 248 | - | // MsgOutput supersession is intentionally deferred: this reliable stream uses | |
| 249 | - | // strict sequence delivery, so deleting an older pending output without a | |
| 250 | - | // receive-side skip/ack signal would stall later frames behind the missing | |
| 251 | - | // sequence. Add an explicit droppable/skip protocol before replacing pending | |
| 252 | - | // outputs. | |
| 253 | 250 | reliable := true | |
| 254 | 251 | t.mu.Lock() | |
| 252 | + | outputSkipThrough := uint64(0) | |
| 253 | + | if f.Type == ports.MsgOutput { | |
| 254 | + | outputSkipThrough = t.supersedePendingOutputLocked() | |
| 255 | + | } | |
| 255 | 256 | var pendingWaitStarted time.Time | |
| 256 | 257 | for reliable && len(t.pending) >= t.maxPending && !t.closed { | |
| 257 | 258 | if t.linkState != ports.LinkStateConnected { | |
| @@ -289,10 +290,10 @@ func (t *Transport) Send(f ports.Frame) error { | |||
| 289 | 290 | seq := t.seq | |
| 290 | 291 | if reliable { | |
| 291 | 292 | now := t.clock.Now() | |
| 292 | - | t.pending[seq] = &pending{frame: f, first: now, last: now} | |
| 293 | + | t.pending[seq] = &pending{frame: f, first: now, last: now, outputSkipThrough: outputSkipThrough} | |
| 293 | 294 | } | |
| 294 | 295 | t.mu.Unlock() | |
| 295 | - | if err := t.sendData(seq, reliable, f); err != nil { | |
| 296 | + | if err := t.sendData(seq, reliable, f, outputSkipThrough); err != nil { | |
| 296 | 297 | if reliable { | |
| 297 | 298 | t.mu.Lock() | |
| 298 | 299 | if _, ok := t.pending[seq]; ok { | |
| @@ -306,6 +307,34 @@ func (t *Transport) Send(f ports.Frame) error { | |||
| 306 | 307 | return nil | |
| 307 | 308 | } | |
| 308 | 309 | ||
| 310 | + | func (t *Transport) supersedePendingOutputLocked() uint64 { | |
| 311 | + | var barrier uint64 | |
| 312 | + | for seq, p := range t.pending { | |
| 313 | + | if p != nil && p.frame.Type != ports.MsgOutput && seq > barrier { | |
| 314 | + | barrier = seq | |
| 315 | + | } | |
| 316 | + | } | |
| 317 | + | var newest uint64 | |
| 318 | + | for seq, p := range t.pending { | |
| 319 | + | if p == nil || p.frame.Type != ports.MsgOutput || seq <= barrier { | |
| 320 | + | continue | |
| 321 | + | } | |
| 322 | + | if seq > newest { | |
| 323 | + | newest = seq | |
| 324 | + | } | |
| 325 | + | } | |
| 326 | + | if newest == 0 { | |
| 327 | + | return 0 | |
| 328 | + | } | |
| 329 | + | for seq, p := range t.pending { | |
| 330 | + | if p != nil && p.frame.Type == ports.MsgOutput && seq > barrier && seq <= newest { | |
| 331 | + | delete(t.pending, seq) | |
| 332 | + | } | |
| 333 | + | } | |
| 334 | + | t.notifySendWaitersLocked() | |
| 335 | + | return newest | |
| 336 | + | } | |
| 337 | + | ||
| 309 | 338 | func (t *Transport) Recv() (ports.Frame, error) { | |
| 310 | 339 | select { | |
| 311 | 340 | case f := <-t.in: | |
| @@ -387,8 +416,8 @@ func (t *Transport) unregisterProbe(id uint64) { | |||
| 387 | 416 | t.mu.Unlock() | |
| 388 | 417 | } | |
| 389 | 418 | ||
| 390 | - | func (t *Transport) sendData(seq uint64, reliable bool, f ports.Frame) error { | |
| 391 | - | p := encodeData(seq, reliable, f) | |
| 419 | + | func (t *Transport) sendData(seq uint64, reliable bool, f ports.Frame, outputSkipThrough uint64) error { | |
| 420 | + | p := encodeData(seq, reliable, f, outputSkipThrough) | |
| 392 | 421 | return t.sendPayload(p) | |
| 393 | 422 | } | |
| 394 | 423 | func (t *Transport) sendAck(seq uint64) { | |
| @@ -547,12 +576,12 @@ func (t *Transport) handleRecord(p []byte) { | |||
| 547 | 576 | } | |
| 548 | 577 | t.mu.Unlock() | |
| 549 | 578 | case recData: | |
| 550 | - | seq, reliable, f, ok := decodeData(p) | |
| 579 | + | seq, reliable, f, outputSkipThrough, ok := decodeData(p) | |
| 551 | 580 | if !ok { | |
| 552 | 581 | return | |
| 553 | 582 | } | |
| 554 | 583 | if reliable { | |
| 555 | - | ackSeq, ack, queued := t.enqueueReliable(seq, f) | |
| 584 | + | ackSeq, ack, queued := t.enqueueReliable(seq, f, outputSkipThrough) | |
| 556 | 585 | if ack { | |
| 557 | 586 | t.sendAck(ackSeq) | |
| 558 | 587 | } | |
| @@ -565,9 +594,12 @@ func (t *Transport) handleRecord(p []byte) { | |||
| 565 | 594 | } | |
| 566 | 595 | } | |
| 567 | 596 | ||
| 568 | - | func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, ack bool, queued bool) { | |
| 597 | + | func (t *Transport) enqueueReliable(seq uint64, f ports.Frame, outputSkipThrough uint64) (ackSeq uint64, ack bool, queued bool) { | |
| 569 | 598 | t.deliverMu.Lock() | |
| 570 | 599 | defer t.deliverMu.Unlock() | |
| 600 | + | if f.Type == ports.MsgOutput && outputSkipThrough > t.recvSkipThrough { | |
| 601 | + | t.recvSkipThrough = outputSkipThrough | |
| 602 | + | } | |
| 571 | 603 | if seq < t.nextRecvSeq { | |
| 572 | 604 | return t.highestContiguousRecvLocked(), true, false | |
| 573 | 605 | } | |
| @@ -584,10 +616,15 @@ func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, a | |||
| 584 | 616 | func (t *Transport) highestContiguousRecvLocked() uint64 { | |
| 585 | 617 | seq := t.nextRecvSeq | |
| 586 | 618 | for { | |
| 587 | - | if _, ok := t.recvBuf[seq]; !ok { | |
| 588 | - | break | |
| 619 | + | if _, ok := t.recvBuf[seq]; ok { | |
| 620 | + | seq++ | |
| 621 | + | continue | |
| 622 | + | } | |
| 623 | + | if seq <= t.recvSkipThrough { | |
| 624 | + | seq++ | |
| 625 | + | continue | |
| 589 | 626 | } | |
| 590 | - | seq++ | |
| 627 | + | break | |
| 591 | 628 | } | |
| 592 | 629 | return seq - 1 | |
| 593 | 630 | } | |
| @@ -602,6 +639,10 @@ func (t *Transport) deliveryLoop() { | |||
| 602 | 639 | for { | |
| 603 | 640 | t.deliverMu.Lock() | |
| 604 | 641 | f, ok := t.recvBuf[t.nextRecvSeq] | |
| 642 | + | for !ok && t.nextRecvSeq <= t.recvSkipThrough { | |
| 643 | + | t.nextRecvSeq++ | |
| 644 | + | f, ok = t.recvBuf[t.nextRecvSeq] | |
| 645 | + | } | |
| 605 | 646 | for !ok { | |
| 606 | 647 | if t.isClosed() { | |
| 607 | 648 | t.deliverMu.Unlock() | |
| @@ -609,6 +650,16 @@ func (t *Transport) deliveryLoop() { | |||
| 609 | 650 | } | |
| 610 | 651 | t.deliverCond.Wait() | |
| 611 | 652 | f, ok = t.recvBuf[t.nextRecvSeq] | |
| 653 | + | for !ok && t.nextRecvSeq <= t.recvSkipThrough { | |
| 654 | + | t.nextRecvSeq++ | |
| 655 | + | f, ok = t.recvBuf[t.nextRecvSeq] | |
| 656 | + | } | |
| 657 | + | } | |
| 658 | + | if t.nextRecvSeq <= t.recvSkipThrough && f.Type == ports.MsgOutput { | |
| 659 | + | delete(t.recvBuf, t.nextRecvSeq) | |
| 660 | + | t.nextRecvSeq++ | |
| 661 | + | t.deliverMu.Unlock() | |
| 662 | + | continue | |
| 612 | 663 | } | |
| 613 | 664 | delete(t.recvBuf, t.nextRecvSeq) | |
| 614 | 665 | t.nextRecvSeq++ | |
| @@ -675,8 +726,9 @@ func (t *Transport) resendLoop() { | |||
| 675 | 726 | func (t *Transport) resendPending() { | |
| 676 | 727 | now := t.clock.Now() | |
| 677 | 728 | var resend []struct { | |
| 678 | - | seq uint64 | |
| 679 | - | f ports.Frame | |
| 729 | + | seq uint64 | |
| 730 | + | f ports.Frame | |
| 731 | + | outputSkipThrough uint64 | |
| 680 | 732 | } | |
| 681 | 733 | t.mu.Lock() | |
| 682 | 734 | seqs := make([]uint64, 0, len(t.pending)) | |
| @@ -697,14 +749,15 @@ func (t *Transport) resendPending() { | |||
| 697 | 749 | p.attempts++ | |
| 698 | 750 | p.retransmitted = true | |
| 699 | 751 | resend = append(resend, struct { | |
| 700 | - | seq uint64 | |
| 701 | - | f ports.Frame | |
| 702 | - | }{seq, p.frame}) | |
| 752 | + | seq uint64 | |
| 753 | + | f ports.Frame | |
| 754 | + | outputSkipThrough uint64 | |
| 755 | + | }{seq, p.frame, p.outputSkipThrough}) | |
| 703 | 756 | limit-- | |
| 704 | 757 | } | |
| 705 | 758 | t.mu.Unlock() | |
| 706 | 759 | for _, r := range resend { | |
| 707 | - | _ = t.sendData(r.seq, true, r.f) | |
| 760 | + | _ = t.sendData(r.seq, true, r.f, r.outputSkipThrough) | |
| 708 | 761 | } | |
| 709 | 762 | } | |
| 710 | 763 | ||
| @@ -887,21 +940,41 @@ func (t *Transport) closeWithError(err error) { | |||
| 887 | 940 | t.deliverMu.Unlock() | |
| 888 | 941 | } | |
| 889 | 942 | ||
| 890 | - | func encodeData(seq uint64, reliable bool, f ports.Frame) []byte { | |
| 891 | - | b := make([]byte, 12+len(f.Payload)) | |
| 943 | + | func encodeData(seq uint64, reliable bool, f ports.Frame, outputSkipThrough uint64) []byte { | |
| 944 | + | headerLen := 12 | |
| 945 | + | if outputSkipThrough > 0 { | |
| 946 | + | headerLen = 20 | |
| 947 | + | } | |
| 948 | + | b := make([]byte, headerLen+len(f.Payload)) | |
| 892 | 949 | b[0] = recData | |
| 893 | 950 | binary.BigEndian.PutUint64(b[1:9], seq) | |
| 894 | 951 | if reliable { | |
| 895 | 952 | b[9] = 1 | |
| 896 | 953 | } | |
| 897 | 954 | b[10] = byte(f.Type) | |
| 898 | - | b[11] = 0 | |
| 899 | - | copy(b[12:], f.Payload) | |
| 955 | + | if outputSkipThrough > 0 { | |
| 956 | + | b[11] = 1 | |
| 957 | + | binary.BigEndian.PutUint64(b[12:20], outputSkipThrough) | |
| 958 | + | } | |
| 959 | + | copy(b[headerLen:], f.Payload) | |
| 900 | 960 | return b | |
| 901 | 961 | } | |
| 902 | - | func decodeData(b []byte) (uint64, bool, ports.Frame, bool) { | |
| 962 | + | func decodeData(b []byte) (uint64, bool, ports.Frame, uint64, bool) { | |
| 903 | 963 | if len(b) < 12 || b[0] != recData { | |
| 904 | - | return 0, false, ports.Frame{}, false | |
| 964 | + | return 0, false, ports.Frame{}, 0, false | |
| 965 | + | } | |
| 966 | + | flags := b[11] | |
| 967 | + | if flags&^byte(1) != 0 { | |
| 968 | + | return 0, false, ports.Frame{}, 0, false | |
| 969 | + | } | |
| 970 | + | headerLen := 12 | |
| 971 | + | outputSkipThrough := uint64(0) | |
| 972 | + | if flags&1 != 0 { | |
| 973 | + | if len(b) < 20 { | |
| 974 | + | return 0, false, ports.Frame{}, 0, false | |
| 975 | + | } | |
| 976 | + | headerLen = 20 | |
| 977 | + | outputSkipThrough = binary.BigEndian.Uint64(b[12:20]) | |
| 905 | 978 | } | |
| 906 | - | return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[12:]...)}, true | |
| 979 | + | return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[headerLen:]...)}, outputSkipThrough, true | |
| 907 | 980 | } | |
| @@ -172,18 +172,20 @@ type Transport struct { | |||||
| 172 | 172 | ||||
| 173 | writeMu sync.Mutex | 173 | writeMu sync.Mutex | ||
| 174 | 174 | ||||
| 175 | - | deliverMu sync.Mutex | 175 | + | deliverMu sync.Mutex |
| 176 | - | deliverCond *sync.Cond | 176 | + | deliverCond *sync.Cond |
| 177 | - | nextRecvSeq uint64 | 177 | + | nextRecvSeq uint64 |
| 178 | - | recvBuf map[uint64]ports.Frame | 178 | + | recvBuf map[uint64]ports.Frame |
| 179 | + | recvSkipThrough uint64 | |||
| 179 | } | 180 | } | ||
| 180 | 181 | ||||
| 181 | type pending struct { | 182 | type pending struct { | ||
| 182 | - | frame ports.Frame | 183 | + | frame ports.Frame |
| 183 | - | first time.Time | 184 | + | first time.Time |
| 184 | - | last time.Time | 185 | + | last time.Time |
| 185 | - | attempts int | 186 | + | attempts int |
| 186 | - | retransmitted bool | 187 | + | retransmitted bool |
| 188 | + | outputSkipThrough uint64 | |||
| 187 | } | 189 | } | ||
| 188 | 190 | ||||
| 189 | func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) { | 191 | func NewTransport(pc net.PacketConn, peer net.Addr, key []byte, sendDir, recvDir uint32) (*Transport, error) { | ||
| @@ -245,13 +247,12 @@ func NewTransportWithOptions(pc net.PacketConn, peer net.Addr, key []byte, sendD | |||||
| 245 | func (t *Transport) DatagramTransport() {} | 247 | func (t *Transport) DatagramTransport() {} | ||
| 246 | 248 | ||||
| 247 | func (t *Transport) Send(f ports.Frame) error { | 249 | func (t *Transport) Send(f ports.Frame) error { | ||
| 248 | - | // MsgOutput supersession is intentionally deferred: this reliable stream uses | |||
| 249 | - | // strict sequence delivery, so deleting an older pending output without a | |||
| 250 | - | // receive-side skip/ack signal would stall later frames behind the missing | |||
| 251 | - | // sequence. Add an explicit droppable/skip protocol before replacing pending | |||
| 252 | - | // outputs. | |||
| 253 | reliable := true | 250 | reliable := true | ||
| 254 | t.mu.Lock() | 251 | t.mu.Lock() | ||
| 252 | + | outputSkipThrough := uint64(0) | |||
| 253 | + | if f.Type == ports.MsgOutput { | |||
| 254 | + | outputSkipThrough = t.supersedePendingOutputLocked() | |||
| 255 | + | } | |||
| 255 | var pendingWaitStarted time.Time | 256 | var pendingWaitStarted time.Time | ||
| 256 | for reliable && len(t.pending) >= t.maxPending && !t.closed { | 257 | for reliable && len(t.pending) >= t.maxPending && !t.closed { | ||
| 257 | if t.linkState != ports.LinkStateConnected { | 258 | if t.linkState != ports.LinkStateConnected { | ||
| @@ -289,10 +290,10 @@ func (t *Transport) Send(f ports.Frame) error { | |||||
| 289 | seq := t.seq | 290 | seq := t.seq | ||
| 290 | if reliable { | 291 | if reliable { | ||
| 291 | now := t.clock.Now() | 292 | now := t.clock.Now() | ||
| 292 | - | t.pending[seq] = &pending{frame: f, first: now, last: now} | 293 | + | t.pending[seq] = &pending{frame: f, first: now, last: now, outputSkipThrough: outputSkipThrough} |
| 293 | } | 294 | } | ||
| 294 | t.mu.Unlock() | 295 | t.mu.Unlock() | ||
| 295 | - | if err := t.sendData(seq, reliable, f); err != nil { | 296 | + | if err := t.sendData(seq, reliable, f, outputSkipThrough); err != nil { |
| 296 | if reliable { | 297 | if reliable { | ||
| 297 | t.mu.Lock() | 298 | t.mu.Lock() | ||
| 298 | if _, ok := t.pending[seq]; ok { | 299 | if _, ok := t.pending[seq]; ok { | ||
| @@ -306,6 +307,34 @@ func (t *Transport) Send(f ports.Frame) error { | |||||
| 306 | return nil | 307 | return nil | ||
| 307 | } | 308 | } | ||
| 308 | 309 | ||||
| 310 | + | func (t *Transport) supersedePendingOutputLocked() uint64 { | |||
| 311 | + | var barrier uint64 | |||
| 312 | + | for seq, p := range t.pending { | |||
| 313 | + | if p != nil && p.frame.Type != ports.MsgOutput && seq > barrier { | |||
| 314 | + | barrier = seq | |||
| 315 | + | } | |||
| 316 | + | } | |||
| 317 | + | var newest uint64 | |||
| 318 | + | for seq, p := range t.pending { | |||
| 319 | + | if p == nil || p.frame.Type != ports.MsgOutput || seq <= barrier { | |||
| 320 | + | continue | |||
| 321 | + | } | |||
| 322 | + | if seq > newest { | |||
| 323 | + | newest = seq | |||
| 324 | + | } | |||
| 325 | + | } | |||
| 326 | + | if newest == 0 { | |||
| 327 | + | return 0 | |||
| 328 | + | } | |||
| 329 | + | for seq, p := range t.pending { | |||
| 330 | + | if p != nil && p.frame.Type == ports.MsgOutput && seq > barrier && seq <= newest { | |||
| 331 | + | delete(t.pending, seq) | |||
| 332 | + | } | |||
| 333 | + | } | |||
| 334 | + | t.notifySendWaitersLocked() | |||
| 335 | + | return newest | |||
| 336 | + | } | |||
| 337 | + | ||||
| 309 | func (t *Transport) Recv() (ports.Frame, error) { | 338 | func (t *Transport) Recv() (ports.Frame, error) { | ||
| 310 | select { | 339 | select { | ||
| 311 | case f := <-t.in: | 340 | case f := <-t.in: | ||
| @@ -387,8 +416,8 @@ func (t *Transport) unregisterProbe(id uint64) { | |||||
| 387 | t.mu.Unlock() | 416 | t.mu.Unlock() | ||
| 388 | } | 417 | } | ||
| 389 | 418 | ||||
| 390 | - | func (t *Transport) sendData(seq uint64, reliable bool, f ports.Frame) error { | 419 | + | func (t *Transport) sendData(seq uint64, reliable bool, f ports.Frame, outputSkipThrough uint64) error { |
| 391 | - | p := encodeData(seq, reliable, f) | 420 | + | p := encodeData(seq, reliable, f, outputSkipThrough) |
| 392 | return t.sendPayload(p) | 421 | return t.sendPayload(p) | ||
| 393 | } | 422 | } | ||
| 394 | func (t *Transport) sendAck(seq uint64) { | 423 | func (t *Transport) sendAck(seq uint64) { | ||
| @@ -547,12 +576,12 @@ func (t *Transport) handleRecord(p []byte) { | |||||
| 547 | } | 576 | } | ||
| 548 | t.mu.Unlock() | 577 | t.mu.Unlock() | ||
| 549 | case recData: | 578 | case recData: | ||
| 550 | - | seq, reliable, f, ok := decodeData(p) | 579 | + | seq, reliable, f, outputSkipThrough, ok := decodeData(p) |
| 551 | if !ok { | 580 | if !ok { | ||
| 552 | return | 581 | return | ||
| 553 | } | 582 | } | ||
| 554 | if reliable { | 583 | if reliable { | ||
| 555 | - | ackSeq, ack, queued := t.enqueueReliable(seq, f) | 584 | + | ackSeq, ack, queued := t.enqueueReliable(seq, f, outputSkipThrough) |
| 556 | if ack { | 585 | if ack { | ||
| 557 | t.sendAck(ackSeq) | 586 | t.sendAck(ackSeq) | ||
| 558 | } | 587 | } | ||
| @@ -565,9 +594,12 @@ func (t *Transport) handleRecord(p []byte) { | |||||
| 565 | } | 594 | } | ||
| 566 | } | 595 | } | ||
| 567 | 596 | ||||
| 568 | - | func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, ack bool, queued bool) { | 597 | + | func (t *Transport) enqueueReliable(seq uint64, f ports.Frame, outputSkipThrough uint64) (ackSeq uint64, ack bool, queued bool) { |
| 569 | t.deliverMu.Lock() | 598 | t.deliverMu.Lock() | ||
| 570 | defer t.deliverMu.Unlock() | 599 | defer t.deliverMu.Unlock() | ||
| 600 | + | if f.Type == ports.MsgOutput && outputSkipThrough > t.recvSkipThrough { | |||
| 601 | + | t.recvSkipThrough = outputSkipThrough | |||
| 602 | + | } | |||
| 571 | if seq < t.nextRecvSeq { | 603 | if seq < t.nextRecvSeq { | ||
| 572 | return t.highestContiguousRecvLocked(), true, false | 604 | return t.highestContiguousRecvLocked(), true, false | ||
| 573 | } | 605 | } | ||
| @@ -584,10 +616,15 @@ func (t *Transport) enqueueReliable(seq uint64, f ports.Frame) (ackSeq uint64, a | |||||
| 584 | func (t *Transport) highestContiguousRecvLocked() uint64 { | 616 | func (t *Transport) highestContiguousRecvLocked() uint64 { | ||
| 585 | seq := t.nextRecvSeq | 617 | seq := t.nextRecvSeq | ||
| 586 | for { | 618 | for { | ||
| 587 | - | if _, ok := t.recvBuf[seq]; !ok { | 619 | + | if _, ok := t.recvBuf[seq]; ok { |
| 588 | - | break | 620 | + | seq++ |
| 621 | + | continue | |||
| 622 | + | } | |||
| 623 | + | if seq <= t.recvSkipThrough { | |||
| 624 | + | seq++ | |||
| 625 | + | continue | |||
| 589 | } | 626 | } | ||
| 590 | - | seq++ | 627 | + | break |
| 591 | } | 628 | } | ||
| 592 | return seq - 1 | 629 | return seq - 1 | ||
| 593 | } | 630 | } | ||
| @@ -602,6 +639,10 @@ func (t *Transport) deliveryLoop() { | |||||
| 602 | for { | 639 | for { | ||
| 603 | t.deliverMu.Lock() | 640 | t.deliverMu.Lock() | ||
| 604 | f, ok := t.recvBuf[t.nextRecvSeq] | 641 | f, ok := t.recvBuf[t.nextRecvSeq] | ||
| 642 | + | for !ok && t.nextRecvSeq <= t.recvSkipThrough { | |||
| 643 | + | t.nextRecvSeq++ | |||
| 644 | + | f, ok = t.recvBuf[t.nextRecvSeq] | |||
| 645 | + | } | |||
| 605 | for !ok { | 646 | for !ok { | ||
| 606 | if t.isClosed() { | 647 | if t.isClosed() { | ||
| 607 | t.deliverMu.Unlock() | 648 | t.deliverMu.Unlock() | ||
| @@ -609,6 +650,16 @@ func (t *Transport) deliveryLoop() { | |||||
| 609 | } | 650 | } | ||
| 610 | t.deliverCond.Wait() | 651 | t.deliverCond.Wait() | ||
| 611 | f, ok = t.recvBuf[t.nextRecvSeq] | 652 | f, ok = t.recvBuf[t.nextRecvSeq] | ||
| 653 | + | for !ok && t.nextRecvSeq <= t.recvSkipThrough { | |||
| 654 | + | t.nextRecvSeq++ | |||
| 655 | + | f, ok = t.recvBuf[t.nextRecvSeq] | |||
| 656 | + | } | |||
| 657 | + | } | |||
| 658 | + | if t.nextRecvSeq <= t.recvSkipThrough && f.Type == ports.MsgOutput { | |||
| 659 | + | delete(t.recvBuf, t.nextRecvSeq) | |||
| 660 | + | t.nextRecvSeq++ | |||
| 661 | + | t.deliverMu.Unlock() | |||
| 662 | + | continue | |||
| 612 | } | 663 | } | ||
| 613 | delete(t.recvBuf, t.nextRecvSeq) | 664 | delete(t.recvBuf, t.nextRecvSeq) | ||
| 614 | t.nextRecvSeq++ | 665 | t.nextRecvSeq++ | ||
| @@ -675,8 +726,9 @@ func (t *Transport) resendLoop() { | |||||
| 675 | func (t *Transport) resendPending() { | 726 | func (t *Transport) resendPending() { | ||
| 676 | now := t.clock.Now() | 727 | now := t.clock.Now() | ||
| 677 | var resend []struct { | 728 | var resend []struct { | ||
| 678 | - | seq uint64 | 729 | + | seq uint64 |
| 679 | - | f ports.Frame | 730 | + | f ports.Frame |
| 731 | + | outputSkipThrough uint64 | |||
| 680 | } | 732 | } | ||
| 681 | t.mu.Lock() | 733 | t.mu.Lock() | ||
| 682 | seqs := make([]uint64, 0, len(t.pending)) | 734 | seqs := make([]uint64, 0, len(t.pending)) | ||
| @@ -697,14 +749,15 @@ func (t *Transport) resendPending() { | |||||
| 697 | p.attempts++ | 749 | p.attempts++ | ||
| 698 | p.retransmitted = true | 750 | p.retransmitted = true | ||
| 699 | resend = append(resend, struct { | 751 | resend = append(resend, struct { | ||
| 700 | - | seq uint64 | 752 | + | seq uint64 |
| 701 | - | f ports.Frame | 753 | + | f ports.Frame |
| 702 | - | }{seq, p.frame}) | 754 | + | outputSkipThrough uint64 |
| 755 | + | }{seq, p.frame, p.outputSkipThrough}) | |||
| 703 | limit-- | 756 | limit-- | ||
| 704 | } | 757 | } | ||
| 705 | t.mu.Unlock() | 758 | t.mu.Unlock() | ||
| 706 | for _, r := range resend { | 759 | for _, r := range resend { | ||
| 707 | - | _ = t.sendData(r.seq, true, r.f) | 760 | + | _ = t.sendData(r.seq, true, r.f, r.outputSkipThrough) |
| 708 | } | 761 | } | ||
| 709 | } | 762 | } | ||
| 710 | 763 | ||||
| @@ -887,21 +940,41 @@ func (t *Transport) closeWithError(err error) { | |||||
| 887 | t.deliverMu.Unlock() | 940 | t.deliverMu.Unlock() | ||
| 888 | } | 941 | } | ||
| 889 | 942 | ||||
| 890 | - | func encodeData(seq uint64, reliable bool, f ports.Frame) []byte { | 943 | + | func encodeData(seq uint64, reliable bool, f ports.Frame, outputSkipThrough uint64) []byte { |
| 891 | - | b := make([]byte, 12+len(f.Payload)) | 944 | + | headerLen := 12 |
| 945 | + | if outputSkipThrough > 0 { | |||
| 946 | + | headerLen = 20 | |||
| 947 | + | } | |||
| 948 | + | b := make([]byte, headerLen+len(f.Payload)) | |||
| 892 | b[0] = recData | 949 | b[0] = recData | ||
| 893 | binary.BigEndian.PutUint64(b[1:9], seq) | 950 | binary.BigEndian.PutUint64(b[1:9], seq) | ||
| 894 | if reliable { | 951 | if reliable { | ||
| 895 | b[9] = 1 | 952 | b[9] = 1 | ||
| 896 | } | 953 | } | ||
| 897 | b[10] = byte(f.Type) | 954 | b[10] = byte(f.Type) | ||
| 898 | - | b[11] = 0 | 955 | + | if outputSkipThrough > 0 { |
| 899 | - | copy(b[12:], f.Payload) | 956 | + | b[11] = 1 |
| 957 | + | binary.BigEndian.PutUint64(b[12:20], outputSkipThrough) | |||
| 958 | + | } | |||
| 959 | + | copy(b[headerLen:], f.Payload) | |||
| 900 | return b | 960 | return b | ||
| 901 | } | 961 | } | ||
| 902 | - | func decodeData(b []byte) (uint64, bool, ports.Frame, bool) { | 962 | + | func decodeData(b []byte) (uint64, bool, ports.Frame, uint64, bool) { |
| 903 | if len(b) < 12 || b[0] != recData { | 963 | if len(b) < 12 || b[0] != recData { | ||
| 904 | - | return 0, false, ports.Frame{}, false | 964 | + | return 0, false, ports.Frame{}, 0, false |
| 965 | + | } | |||
| 966 | + | flags := b[11] | |||
| 967 | + | if flags&^byte(1) != 0 { | |||
| 968 | + | return 0, false, ports.Frame{}, 0, false | |||
| 969 | + | } | |||
| 970 | + | headerLen := 12 | |||
| 971 | + | outputSkipThrough := uint64(0) | |||
| 972 | + | if flags&1 != 0 { | |||
| 973 | + | if len(b) < 20 { | |||
| 974 | + | return 0, false, ports.Frame{}, 0, false | |||
| 975 | + | } | |||
| 976 | + | headerLen = 20 | |||
| 977 | + | outputSkipThrough = binary.BigEndian.Uint64(b[12:20]) | |||
| 905 | } | 978 | } | ||
| 906 | - | return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[12:]...)}, true | 979 | + | return binary.BigEndian.Uint64(b[1:9]), b[9] == 1, ports.Frame{Type: ports.MsgType(b[10]), Payload: append([]byte(nil), b[headerLen:]...)}, outputSkipThrough, true |
| 907 | } | 980 | } | ||
M internal/adapters/dgram/transport_test.go +72 -13
| @@ -389,7 +389,7 @@ func TestAuthenticatedPeerChangeUpdatesPeerAndEmitsConnected(t *testing.T) { | |||
| 389 | 389 | t.Fatalf("unauthenticated packet changed peer to %v", a.Peer()) | |
| 390 | 390 | } | |
| 391 | 391 | c, _ := pdgram.NewCodec(key()) | |
| 392 | - | rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing}) | |
| 392 | + | rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing}, 0) | |
| 393 | 393 | frags, _ := pdgram.FragmentPayload(9, rec, pdgram.DefaultMTU) | |
| 394 | 394 | raw, _ := pdgram.MarshalFragment(frags[0]) | |
| 395 | 395 | aPC.in <- packet{c.Seal(2, 99, raw, nil), testAddr("evil")} | |
| @@ -511,11 +511,11 @@ func TestServerWithoutRebindDoesNotHopPorts(t *testing.T) { | |||
| 511 | 511 | ||
| 512 | 512 | func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) { | |
| 513 | 513 | tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)} | |
| 514 | - | _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")}) | |
| 514 | + | _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")}, 0) | |
| 515 | 515 | if !ack || !queued { | |
| 516 | 516 | t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued) | |
| 517 | 517 | } | |
| 518 | - | _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}) | |
| 518 | + | _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0) | |
| 519 | 519 | if !ack || !queued { | |
| 520 | 520 | t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued) | |
| 521 | 521 | } | |
| @@ -524,6 +524,32 @@ func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) { | |||
| 524 | 524 | } | |
| 525 | 525 | } | |
| 526 | 526 | ||
| 527 | + | func TestDataRecordWireEncodingOutputSkip(t *testing.T) { | |
| 528 | + | got := encodeData(5, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("x")}, 3) | |
| 529 | + | want := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 0, 0, 0, 0, 0, 0, 0, 3, 'x'} | |
| 530 | + | if !bytes.Equal(got, want) { | |
| 531 | + | t.Fatalf("encoded data=%v, want %v", got, want) | |
| 532 | + | } | |
| 533 | + | seq, reliable, frame, skip, ok := decodeData(got) | |
| 534 | + | if !ok || seq != 5 || !reliable || frame.Type != ports.MsgOutput || string(frame.Payload) != "x" || skip != 3 { | |
| 535 | + | t.Fatalf("decoded seq=%d reliable=%v frame=%+v skip=%d ok=%v", seq, reliable, frame, skip, ok) | |
| 536 | + | } | |
| 537 | + | } | |
| 538 | + | ||
| 539 | + | func TestDataRecordWireDecodeRejectsTruncatedOutputSkip(t *testing.T) { | |
| 540 | + | truncated := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 0} | |
| 541 | + | if _, _, _, _, ok := decodeData(truncated); ok { | |
| 542 | + | t.Fatal("decodeData accepted truncated output skip header") | |
| 543 | + | } | |
| 544 | + | } | |
| 545 | + | ||
| 546 | + | func TestDataRecordWireDecodeRejectsUnknownFlags(t *testing.T) { | |
| 547 | + | withTrailingGarbage := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 0x80, 'x'} | |
| 548 | + | if _, _, _, _, ok := decodeData(withTrailingGarbage); ok { | |
| 549 | + | t.Fatal("decodeData accepted unknown data-record flags with trailing garbage") | |
| 550 | + | } | |
| 551 | + | } | |
| 552 | + | ||
| 527 | 553 | func TestOutputRetransmitsUntilAck(t *testing.T) { | |
| 528 | 554 | aPC, bPC := newPair() | |
| 529 | 555 | var dropped atomic.Bool | |
| @@ -551,7 +577,7 @@ func TestOutputRetransmitsUntilAck(t *testing.T) { | |||
| 551 | 577 | } | |
| 552 | 578 | } | |
| 553 | 579 | ||
| 554 | - | func TestReliableDeliveryPreservesSequenceOrder(t *testing.T) { | |
| 580 | + | func TestReliableDeliveryPreservesInputSequenceOrder(t *testing.T) { | |
| 555 | 581 | aPC, bPC := newPair() | |
| 556 | 582 | a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2) | |
| 557 | 583 | b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1) | |
| @@ -559,18 +585,51 @@ func TestReliableDeliveryPreservesSequenceOrder(t *testing.T) { | |||
| 559 | 585 | defer func() { _ = b.Close() }() | |
| 560 | 586 | ||
| 561 | 587 | for i := range 100 { | |
| 562 | - | if err := a.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}); err != nil { | |
| 588 | + | if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte{byte(i)}}); err != nil { | |
| 563 | 589 | t.Fatal(err) | |
| 564 | 590 | } | |
| 565 | 591 | } | |
| 566 | 592 | for i := range 100 { | |
| 567 | 593 | got := recvWithin(t, b, time.Second) | |
| 568 | - | if got.Type != ports.MsgOutput || len(got.Payload) != 1 || got.Payload[0] != byte(i) { | |
| 594 | + | if got.Type != ports.MsgInput || len(got.Payload) != 1 || got.Payload[0] != byte(i) { | |
| 569 | 595 | t.Fatalf("frame %d delivered out of order: %+v", i, got) | |
| 570 | 596 | } | |
| 571 | 597 | } | |
| 572 | 598 | } | |
| 573 | 599 | ||
| 600 | + | func TestOutputSupersessionSkipsDroppedOlderOutput(t *testing.T) { | |
| 601 | + | aPC, bPC := newPair() | |
| 602 | + | var sendAttempts atomic.Int32 | |
| 603 | + | aPC.drop = func(_ []byte, addr net.Addr) bool { | |
| 604 | + | return addr.String() == "b" && sendAttempts.Add(1) <= 2 | |
| 605 | + | } | |
| 606 | + | a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2) | |
| 607 | + | b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1) | |
| 608 | + | defer func() { _ = a.Close() }() | |
| 609 | + | defer func() { _ = b.Close() }() | |
| 610 | + | ||
| 611 | + | for _, payload := range []string{"state1", "state2", "state3"} { | |
| 612 | + | if err := a.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte(payload)}); err != nil { | |
| 613 | + | t.Fatal(err) | |
| 614 | + | } | |
| 615 | + | } | |
| 616 | + | ||
| 617 | + | a.mu.Lock() | |
| 618 | + | pending := len(a.pending) | |
| 619 | + | a.mu.Unlock() | |
| 620 | + | if pending > 1 { | |
| 621 | + | t.Fatalf("pending outputs=%d, want collapsed to newest", pending) | |
| 622 | + | } | |
| 623 | + | ||
| 624 | + | got := recvWithin(t, b, time.Second) | |
| 625 | + | if got.Type != ports.MsgOutput || string(got.Payload) != "state3" { | |
| 626 | + | t.Fatalf("got=%+v, want newest output after skipped predecessors", got) | |
| 627 | + | } | |
| 628 | + | if sendAttempts.Load() < 3 { | |
| 629 | + | t.Fatalf("send attempts=%d, want first two output sends dropped before newest arrived", sendAttempts.Load()) | |
| 630 | + | } | |
| 631 | + | } | |
| 632 | + | ||
| 574 | 633 | func TestAckProcessingContinuesWhenConsumerBackpressured(t *testing.T) { | |
| 575 | 634 | aPC, bPC := newPair() | |
| 576 | 635 | a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2) | |
| @@ -719,7 +778,7 @@ func TestCumulativeAckReleasesAllEarlierPendingFrames(t *testing.T) { | |||
| 719 | 778 | func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) { | |
| 720 | 779 | tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)} | |
| 721 | 780 | ||
| 722 | - | ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")}) | |
| 781 | + | ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")}, 0) | |
| 723 | 782 | if !ack || !queued { | |
| 724 | 783 | t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued) | |
| 725 | 784 | } | |
| @@ -727,7 +786,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) { | |||
| 727 | 786 | t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want) | |
| 728 | 787 | } | |
| 729 | 788 | ||
| 730 | - | ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")}) | |
| 789 | + | ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")}, 0) | |
| 731 | 790 | if !ack || !queued { | |
| 732 | 791 | t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued) | |
| 733 | 792 | } | |
| @@ -735,7 +794,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) { | |||
| 735 | 794 | t.Fatalf("first contiguous ack=%d, want %d", got, want) | |
| 736 | 795 | } | |
| 737 | 796 | ||
| 738 | - | ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")}) | |
| 797 | + | ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")}, 0) | |
| 739 | 798 | if !ack || !queued { | |
| 740 | 799 | t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued) | |
| 741 | 800 | } | |
| @@ -966,12 +1025,12 @@ func TestReliableRecvBufferBoundedForFarFutureSequences(t *testing.T) { | |||
| 966 | 1025 | tr := &Transport{nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame), maxRecvBuffer: maxRecvBuffer, done: make(chan struct{})} | |
| 967 | 1026 | tr.deliverCond = sync.NewCond(&tr.deliverMu) | |
| 968 | 1027 | for i := range maxRecvBuffer + 100 { | |
| 969 | - | tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}) | |
| 1028 | + | tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}, 0) | |
| 970 | 1029 | } | |
| 971 | 1030 | if got := len(tr.recvBuf); got != maxRecvBuffer { | |
| 972 | 1031 | t.Fatalf("recvBuf len=%d, want %d", got, maxRecvBuffer) | |
| 973 | 1032 | } | |
| 974 | - | tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}) | |
| 1033 | + | tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0) | |
| 975 | 1034 | if _, ok := tr.recvBuf[1]; !ok { | |
| 976 | 1035 | t.Fatalf("next expected sequence was dropped when far-future buffer was full") | |
| 977 | 1036 | } | |
| @@ -994,14 +1053,14 @@ func TestReliableFullRecvBufferDoesNotAckDroppedFarFutureFrame(t *testing.T) { | |||
| 994 | 1053 | for i := range maxRecvBuffer { | |
| 995 | 1054 | tr.recvBuf[uint64(1000+i)] = ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}} | |
| 996 | 1055 | } | |
| 997 | - | tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")})) | |
| 1056 | + | tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")}, 0)) | |
| 998 | 1057 | select { | |
| 999 | 1058 | case pkt := <-bPC.in: | |
| 1000 | 1059 | t.Fatalf("unexpected ACK packet for dropped frame: %x", pkt.b) | |
| 1001 | 1060 | case <-time.After(25 * time.Millisecond): | |
| 1002 | 1061 | } | |
| 1003 | 1062 | ||
| 1004 | - | tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})) | |
| 1063 | + | tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0)) | |
| 1005 | 1064 | select { | |
| 1006 | 1065 | case <-bPC.in: | |
| 1007 | 1066 | case <-time.After(time.Second): | |
| @@ -389,7 +389,7 @@ func TestAuthenticatedPeerChangeUpdatesPeerAndEmitsConnected(t *testing.T) { | |||||
| 389 | t.Fatalf("unauthenticated packet changed peer to %v", a.Peer()) | 389 | t.Fatalf("unauthenticated packet changed peer to %v", a.Peer()) | ||
| 390 | } | 390 | } | ||
| 391 | c, _ := pdgram.NewCodec(key()) | 391 | c, _ := pdgram.NewCodec(key()) | ||
| 392 | - | rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing}) | 392 | + | rec := encodeData(7, true, ports.Frame{Type: ports.MsgPing}, 0) |
| 393 | frags, _ := pdgram.FragmentPayload(9, rec, pdgram.DefaultMTU) | 393 | frags, _ := pdgram.FragmentPayload(9, rec, pdgram.DefaultMTU) | ||
| 394 | raw, _ := pdgram.MarshalFragment(frags[0]) | 394 | raw, _ := pdgram.MarshalFragment(frags[0]) | ||
| 395 | aPC.in <- packet{c.Seal(2, 99, raw, nil), testAddr("evil")} | 395 | aPC.in <- packet{c.Seal(2, 99, raw, nil), testAddr("evil")} | ||
| @@ -511,11 +511,11 @@ func TestServerWithoutRebindDoesNotHopPorts(t *testing.T) { | |||||
| 511 | 511 | ||||
| 512 | func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) { | 512 | func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) { | ||
| 513 | tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)} | 513 | tr := &Transport{maxRecvBuffer: 1, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)} | ||
| 514 | - | _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")}) | 514 | + | _, ack, queued := tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("future")}, 0) |
| 515 | if !ack || !queued { | 515 | if !ack || !queued { | ||
| 516 | t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued) | 516 | t.Fatalf("future enqueue ack=%v queued=%v, want true/true", ack, queued) | ||
| 517 | } | 517 | } | ||
| 518 | - | _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}) | 518 | + | _, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0) |
| 519 | if !ack || !queued { | 519 | if !ack || !queued { | ||
| 520 | t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued) | 520 | t.Fatalf("next enqueue ack=%v queued=%v, want true/true", ack, queued) | ||
| 521 | } | 521 | } | ||
| @@ -524,6 +524,32 @@ func TestReliableReceiveBufferDoesNotDropAckedFutureFrame(t *testing.T) { | |||||
| 524 | } | 524 | } | ||
| 525 | } | 525 | } | ||
| 526 | 526 | ||||
| 527 | + | func TestDataRecordWireEncodingOutputSkip(t *testing.T) { | |||
| 528 | + | got := encodeData(5, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("x")}, 3) | |||
| 529 | + | want := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 0, 0, 0, 0, 0, 0, 0, 3, 'x'} | |||
| 530 | + | if !bytes.Equal(got, want) { | |||
| 531 | + | t.Fatalf("encoded data=%v, want %v", got, want) | |||
| 532 | + | } | |||
| 533 | + | seq, reliable, frame, skip, ok := decodeData(got) | |||
| 534 | + | if !ok || seq != 5 || !reliable || frame.Type != ports.MsgOutput || string(frame.Payload) != "x" || skip != 3 { | |||
| 535 | + | t.Fatalf("decoded seq=%d reliable=%v frame=%+v skip=%d ok=%v", seq, reliable, frame, skip, ok) | |||
| 536 | + | } | |||
| 537 | + | } | |||
| 538 | + | ||||
| 539 | + | func TestDataRecordWireDecodeRejectsTruncatedOutputSkip(t *testing.T) { | |||
| 540 | + | truncated := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 1, 0} | |||
| 541 | + | if _, _, _, _, ok := decodeData(truncated); ok { | |||
| 542 | + | t.Fatal("decodeData accepted truncated output skip header") | |||
| 543 | + | } | |||
| 544 | + | } | |||
| 545 | + | ||||
| 546 | + | func TestDataRecordWireDecodeRejectsUnknownFlags(t *testing.T) { | |||
| 547 | + | withTrailingGarbage := []byte{recData, 0, 0, 0, 0, 0, 0, 0, 5, 1, byte(ports.MsgOutput), 0x80, 'x'} | |||
| 548 | + | if _, _, _, _, ok := decodeData(withTrailingGarbage); ok { | |||
| 549 | + | t.Fatal("decodeData accepted unknown data-record flags with trailing garbage") | |||
| 550 | + | } | |||
| 551 | + | } | |||
| 552 | + | ||||
| 527 | func TestOutputRetransmitsUntilAck(t *testing.T) { | 553 | func TestOutputRetransmitsUntilAck(t *testing.T) { | ||
| 528 | aPC, bPC := newPair() | 554 | aPC, bPC := newPair() | ||
| 529 | var dropped atomic.Bool | 555 | var dropped atomic.Bool | ||
| @@ -551,7 +577,7 @@ func TestOutputRetransmitsUntilAck(t *testing.T) { | |||||
| 551 | } | 577 | } | ||
| 552 | } | 578 | } | ||
| 553 | 579 | ||||
| 554 | - | func TestReliableDeliveryPreservesSequenceOrder(t *testing.T) { | 580 | + | func TestReliableDeliveryPreservesInputSequenceOrder(t *testing.T) { |
| 555 | aPC, bPC := newPair() | 581 | aPC, bPC := newPair() | ||
| 556 | a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2) | 582 | a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2) | ||
| 557 | b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1) | 583 | b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1) | ||
| @@ -559,18 +585,51 @@ func TestReliableDeliveryPreservesSequenceOrder(t *testing.T) { | |||||
| 559 | defer func() { _ = b.Close() }() | 585 | defer func() { _ = b.Close() }() | ||
| 560 | 586 | ||||
| 561 | for i := range 100 { | 587 | for i := range 100 { | ||
| 562 | - | if err := a.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}); err != nil { | 588 | + | if err := a.Send(ports.Frame{Type: ports.MsgInput, Payload: []byte{byte(i)}}); err != nil { |
| 563 | t.Fatal(err) | 589 | t.Fatal(err) | ||
| 564 | } | 590 | } | ||
| 565 | } | 591 | } | ||
| 566 | for i := range 100 { | 592 | for i := range 100 { | ||
| 567 | got := recvWithin(t, b, time.Second) | 593 | got := recvWithin(t, b, time.Second) | ||
| 568 | - | if got.Type != ports.MsgOutput || len(got.Payload) != 1 || got.Payload[0] != byte(i) { | 594 | + | if got.Type != ports.MsgInput || len(got.Payload) != 1 || got.Payload[0] != byte(i) { |
| 569 | t.Fatalf("frame %d delivered out of order: %+v", i, got) | 595 | t.Fatalf("frame %d delivered out of order: %+v", i, got) | ||
| 570 | } | 596 | } | ||
| 571 | } | 597 | } | ||
| 572 | } | 598 | } | ||
| 573 | 599 | ||||
| 600 | + | func TestOutputSupersessionSkipsDroppedOlderOutput(t *testing.T) { | |||
| 601 | + | aPC, bPC := newPair() | |||
| 602 | + | var sendAttempts atomic.Int32 | |||
| 603 | + | aPC.drop = func(_ []byte, addr net.Addr) bool { | |||
| 604 | + | return addr.String() == "b" && sendAttempts.Add(1) <= 2 | |||
| 605 | + | } | |||
| 606 | + | a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2) | |||
| 607 | + | b, _ := NewTransport(bPC, testAddr("a"), key(), 2, 1) | |||
| 608 | + | defer func() { _ = a.Close() }() | |||
| 609 | + | defer func() { _ = b.Close() }() | |||
| 610 | + | ||||
| 611 | + | for _, payload := range []string{"state1", "state2", "state3"} { | |||
| 612 | + | if err := a.Send(ports.Frame{Type: ports.MsgOutput, Payload: []byte(payload)}); err != nil { | |||
| 613 | + | t.Fatal(err) | |||
| 614 | + | } | |||
| 615 | + | } | |||
| 616 | + | ||||
| 617 | + | a.mu.Lock() | |||
| 618 | + | pending := len(a.pending) | |||
| 619 | + | a.mu.Unlock() | |||
| 620 | + | if pending > 1 { | |||
| 621 | + | t.Fatalf("pending outputs=%d, want collapsed to newest", pending) | |||
| 622 | + | } | |||
| 623 | + | ||||
| 624 | + | got := recvWithin(t, b, time.Second) | |||
| 625 | + | if got.Type != ports.MsgOutput || string(got.Payload) != "state3" { | |||
| 626 | + | t.Fatalf("got=%+v, want newest output after skipped predecessors", got) | |||
| 627 | + | } | |||
| 628 | + | if sendAttempts.Load() < 3 { | |||
| 629 | + | t.Fatalf("send attempts=%d, want first two output sends dropped before newest arrived", sendAttempts.Load()) | |||
| 630 | + | } | |||
| 631 | + | } | |||
| 632 | + | ||||
| 574 | func TestAckProcessingContinuesWhenConsumerBackpressured(t *testing.T) { | 633 | func TestAckProcessingContinuesWhenConsumerBackpressured(t *testing.T) { | ||
| 575 | aPC, bPC := newPair() | 634 | aPC, bPC := newPair() | ||
| 576 | a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2) | 635 | a, _ := NewTransport(aPC, testAddr("b"), key(), 1, 2) | ||
| @@ -719,7 +778,7 @@ func TestCumulativeAckReleasesAllEarlierPendingFrames(t *testing.T) { | |||||
| 719 | func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) { | 778 | func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) { | ||
| 720 | tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)} | 779 | tr := &Transport{maxRecvBuffer: maxRecvBuffer, nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame)} | ||
| 721 | 780 | ||||
| 722 | - | ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")}) | 781 | + | ackSeq, ack, queued := tr.enqueueReliable(3, ports.Frame{Type: ports.MsgOutput, Payload: []byte("third")}, 0) |
| 723 | if !ack || !queued { | 782 | if !ack || !queued { | ||
| 724 | t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued) | 783 | t.Fatalf("out-of-order enqueue ack=%v queued=%v, want true/true", ack, queued) | ||
| 725 | } | 784 | } | ||
| @@ -727,7 +786,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) { | |||||
| 727 | t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want) | 786 | t.Fatalf("out-of-order ack=%d, want highest contiguous %d", got, want) | ||
| 728 | } | 787 | } | ||
| 729 | 788 | ||||
| 730 | - | ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")}) | 789 | + | ackSeq, ack, queued = tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("first")}, 0) |
| 731 | if !ack || !queued { | 790 | if !ack || !queued { | ||
| 732 | t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued) | 791 | t.Fatalf("first enqueue ack=%v queued=%v, want true/true", ack, queued) | ||
| 733 | } | 792 | } | ||
| @@ -735,7 +794,7 @@ func TestReliableReceiverAcksOnlyHighestContiguousSequence(t *testing.T) { | |||||
| 735 | t.Fatalf("first contiguous ack=%d, want %d", got, want) | 794 | t.Fatalf("first contiguous ack=%d, want %d", got, want) | ||
| 736 | } | 795 | } | ||
| 737 | 796 | ||||
| 738 | - | ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")}) | 797 | + | ackSeq, ack, queued = tr.enqueueReliable(2, ports.Frame{Type: ports.MsgOutput, Payload: []byte("second")}, 0) |
| 739 | if !ack || !queued { | 798 | if !ack || !queued { | ||
| 740 | t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued) | 799 | t.Fatalf("gap fill enqueue ack=%v queued=%v, want true/true", ack, queued) | ||
| 741 | } | 800 | } | ||
| @@ -966,12 +1025,12 @@ func TestReliableRecvBufferBoundedForFarFutureSequences(t *testing.T) { | |||||
| 966 | tr := &Transport{nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame), maxRecvBuffer: maxRecvBuffer, done: make(chan struct{})} | 1025 | tr := &Transport{nextRecvSeq: 1, recvBuf: make(map[uint64]ports.Frame), maxRecvBuffer: maxRecvBuffer, done: make(chan struct{})} | ||
| 967 | tr.deliverCond = sync.NewCond(&tr.deliverMu) | 1026 | tr.deliverCond = sync.NewCond(&tr.deliverMu) | ||
| 968 | for i := range maxRecvBuffer + 100 { | 1027 | for i := range maxRecvBuffer + 100 { | ||
| 969 | - | tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}) | 1028 | + | tr.enqueueReliable(uint64(1000+i), ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}}, 0) |
| 970 | } | 1029 | } | ||
| 971 | if got := len(tr.recvBuf); got != maxRecvBuffer { | 1030 | if got := len(tr.recvBuf); got != maxRecvBuffer { | ||
| 972 | t.Fatalf("recvBuf len=%d, want %d", got, maxRecvBuffer) | 1031 | t.Fatalf("recvBuf len=%d, want %d", got, maxRecvBuffer) | ||
| 973 | } | 1032 | } | ||
| 974 | - | tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}) | 1033 | + | tr.enqueueReliable(1, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0) |
| 975 | if _, ok := tr.recvBuf[1]; !ok { | 1034 | if _, ok := tr.recvBuf[1]; !ok { | ||
| 976 | t.Fatalf("next expected sequence was dropped when far-future buffer was full") | 1035 | t.Fatalf("next expected sequence was dropped when far-future buffer was full") | ||
| 977 | } | 1036 | } | ||
| @@ -994,14 +1053,14 @@ func TestReliableFullRecvBufferDoesNotAckDroppedFarFutureFrame(t *testing.T) { | |||||
| 994 | for i := range maxRecvBuffer { | 1053 | for i := range maxRecvBuffer { | ||
| 995 | tr.recvBuf[uint64(1000+i)] = ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}} | 1054 | tr.recvBuf[uint64(1000+i)] = ports.Frame{Type: ports.MsgOutput, Payload: []byte{byte(i)}} | ||
| 996 | } | 1055 | } | ||
| 997 | - | tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")})) | 1056 | + | tr.handleRecord(encodeData(9999, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("dropped")}, 0)) |
| 998 | select { | 1057 | select { | ||
| 999 | case pkt := <-bPC.in: | 1058 | case pkt := <-bPC.in: | ||
| 1000 | t.Fatalf("unexpected ACK packet for dropped frame: %x", pkt.b) | 1059 | t.Fatalf("unexpected ACK packet for dropped frame: %x", pkt.b) | ||
| 1001 | case <-time.After(25 * time.Millisecond): | 1060 | case <-time.After(25 * time.Millisecond): | ||
| 1002 | } | 1061 | } | ||
| 1003 | 1062 | ||||
| 1004 | - | tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")})) | 1063 | + | tr.handleRecord(encodeData(1, true, ports.Frame{Type: ports.MsgOutput, Payload: []byte("next")}, 0)) |
| 1005 | select { | 1064 | select { | ||
| 1006 | case <-bPC.in: | 1065 | case <-bPC.in: | ||
| 1007 | case <-time.After(time.Second): | 1066 | case <-time.After(time.Second): | ||
M internal/ports/frame.go +1 -1
| @@ -1,7 +1,7 @@ | |||
| 1 | 1 | package ports | |
| 2 | 2 | ||
| 3 | 3 | // ProtocolVersion is the current vev IPC wire protocol version. | |
| 4 | - | const ProtocolVersion uint16 = 10 | |
| 4 | + | const ProtocolVersion uint16 = 11 | |
| 5 | 5 | ||
| 6 | 6 | // MsgType identifies the kind of payload carried by a Frame. | |
| 7 | 7 | type MsgType uint8 | |
| @@ -1,7 +1,7 @@ | |||||
| 1 | package ports | 1 | package ports | ||
| 2 | 2 | ||||
| 3 | // ProtocolVersion is the current vev IPC wire protocol version. | 3 | // ProtocolVersion is the current vev IPC wire protocol version. | ||
| 4 | - | const ProtocolVersion uint16 = 10 | 4 | + | const ProtocolVersion uint16 = 11 |
| 5 | 5 | ||||
| 6 | // MsgType identifies the kind of payload carried by a Frame. | 6 | // MsgType identifies the kind of payload carried by a Frame. | ||
| 7 | type MsgType uint8 | 7 | type MsgType uint8 | ||
M internal/ports/frame_test.go +2 -2
| @@ -3,8 +3,8 @@ package ports | |||
| 3 | 3 | import "testing" | |
| 4 | 4 | ||
| 5 | 5 | func TestProtocolVersion(t *testing.T) { | |
| 6 | - | if ProtocolVersion != 10 { | |
| 7 | - | t.Fatalf("ProtocolVersion = %d, want 10", ProtocolVersion) | |
| 6 | + | if ProtocolVersion != 11 { | |
| 7 | + | t.Fatalf("ProtocolVersion = %d, want 11", ProtocolVersion) | |
| 8 | 8 | } | |
| 9 | 9 | } | |
| 10 | 10 | ||
| @@ -3,8 +3,8 @@ package ports | |||||
| 3 | import "testing" | 3 | import "testing" | ||
| 4 | 4 | ||||
| 5 | func TestProtocolVersion(t *testing.T) { | 5 | func TestProtocolVersion(t *testing.T) { | ||
| 6 | - | if ProtocolVersion != 10 { | 6 | + | if ProtocolVersion != 11 { |
| 7 | - | t.Fatalf("ProtocolVersion = %d, want 10", ProtocolVersion) | 7 | + | t.Fatalf("ProtocolVersion = %d, want 11", ProtocolVersion) |
| 8 | } | 8 | } | ||
| 9 | } | 9 | } | ||
| 10 | 10 | ||||