v0.25.0 netacl and fixes
This commit is contained in:
1 parent
9e86d7999d
commit
4af072f4e5
30 files changed
+918
-228
No files matched your search
@@ -219,8 +219,7 @@ func (t *TCPChainSink) Start(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop terminates the forwarding loop. Worst-case latency: one write timeout
|
||||
// plus one backoff wait (both interruptible or bounded).
|
||||
// Stop delivers what is queued within sink.FlushBound, then ends the loop
|
||||
func (t *TCPChainSink) Stop() {
|
||||
t.logger.Info("msg", "Stopping TCP chain sink",
|
||||
"component", "tcp_chain_sink",
|
||||
@@ -273,49 +272,53 @@ func (t *TCPChainSink) runLoop(ctx context.Context) {
|
||||
defer t.wg.Done()
|
||||
defer t.closeConn()
|
||||
|
||||
// Fold done into the context, so a connect or exchange in flight ends on Stop
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
// Stop leaves sink.FlushBound to deliver the queue, so a finite input
|
||||
// arrives whole; a connect, exchange or link still open then ends
|
||||
ctx, cancel := sink.FlushContext(ctx, t.done, t.writeTimeout)
|
||||
defer cancel()
|
||||
go func() {
|
||||
select {
|
||||
case <-t.done:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-t.done:
|
||||
for len(t.input) > 0 {
|
||||
if !t.forward(ctx, <-t.input) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
case event, ok := <-t.input:
|
||||
if !ok {
|
||||
case event := <-t.input:
|
||||
if !t.forward(ctx, event) {
|
||||
return
|
||||
}
|
||||
entry, synthesized := chain.EntryFromEvent(event, t.node, t.id)
|
||||
if synthesized {
|
||||
t.synthesized.Add(1)
|
||||
}
|
||||
line, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
// Non-transient: drop
|
||||
t.logger.Error("msg", "Failed to marshal chain entry",
|
||||
"component", "tcp_chain_sink",
|
||||
"error", err)
|
||||
continue
|
||||
}
|
||||
if !t.deliver(ctx, append(line, '\n')) {
|
||||
return // shutdown during retry
|
||||
}
|
||||
t.totalProcessed.Add(1)
|
||||
t.lastProcessed.Store(time.Now())
|
||||
t.proxy.UpdateActivity(t.session.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// forward delivers one event; false once shutdown ends the delivery
|
||||
func (t *TCPChainSink) forward(ctx context.Context, event core.TransportEvent) bool {
|
||||
entry, synthesized := chain.EntryFromEvent(event, t.node, t.id)
|
||||
if synthesized {
|
||||
t.synthesized.Add(1)
|
||||
}
|
||||
line, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
// Non-transient: drop
|
||||
t.logger.Error("msg", "Failed to marshal chain entry",
|
||||
"component", "tcp_chain_sink",
|
||||
"error", err)
|
||||
return true
|
||||
}
|
||||
if !t.deliver(ctx, append(line, '\n')) {
|
||||
return false
|
||||
}
|
||||
t.totalProcessed.Add(1)
|
||||
t.lastProcessed.Store(time.Now())
|
||||
t.proxy.UpdateActivity(t.session.ID)
|
||||
return true
|
||||
}
|
||||
|
||||
// deliver writes one line, holding it across reconnects until sent or shutdown.
|
||||
// Backpressure during outage propagates to the pipeline dispatch drop counter.
|
||||
func (t *TCPChainSink) deliver(ctx context.Context, line []byte) bool {
|
||||
@@ -399,11 +402,14 @@ func (t *TCPChainSink) connect(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
// A source never writes once a link is up: a line is a refusal this sink
|
||||
// could not otherwise see, EOF a close. Stop writing into either.
|
||||
// could not otherwise see, EOF a close. Stop writing into either. The end
|
||||
// of ctx closes the link too, cutting short a write in flight.
|
||||
stop := context.AfterFunc(ctx, func() { conn.Close() })
|
||||
t.wg.Add(1)
|
||||
go func() {
|
||||
defer t.wg.Done()
|
||||
err := authz.AwaitClose(r)
|
||||
stop()
|
||||
conn.Close()
|
||||
if errors.Is(err, authz.ErrRefused) {
|
||||
t.logger.Warn("msg", "Chain link refused",
|
||||
@@ -450,7 +456,5 @@ func (t *TCPChainSink) waitBackoff(ctx context.Context, failures int) bool {
|
||||
return true
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
case <-t.done:
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package tcpchain
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -66,3 +68,72 @@ func TestRefusingSourceIsRetriedWithBackoff(t *testing.T) {
|
||||
t.Fatalf("%d connections in 1.5 s against a refusing source", n)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop delivers what is queued, so the end of a finite input reaches the
|
||||
// source whole, and returns at the flush bound when the source stops reading.
|
||||
func TestStopDeliversTheQueueWithinTheBound(t *testing.T) {
|
||||
const n = 2000 // of 8 KiB: more than a stalled source's socket buffers hold
|
||||
padding := strings.Repeat("x", 8192)
|
||||
manager := session.NewManager(time.Hour)
|
||||
t.Cleanup(manager.Stop)
|
||||
// stop queues n entries, starts and stops a sink, and returns how long
|
||||
// Stop took and how many entries a reading source received
|
||||
stop := func(reading bool) (time.Duration, int) {
|
||||
ln, err := net.Listen("tcp4", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { ln.Close() })
|
||||
lines := make(chan int, 1)
|
||||
go func() {
|
||||
conn, err := ln.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
if !reading {
|
||||
<-t.Context().Done()
|
||||
return
|
||||
}
|
||||
count, scanner := -1, bufio.NewScanner(conn) // -1: the hello
|
||||
scanner.Buffer(nil, 64*1024)
|
||||
for scanner.Scan() {
|
||||
count++
|
||||
}
|
||||
lines <- count
|
||||
}()
|
||||
_, port, _ := net.SplitHostPort(ln.Addr().String())
|
||||
p, _ := strconv.ParseInt(port, 10, 64)
|
||||
created, err := NewTCPChainSinkPlugin("fwd", map[string]any{
|
||||
"host": "127.0.0.1", "port": p, "buffer_size": int64(n), // writes time out at 5 s
|
||||
}, log.NewLogger(), session.NewProxy(manager, "fwd"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sink := created.(*TCPChainSink)
|
||||
for i := range n {
|
||||
sink.Input() <- core.TransportEvent{Time: time.Now(), Payload: fmt.Appendf(nil, "entry %d %s", i, padding)}
|
||||
}
|
||||
if err := sink.Start(t.Context()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
start := time.Now()
|
||||
sink.Stop()
|
||||
d := time.Since(start)
|
||||
if !reading {
|
||||
return d, 0
|
||||
}
|
||||
select {
|
||||
case got := <-lines:
|
||||
return d, got
|
||||
case <-time.After(5 * time.Second):
|
||||
return d, 0
|
||||
}
|
||||
}
|
||||
if _, got := stop(true); got != n {
|
||||
t.Fatalf("the source received %d of %d entries", got, n)
|
||||
}
|
||||
if d, _ := stop(false); d > 3500*time.Millisecond {
|
||||
t.Fatalf("Stop took %v past a 2 s flush bound", d)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user