mirror of
				https://github.com/tailscale/tailscale.git
				synced 2025-10-31 08:11:32 +01:00 
			
		
		
		
	This is just #cleanup to resolve a TODO Also add a package doc. Signed-off-by: Maisem Ali <maisem@tailscale.com>
		
			
				
	
	
		
			245 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			245 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright (c) Tailscale Inc & AUTHORS
 | |
| // SPDX-License-Identifier: BSD-3-Clause
 | |
| 
 | |
| package memnet
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"log"
 | |
| 	"net"
 | |
| 	"os"
 | |
| 	"sync"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| const debugPipe = false
 | |
| 
 | |
| // Pipe implements an in-memory FIFO with timeouts.
 | |
| type Pipe struct {
 | |
| 	name   string
 | |
| 	maxBuf int
 | |
| 	mu     sync.Mutex
 | |
| 	cnd    *sync.Cond
 | |
| 
 | |
| 	blocked          bool
 | |
| 	closed           bool
 | |
| 	buf              bytes.Buffer
 | |
| 	readTimeout      time.Time
 | |
| 	writeTimeout     time.Time
 | |
| 	cancelReadTimer  func()
 | |
| 	cancelWriteTimer func()
 | |
| }
 | |
| 
 | |
| // NewPipe creates a Pipe with a buffer size fixed at maxBuf.
 | |
| func NewPipe(name string, maxBuf int) *Pipe {
 | |
| 	p := &Pipe{
 | |
| 		name:   name,
 | |
| 		maxBuf: maxBuf,
 | |
| 	}
 | |
| 	p.cnd = sync.NewCond(&p.mu)
 | |
| 	return p
 | |
| }
 | |
| 
 | |
| // readOrBlock attempts to read from the buffer, if the buffer is empty and
 | |
| // the connection hasn't been closed it will block until there is a change.
 | |
