flipcall

package
v0.0.0-...-c27c9a0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 14, 2024 License: Apache-2.0, MIT Imports: 15 Imported by: 6

Documentation

Overview

Package flipcall implements a protocol providing Fast Local Interprocess Procedure Calls between mutually-distrusting processes.

Example
const (
	reqPrefix     = "request "
	respPrefix    = "response "
	count         = 3
	maxMessageLen = len(respPrefix) + 1 // 1 digit
)

pwa, err := NewPacketWindowAllocator()
if err != nil {
	panic(err)
}
defer pwa.Destroy()
pwd, err := pwa.Allocate(PacketWindowLengthForDataCap(uint32(maxMessageLen)))
if err != nil {
	panic(err)
}
var clientEP Endpoint
if err := clientEP.Init(ClientSide, pwd); err != nil {
	panic(err)
}
defer clientEP.Destroy()
var serverEP Endpoint
if err := serverEP.Init(ServerSide, pwd); err != nil {
	panic(err)
}
defer serverEP.Destroy()

var serverRun sync.WaitGroup
serverRun.Add(1)
go func() {
	defer serverRun.Done()
	i := 0
	var buf bytes.Buffer
	// wait for first request
	n, err := serverEP.RecvFirst()
	if err != nil {
		return
	}
	for {
		// read request
		buf.Reset()
		buf.Write(serverEP.Data()[:n])
		fmt.Println(buf.String())
		// write response
		buf.Reset()
		fmt.Fprintf(&buf, "%s%d", respPrefix, i)
		copy(serverEP.Data(), buf.Bytes())
		// send response and wait for next request
		n, err = serverEP.SendRecv(uint32(buf.Len()))
		if err != nil {
			return
		}
		i++
	}
}()
defer func() {
	serverEP.Shutdown()
	serverRun.Wait()
}()

// establish connection as client
if err := clientEP.Connect(); err != nil {
	panic(err)
}
var buf bytes.Buffer
for i := 0; i < count; i++ {
	// write request
	buf.Reset()
	fmt.Fprintf(&buf, "%s%d", reqPrefix, i)
	copy(clientEP.Data(), buf.Bytes())
	// send request and wait for response
	n, err := clientEP.SendRecv(uint32(buf.Len()))
	if err != nil {
		panic(err)
	}
	// read response
	buf.Reset()
	buf.Write(clientEP.Data()[:n])
	fmt.Println(buf.String())
}
Output:

request 0
response 0
request 1
response 1
request 2
response 2

Index

Examples

Constants

View Source
const (
	// PacketHeaderBytes is the size of a flipcall packet header in bytes. The
	// maximum datagram size supported by a flipcall connection is equal to the
	// length of the packet window minus PacketHeaderBytes.
	//
	// PacketHeaderBytes is exported to support its use in constant
	// expressions. Non-constant expressions may prefer to use
	// PacketWindowLengthForDataCap().
	PacketHeaderBytes = 16
)

Packets consist of a 16-byte header followed by an arbitrarily-sized datagram. The header consists of:

  • A 4-byte native-endian connection state.

  • A 4-byte native-endian datagram length in bytes.

  • 8 reserved bytes.

Variables

This section is empty.

Functions

func PacketWindowLengthForDataCap

func PacketWindowLengthForDataCap(dataCap uint32) int

PacketWindowLengthForDataCap returns the minimum packet window size required to accommodate datagrams of the given size in bytes.

Types

type DatagramReader

type DatagramReader struct {
	// contains filtered or unexported fields
}

DatagramReader implements io.Reader by reading a datagram from an Endpoint's packet window. Its use is optional; users that can use Endpoint.Data() more efficiently are advised to do so.

func (*DatagramReader) Init

func (r *DatagramReader) Init(ep *Endpoint, dataLen uint32)

Init must be called on zero-value DatagramReaders before first use.

Preconditions: dataLen is 0, or was returned by a previous call to ep.RecvFirst() or ep.SendRecv().

func (*DatagramReader) Read

func (r *DatagramReader) Read(dst []byte) (int, error)

Read implements io.Reader.Read.

func (*DatagramReader) Reset

func (r *DatagramReader) Reset(dataLen uint32)

Reset causes r to begin reading a new datagram of the given length from the associated Endpoint.

Preconditions: dataLen is 0, or was returned by a previous call to the associated Endpoint's RecvFirst() or SendRecv() methods.

