@@ -2,6 +2,7 @@ package vpn
2
2
3
3
import (
4
4
"context"
5
+ "io"
5
6
"net"
6
7
"net/netip"
7
8
"net/url"
@@ -485,9 +486,7 @@ func TestTunnel_sendAgentUpdate(t *testing.T) {
485
486
486
487
//nolint:revive // t takes precedence
487
488
func setupTunnel (t * testing.T , ctx context.Context , client * fakeClient , mClock quartz.Clock ) (* Tunnel , * speaker [* ManagerMessage , * TunnelMessage , TunnelMessage ]) {
488
- mp , tp := net .Pipe ()
489
- t .Cleanup (func () { _ = mp .Close () })
490
- t .Cleanup (func () { _ = tp .Close () })
489
+ mp , tp := newBidiPipe (t )
491
490
logger := testutil .Logger (t )
492
491
493
492
var tun * Tunnel
@@ -510,3 +509,55 @@ func setupTunnel(t *testing.T, ctx context.Context, client *fakeClient, mClock q
510
509
mgr .start ()
511
510
return tun , mgr
512
511
}
512
+
513
+ // In practice, the underlying connection is a pair of dup'd pipes. The tunnel
514
+ // closing the connection (the dup'd FD) should not cause the manager to read EOF.
515
+ // By replicating the behavior of these FDs, we avoid a race where the manager
516
+ // reads EOF before it reads the stop response from the tunnel.
517
+ type bidiPipe struct {
518
+ rw io.ReadWriteCloser
519
+ * refcnt
520
+ }
521
+
522
+ type refcnt struct {
523
+ count int
524
+ mu sync.Mutex
525
+ closeFunc func () error
526
+ }
527
+
528
+ // Not idempotent, but it's only called once per bidiPipe.
529
+ func (b * bidiPipe ) Close () error {
530
+ b .refcnt .mu .Lock ()
531
+ defer b .refcnt .mu .Unlock ()
532
+
533
+ b .refcnt .count --
534
+ if b .refcnt .count == 0 {
535
+ _ = b .refcnt .closeFunc ()
536
+ }
537
+ return nil
538
+ }
539
+
540
+ func (b * bidiPipe ) Read (p []byte ) (n int , err error ) {
541
+ return b .rw .Read (p )
542
+ }
543
+
544
+ func (b * bidiPipe ) Write (p []byte ) (n int , err error ) {
545
+ return b .rw .Write (p )
546
+ }
547
+
548
+ func newBidiPipe (t * testing.T ) (io.ReadWriteCloser , io.ReadWriteCloser ) {
549
+ mp , tp := net .Pipe ()
550
+ t .Cleanup (func () { _ = mp .Close () })
551
+ t .Cleanup (func () { _ = tp .Close () })
552
+
553
+ refcnt := & refcnt {
554
+ count : 2 ,
555
+ closeFunc : func () error {
556
+ _ = tp .Close ()
557
+ _ = mp .Close ()
558
+ return nil
559
+ },
560
+ }
561
+
562
+ return & bidiPipe {rw : tp , refcnt : refcnt }, & bidiPipe {rw : mp , refcnt : refcnt }
563
+ }
0 commit comments