| func (p *Pipe) readOrBlock(b []byte) (int, error) {
 | |
| 	p.mu.Lock()
 | |
| 	defer p.mu.Unlock()
 | |
| 	if !p.readTimeout.IsZero() && !time.Now().Before(p.readTimeout) {
 | |
| 		return 0, os.ErrDeadlineExceeded
 | |
| 	}
 | |
| 	if p.blocked {
 | |
| 		p.cnd.Wait()
 | |
| 		return 0, nil
 | |
| 	}
 | |
| 
 | |
| 	n, err := p.buf.Read(b)
 | |
| 	// err will either be nil or io.EOF.
 | |
| 	if err == io.EOF {
 | |
| 		if p.closed {
 | |
| 			return n, err
 | |
| 		}
 | |
| 		// Wait for something to change.
 | |
| 		p.cnd.Wait()
 | |
| 	}
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // Read implements io.Reader.
 | |
| // Once the buffer is drained (i.e. after Close), subsequent calls will
 | |
| // return io.EOF.
 | |
| func (p *Pipe) Read(b []byte) (n int, err error) {
 | |
| 	if debugPipe {
 | |
| 		orig := b
 | |
| 		defer func() {
 | |
| 			log.Printf("Pipe(%q).Read(%q) n=%d, err=%v", p.name, string(orig[:n]), n, err)
 | |
| 		}()
 | |
| 	}
 | |
| 	for n == 0 {
 | |
| 		n2, err := p.readOrBlock(b)
 | |
| 		if err != nil {
 | |
| 			return n2, err
 | |
| 		}
 | |
| 		n += n2
 | |
| 	}
 | |
| 	p.cnd.Signal()
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // writeOrBlock attempts to write to the buffer, if the buffer is full it will
 | |
| // block until there is a change.
 | |
| func (p *Pipe) writeOrBlock(b []byte) (int, error) {
 | |
| 	p.mu.Lock()
 | |
| 	defer p.mu.Unlock()
 | |
| 	if p.closed {
 | |
| 		return 0, net.ErrClosed
 | |
| 	}
 | |
| 	if !p.writeTimeout.IsZero() && !time.Now().Before(p.writeTimeout) {
 | |
| 		return 0, os.ErrDeadlineExceeded
 | |
| 	}
 | |
| 	if p.blocked {
 | |
| 		p.cnd.Wait()
 | |
| 		return 0, nil
 | |
| 	}
 | |
| 
 | |
| 	// Optimistically we want to write the entire slice.
 | |
| 	n := len(b)
 | |
| 	if limit := p.maxBuf - p.buf.Len(); limit < n {
 | |
| 		// However, we don't have enough capacity to write everything.
 | |
| 		n = limit
 | |
| 	}
 | |
| 	if n == 0 {
 | |
| 		// Wait for something to change.
 | |
| 		p.cnd.Wait()
 | |
| 		return 0, nil
 | |
| 	}
 | |
| 
 | |
| 	p.buf.Write(b[:n])
 | |
| 	p.cnd.Signal()
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // Write implements io.Writer.
 | |
| func (p *Pipe) Write(b []byte) (n int, err error) {
 | |
| 	if debugPipe {
 | |
| 		orig := b
 | |
| 		defer func() {
 | |
| 			log.Printf("Pipe(%q).Write(%q) n=%d, err=%v", p.name, string(orig), n, err)
 | |
| 		}()
 | |
| 	}
 | |
| 	for len(b) > 0 {
 | |
| 		n2, err := p.writeOrBlock(b)
 | |
| 		if err != nil {
 | |
| 			return n + n2, err
 | |
| 		}
 | |
| 		n += n2
 | |
| 		b = b[n2:]
 | |
| 	}
 | |
| 	return n, nil
 | |
| }
 | |
| 
 | |
| // Close closes the pipe.
 | |
| func (p *Pipe) Close() error {
 | |
| 	p.mu.Lock()
 | |
| 	defer p.mu.Unlock()
 | |
| 	p.closed = true
 | |
| 	p.blocked = false
 | |
| 	if p.cancelWriteTimer != nil {
 | |
| 		p.cancelWriteTimer()
 | |
| 		p.cancelWriteTimer = nil
 | |
| 	}
 | |
| 	if p.cancelReadTimer != nil {
 | |
| 		p.cancelReadTimer()
 | |
| 		p.cancelReadTimer = nil
 | |
| 	}
 | |
| 	p.cnd.Broadcast()
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (p *Pipe) deadlineTimer(t time.Time) func() {
 | |
| 	if t.IsZero() {
 | |
| 		return nil
 | |
| 	}
 | |
| 	if t.Before(time.Now()) {
 | |
| 		p.cnd.Broadcast()
 | |
| 		return nil
 | |
| 	}
 | |
| 	ctx, cancel := context.WithDeadline(context.Background(), t)
 | |
| 	go func() {
 | |
| 		<-ctx.Done()
 | |
| 		if ctx.Err() == context.DeadlineExceeded {
 | |
| 			p.cnd.Broadcast()
 | |
| 		}
 | |
| 	}()
 | |
| 	return cancel
 | |
| }
 | |
| 
 | |
| // SetReadDeadline sets the deadline for future Read calls.
 | |
| func (p *Pipe) SetReadDeadline(t time.Time) error {
 | |
| 	p.mu.Lock()
 | |
| 	defer p.mu.Unlock()
 | |
| 	p.readTimeout = t
 | |
| 	// If we already have a deadline, cancel it and create a new one.
 | |
| 	if p.cancelReadTimer != nil {
 | |
| 		p.cancelReadTimer()
 | |
| 		p.cancelReadTimer = nil
 | |
| 	}
 | |
| 	p.cancelReadTimer = p.deadlineTimer(t)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // SetWriteDeadline sets the deadline for future Write calls.
 | |
| func (p *Pipe) SetWriteDeadline(t time.Time) error {
 | |
| 	p.mu.Lock()
 | |
| 	defer p.mu.Unlock()
 | |
| 	p.writeTimeout = t
 | |
| 	// If we already have a deadline, cancel it and create a new one.
 | |
| 	if p.cancelWriteTimer != nil {
 | |
| 		p.cancelWriteTimer()
 | |
| 		p.cancelWriteTimer = nil
 | |
| 	}
 | |
| 	p.cancelWriteTimer = p.deadlineTimer(t)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Block will cause all calls to Read and Write to block until they either
 | |
| // timeout, are unblocked or the pipe is closed.
 | |
| func (p *Pipe) Block() error {
 | |
| 	p.mu.Lock()
 | |
| 	defer p.mu.Unlock()
 | |
| 	closed := p.closed
 | |
| 	blocked := p.blocked
 | |
| 	p.blocked = true
 | |
| 
 | |
| 	if closed {
 | |
| 		return fmt.Errorf("memnet.Pipe(%q).Block: closed", p.name)
 | |
| 	}
 | |
| 	if blocked {
 | |
| 		return fmt.Errorf("memnet.Pipe(%q).Block: already blocked", p.name)
 | |
| 	}
 | |
| 	p.cnd.Broadcast()
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Unblock will cause all blocked Read/Write calls to continue execution.
 | |
| func (p *Pipe) Unblock() error {
 | |
| 	p.mu.Lock()
 | |
| 	defer p.mu.Unlock()
 | |
| 	closed := p.closed
 | |
| 	blocked := p.blocked
 | |
| 	p.blocked = false
 | |
| 
 | |
| 	if closed {
 | |
| 		return fmt.Errorf("memnet.Pipe(%q).Block: closed", p.name)
 | |
| 	}
 | |
| 	if !blocked {
 | |
| 		return fmt.Errorf("memnet.Pipe(%q).Block: already unblocked", p.name)
 | |
| 	}
 | |
| 	p.cnd.Broadcast()
 | |
| 	return nil
 | |
| }
 |