type DatagramWriter

type DatagramWriter struct {
	// contains filtered or unexported fields
}

DatagramWriter implements io.Writer by writing a datagram to an Endpoint's packet window. Its use is optional; users that can use Endpoint.Data() more efficiently are advised to do so.

func (*DatagramWriter) Init

func (w *DatagramWriter) Init(ep *Endpoint)

Init must be called on zero-value DatagramWriters before first use.

func (*DatagramWriter) Len

func (w *DatagramWriter) Len() uint32

Len returns the length of the written datagram.

func (*DatagramWriter) Reset

func (w *DatagramWriter) Reset()

Reset causes w to begin writing a new datagram to the associated Endpoint.

func (*DatagramWriter) Write

func (w *DatagramWriter) Write(src []byte) (int, error)

Write implements io.Writer.Write.

type Endpoint

type Endpoint struct {
	// contains filtered or unexported fields
}

An Endpoint provides the ability to synchronously transfer data and control to a connected peer Endpoint, which may be in another process.

Since the Endpoint control transfer model is synchronous, at any given time one Endpoint "has control" (designated the active Endpoint), and the other is "waiting for control" (designated the inactive Endpoint). Users of the flipcall package designate one Endpoint as the client, which is initially active, and the other as the server, which is initially inactive. See flipcall_example_test.go for usage.

func NewEndpoint

func NewEndpoint(side EndpointSide, pwd PacketWindowDescriptor, opts ...EndpointOption) (*Endpoint, error)

NewEndpoint is a convenience function that returns an initialized Endpoint allocated on the heap.

func (*Endpoint) Connect

func (ep *Endpoint) Connect() error

Connect blocks until the peer Endpoint has called Endpoint.RecvFirst().

Preconditions:

  • ep is a client Endpoint.
  • ep.Connect(), ep.RecvFirst(), ep.SendRecv(), and ep.SendLast() have never been called.

func (*Endpoint) Data

func (ep *Endpoint) Data() (bs []byte)

Data returns the datagram part of ep's packet window as a byte slice.

Note that the packet window is shared with the potentially-untrusted peer Endpoint, which may concurrently mutate the contents of the packet window. Thus:

  • Readers must not assume that two reads of the same byte in Data() will return the same result. In other words, readers should read any given byte in Data() at most once.

  • Writers must not assume that they will read back the same data that they have written. In other words, writers should avoid reading from Data() at all.

func (*Endpoint) DataCap

func (ep *Endpoint) DataCap() uint32

DataCap returns the maximum datagram size supported by ep. Equivalently, DataCap returns len(ep.Data()).

func (*Endpoint) Destroy

func (ep *Endpoint) Destroy()

Destroy releases resources owned by ep. No other Endpoint methods may be called after Destroy.

func (*Endpoint) Init

func (ep *Endpoint) Init(side EndpointSide, pwd PacketWindowDescriptor, opts ...EndpointOption) error

Init must be called on zero-value Endpoints before first use. If it succeeds, ep.Destroy() must be called once the Endpoint is no longer in use.

pwd represents the packet window used to exchange data with the peer Endpoint. FD may differ between Endpoints if they are in different processes, but must represent the same file. The packet window must initially be filled with zero bytes.

func (*Endpoint) NewReader

func (ep *Endpoint) NewReader(dataLen uint32) *DatagramReader

NewReader is a convenience function that returns an initialized DatagramReader allocated on the heap.

Preconditions: dataLen was returned by a previous call to ep.RecvFirst() or ep.SendRecv().

func (*Endpoint) NewWriter

func (ep *Endpoint) NewWriter() *DatagramWriter

NewWriter is a convenience function that returns an initialized DatagramWriter allocated on the heap.

func (*Endpoint) RecvFirst

func (ep *Endpoint) RecvFirst() (uint32, error)

RecvFirst blocks until the peer Endpoint calls Endpoint.SendRecv(), then returns the datagram length specified by that call.

Preconditions:

  • ep is a server Endpoint.
  • ep.SendRecv(), ep.RecvFirst(), and ep.SendLast() have never been called.

func (*Endpoint) SendLast

func (ep *Endpoint) SendLast(dataLen uint32) error

SendLast causes the peer Endpoint's call to Endpoint.SendRecv() or Endpoint.RecvFirst() to return with the given datagram length.

Preconditions:

  • dataLen <= ep.DataCap().
  • No previous call to ep.SendRecv() or ep.RecvFirst() has returned an error.
  • ep.SendLast() has never been called.
  • If ep is a client Endpoint, ep.Connect() has previously been called and returned nil.

func (*Endpoint) SendRecv

func (ep *Endpoint) SendRecv(dataLen uint32) (uint32, error)

SendRecv transfers control to the peer Endpoint, causing its call to Endpoint.SendRecv() or Endpoint.RecvFirst() to return with the given datagram length, then blocks until the peer Endpoint calls Endpoint.SendRecv() or Endpoint.SendLast().

Preconditions:

  • dataLen <= ep.DataCap().
  • No previous call to ep.SendRecv() or ep.RecvFirst() has returned an error.
  • ep.SendLast() has never been called.
  • If ep is a client Endpoint, ep.Connect() has previously been called and returned nil.

func (*Endpoint) SendRecvFast

func (ep *Endpoint) SendRecvFast(dataLen uint32) (uint32, error)

SendRecvFast is equivalent to SendRecv, but may prevent the caller's runtime P from being released, in which case the calling goroutine continues to count against GOMAXPROCS while waiting for the peer Endpoint to return control to the caller.

SendRecvFast is appropriate if the peer Endpoint is expected to consistently return control in a short amount of time (less than ~10ms).

Preconditions: As for SendRecv.

func (*Endpoint) Shutdown

func (ep *Endpoint) Shutdown()

Shutdown causes concurrent and future calls to ep.Connect(), ep.SendRecv(), ep.RecvFirst(), and ep.SendLast(), as well as the same calls in the peer Endpoint, to unblock and return ShutdownErrors. It does not wait for concurrent calls to return. Successive calls to Shutdown have no effect.

Shutdown is the only Endpoint method that may be called concurrently with other methods on the same Endpoint.

type EndpointOption

type EndpointOption interface {
	// contains filtered or unexported methods
}

An EndpointOption configures an Endpoint.

type EndpointSide

type EndpointSide int

EndpointSide indicates which side of a connection an Endpoint belongs to.

const (
	// ClientSide indicates that an Endpoint is a client (initially-active;
	// first method call should be Connect).
	ClientSide EndpointSide = iota

	// ServerSide indicates that an Endpoint is a server (initially-inactive;
	// first method call should be RecvFirst.)
	ServerSide
)

type PacketWindowAllocator

type PacketWindowAllocator struct {
	// contains filtered or unexported fields
}

A PacketWindowAllocator owns a shared memory file, and allocates packet windows from it.

func NewPacketWindowAllocator

func NewPacketWindowAllocator() (*PacketWindowAllocator, error)

NewPacketWindowAllocator is a convenience function that returns an initialized PacketWindowAllocator allocated on the heap.

func (*PacketWindowAllocator) Allocate

func (pwa *PacketWindowAllocator) Allocate(size int) (PacketWindowDescriptor, error)

Allocate allocates a new packet window of at least the given size and returns a PacketWindowDescriptor representing it.

Preconditions: size > 0.

func (*PacketWindowAllocator) Destroy

func (pwa *PacketWindowAllocator) Destroy()

Destroy releases resources owned by pwa. This invalidates file descriptors previously returned by pwa.FD() and pwd.Allocate().

func (*PacketWindowAllocator) FD

func (pwa *PacketWindowAllocator) FD() int

FD represents the file descriptor of the shared memory file backing pwa.

func (*PacketWindowAllocator) Init

func (pwa *PacketWindowAllocator) Init() error

Init must be called on zero-value PacketWindowAllocators before first use. If it succeeds, Destroy() must be called once the PacketWindowAllocator is no longer in use.

type PacketWindowDescriptor

type PacketWindowDescriptor struct {
	// FD is the file descriptor representing the shared memory file.
	FD int

	// Offset is the offset into the shared memory file at which the packet
	// window begins.
	Offset int64

	// Length is the size of the packet window in bytes.
	Length int
}

PacketWindowDescriptor represents a packet window, a range of pages in a shared memory file that is used to exchange packets between partner Endpoints.

type ShutdownError

type ShutdownError struct{}

ShutdownError is returned by most Endpoint methods after Endpoint.Shutdown() has been called.

func (ShutdownError) Error

func (ShutdownError) Error() string

Error implements error.Error.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL