tchannel

package
v2.0.0-beta2+incompatible Latest Latest
Warning

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

Go to latest
Published: Mar 26, 2015 License: MIT Imports: 16 Imported by: 0

README

TChannel

network multiplexing and framing protocol for RPC

Stability: experimental

NOTE: master:golang is not yet stable

Getting started

Get Mercurial and Golang from your package manager of choice.

brew install hg
brew install golang
mkdir -p ~/golang/src

Set up your environment for your shell of choice.

export GOPATH="${HOME}/golang"
export PATH="${PATH}":"${GOPATH}"/bin

TChannel uses godep to manage dependencies. To get started:

mkdir -p $GOPATH/src/github.com/uber
cd $GOPATH/src/github.com/uber && git clone git@github.com/uber/tchannel.git
go get github.com/tools/godep
cd $GOPATH/src/github.com/uber/tchannel/golang
godep restore
make

To try out.

cd build/examples
./server

Note host:port then run client.

cd build/examples
./client -o echo -2 hi -3 hi -p localhost:10500 -s TestService

Example

import (
    "encoding/json"
    "time"
    "github.com/uber/tchannel/golang"
    "code.google.com/p/go.net/context"
    "fmt"
)

type Headers map[string]string

type Ping struct {
    Message string `json:"message"`
}

type Pong struct {
    Message string `json:"message"`
}

func ping(ctx context.Context, call *tchannel.InboundCall) {
    var headers Header

    if err := call.ReadArg2(tchannel.NewJSONInput(&headers)); err != nil {
        fmt.Printf("Could not read headers from client: %v", err)
        return
    }

    var ping Ping
    if err := call.ReadArg3(tchannel.NewJSONInput(&ping)); err != nil {
        fmt.Printf("Could not read body from client: %v", err)
        return
    }

    if err := call.Response().WriteArg2(tchannel.NewJSONOutput(headers)); err != nil {
        fmt.Printf("Could not echo response headers to client: %v", err)
        return
    }

    pong := Pong{Message: fmt.Sprintf("ping %s", ping.Message)}
    if err := call.Response().WriteArg3(tchannel.NewJSONOutput(pong)); err != nil {
        fmt.Printf("Could not write response body to client: %v", err)
        return
    }
}

func main() {
    // Create a new TChannel for handling requests
    server, err := tchannel.NewChannel("localhost:8050", nil)
    if err != nil {
        panic(err)
    }

    // Register a handler for the ping message on the PingService
    server.Register(tchannel.HandleFunc(ping), "PingService", "ping")

    // Listen for incoming requests
    go server.ListenAndHandle()

    // Create a new TChannel for sending requests.
    client, err := tchannel.NewChannel("localhost:8051", nil)
    if err != nil {
        panic(err)
    }

    // Listen for bi-directional messages
    go client.ListenAndHandle()

    // Make a call to ourselves, with a timeout of 10s
    ctx, cancel := context.WithTimeout(context.Background(), time.Second * 10)
    defer cancel()

    var responseHeaders Headers
    var pong Pong
    if err := client.RoundTrip("localhost:8050", "PingService", "ping",
        tchannel.NewJSONOutput(Headers{}),  tchannel.NewJSONOutput(Ping{Message: "Hello world"}),
        tchannel.NewJSONInput(&responseHeaders), tchannel.NewJSONInput(&pong)); err != nil {
        panic(err)
    }

    fmt.Printf("Received pong: %s\n", pong.Message)
}

This examples creates a client and server channel. The server channel registers a PingService with a ping operation, which takes request Headers and a Ping body and returns the same Headers along with a Pong body. The client sends a ping request to the server

Note that every instance is bidirectional, so the same channel can be used for both sending and receiving requests to peers. New connections are initiated on demand.

Overview

TChannel is a network protocol with the following goals:

  • request / response model
  • multiple requests multiplexed across the same TCP socket
  • out of order responses
  • streaming request and responses
  • all frames checksummed
  • transport arbitrary payloads
  • easy to implement in multiple languages
  • near-redis performance

This protocol is intended to run on datacenter networks for inter-process communication.

Protocol

TChannel frames have a fixed length header and 3 variable length fields. The underlying protocol does not assign meaning to these fields, but the included client/server implementation uses the first field to represent a unique endpoint or function name in an RPC model. The next two fields can be used for arbitrary data. Some suggested way to use the 3 fields are:

  • URI path, HTTP method and headers as JSON, body
  • function name, headers, thrift / protobuf

Note however that the only encoding supported by TChannel is UTF-8. If you want JSON, you'll need to stringify and parse outside of TChannel.

This design supports efficient routing and forwarding of data where the routing information needs to parse only the first or second field, but the 3rd field is forwarded without parsing.

There is no notion of client and server in this system. Every TChannel instance is capable of making or receiving requests, and thus requires a unique port on which to listen. This requirement may change in the future.

Further examples

Tests

make test or make cover

Contributors

  • mmihic

MIT Licenced

Documentation

Index

Constants

View Source
const (
	// MaxFrameSize is the total maximum size for a frame
	MaxFrameSize = math.MaxUint16

	// FrameHeaderSize is the size of the header element for a frame
	FrameHeaderSize = 16

	// MaxFramePayloadSize is the maximum size of the payload for a single frame
	MaxFramePayloadSize = MaxFrameSize - FrameHeaderSize
)
View Source
const (
	InitParamHostPort    = "host_port"
	InitParamProcessName = "process_name"
)

Standard init params

View Source
const CurrentProtocolVersion = 0x02

CurrentProtocolVersion is the current version of the TChannel protocol supported by this stack

Variables

View Source
var (
	// ErrConnectionClosed is returned when a caller performs an operation on a closed connection
	ErrConnectionClosed = errors.New("connection is closed")

	// ErrConnectionNotReady is returned when a caller attempts to send a request through
	// a connection which has not yet been initialized
	ErrConnectionNotReady = errors.New("connection is not yet ready")

	// ErrSendBufferFull is returned when a message cannot be sent to the peer because
	// the frame sending buffer has become full.  Typically this indicates that the
	// connection is stuck and writes have become backed up
	ErrSendBufferFull = errors.New("connection send buffer is full, cannot send frame")
)
View Source
var (
	// ErrServerBusy is a SystemError indicating the server is busy
	ErrServerBusy = NewSystemError(ErrorCodeBusy, "server busy")

	// ErrRequestCancelled is a SystemError indicating the request has been cancelled on the peer
	ErrRequestCancelled = NewSystemError(ErrorCodeCancelled, "request cancelled")

	// ErrTimeout is a SytemError indicating the request has timed out
	ErrTimeout = NewSystemError(ErrorCodeTimeout, "timeout")
)
View Source
var (
	// ErrMismatchedChecksumTypes is returned when a peer sends a continuation fragment containing
	// a different checksum type from that used for the original message
	ErrMismatchedChecksumTypes = errors.New("peer sent a different checksum type for fragment")

	// ErrWriteAfterComplete is returned when a caller attempts to write to a body after the last fragment was sent
	ErrWriteAfterComplete = errors.New("attempted to write to a stream after the last fragment sent")

	// ErrMismatchedChecksum is returned when a local checksum calculation differs from that reported by peer
	ErrMismatchedChecksum = errors.New("local checksum differs from peer")

	// ErrDataLeftover is returned when a caller considers an argument complete, but there is more data
	// remaining in the argument
	ErrDataLeftover = errors.New("more data remaining in argument")
)
View Source
var DefaultFramePool = defaultFramePool{}

The DefaultFramePool uses the heap as the pool

View Source
var (
	// ErrHandlerNotFound is returned when no registered handler can be found for a given service and operation
	ErrHandlerNotFound = NewSystemError(ErrorCodeBadRequest, "no handler for service and operation")
)

Functions

func NewRootContext

func NewRootContext(ctx context.Context) context.Context

NewRootContext creates a new root context for making outbound calls

func NewSystemError

func NewSystemError(code SystemErrorCode, msg string) error

NewSystemError defines a new SystemError with a code and message

Types

type BytesInput

type BytesInput []byte

A BytesInput reads an entire call argument into a byte slice

func (*BytesInput) ReadFrom

func (in *BytesInput) ReadFrom(r io.Reader) error

ReadFrom fills in the byte slice from the input stream

type BytesOutput

type BytesOutput []byte

A BytesOutput writes a byte slice as a call argument

func (BytesOutput) WriteTo

func (out BytesOutput) WriteTo(w io.Writer) error

WriteTo writes out the byte stream

type ChannelOptions

type ChannelOptions struct {
	// Default Connection options
	DefaultConnectionOptions ConnectionOptions

	// The name of the process, for logging and reporting to peers
	ProcessName string

	// The logger to use for this channel
	Logger Logger
}

ChannelOptions are used to control parameters on a create a TChannel

type Checksum

type Checksum interface {
	// TypeCode returns the type of this checksum
	TypeCode() ChecksumType

	// Size returns the size of the calculated checksum
	Size() int

	// Add adds bytes to the checksum calculation
	Add(b []byte) []byte

	// Sum returns the current checksum value
	Sum() []byte
}

A Checksum calculates a running checksum against a bytestream

type ChecksumType

type ChecksumType byte

A ChecksumType is a checksum algorithm supported by TChannel for checksumming call bodies

const (
	// ChecksumTypeNone indicates no checksum is included in the message
	ChecksumTypeNone ChecksumType = 0

	// ChecksumTypeCrc32 indicates the message checksum is calculated using crc32
	ChecksumTypeCrc32 ChecksumType = 1

	// ChecksumTypeFarmhash indicates the message checksum is calculated using Farmhash
	ChecksumTypeFarmhash ChecksumType = 2
)

func (ChecksumType) ChecksumSize

func (t ChecksumType) ChecksumSize() int

ChecksumSize returns the size in bytes of the checksum calculation

func (ChecksumType) New

func (t ChecksumType) New() Checksum

New creates a new Checksum of the given type

type Connection

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

Connection represents a connection to a remote peer.

func (*Connection) NextMessageID

func (c *Connection) NextMessageID() uint32

NextMessageID reserves the next available message id for this connection

type ConnectionOptions

type ConnectionOptions struct {
	// The identity of the local peer
	PeerInfo PeerInfo

	// The frame pool, allowing better management of frame buffers.  Defaults to using raw heap
	FramePool FramePool

	// The size of receive channel buffers.  Defaults to 512
	RecvBufferSize int

	// The size of send channel buffers.  Defaults to 512
	SendBufferSize int

	// The type of checksum to use when sending messages
	ChecksumType ChecksumType
}

ConnectionOptions are options that control the behavior of a Connection

type Frame

type Frame struct {

	// The header for the frame
	Header FrameHeader

	// The payload for the frame
	Payload []byte
	// contains filtered or unexported fields
}

A Frame is a header and payload

func NewFrame

func NewFrame(payloadCapacity int) *Frame

NewFrame allocates a new frame with the given payload capacity

func (*Frame) ReadFrom

func (f *Frame) ReadFrom(r io.Reader) error

ReadFrom reads the frame from the given io.Reader

func (*Frame) SizedPayload

func (f *Frame) SizedPayload() []byte

SizedPayload returns the slice of the payload actually used, as defined by the header

func (*Frame) WriteTo

func (f *Frame) WriteTo(w io.Writer) error

WriteTo writes the frame to the given io.Writer

type FrameHeader

type FrameHeader struct {

	// The id of the message represented by the frame
	ID uint32
	// contains filtered or unexported fields
}

FrameHeader is the header for a frame, containing the MessageType and size

func (FrameHeader) FrameSize

func (fh FrameHeader) FrameSize() uint16

FrameSize returns the total size of the frame

func (FrameHeader) PayloadSize

func (fh FrameHeader) PayloadSize() uint16

PayloadSize returns the size of the frame payload

func (*FrameHeader) SetPayloadSize

func (fh *FrameHeader) SetPayloadSize(size uint16)

SetPayloadSize sets the size of the frame payload

func (FrameHeader) String

func (fh FrameHeader) String() string

type FramePool

type FramePool interface {
	// Retrieves a new frame from the pool
	Get() *Frame

	// Releases a frame back to the pool
	Release(f *Frame)
}

A FramePool is a pool for managing and re-using frames

type Handler

type Handler interface {
	// Handles an incoming call for service
	Handle(ctx context.Context, call *InboundCall)
}

A Handler is an object hat can be registered with a Channel to process incoming calls for a given service and operation

type HandlerFunc

type HandlerFunc func(ctx context.Context, call *InboundCall)

The HandlerFunc is an adapter to allow the use of ordering functions as TChannel handlers. If f is a function with the appropriate signature, HandlerFunc(f) is a Hander object that calls f

func (HandlerFunc) Handle

func (f HandlerFunc) Handle(ctx context.Context, call *InboundCall)

Handle calls f(ctx, call)

type InboundCall

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

An InboundCall is an incoming call from a peer

func (*InboundCall) Operation

func (call *InboundCall) Operation() []byte

Operation teturns the operation being called

func (*InboundCall) ReadArg2

func (call *InboundCall) ReadArg2(arg Input) error

ReadArg2 reads the second argument from the inbound call, blocking until the entire argument has been read or an error/timeout occurs.

func (*InboundCall) ReadArg3

func (call *InboundCall) ReadArg3(arg Input) error

ReadArg3 reads the third argument from the inbound call, blocking until th entire argument has been read or an error/timeout occurs.

func (*InboundCall) Response

func (call *InboundCall) Response() *InboundCallResponse

Response provides access to the InboundCallResponse object which can be used to write back to the calling peer

func (*InboundCall) ServiceName

func (call *InboundCall) ServiceName() string

ServiceName returns the name of the service being called

type InboundCallResponse

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

An InboundCallResponse is used to send the response back to the calling peer

func (*InboundCallResponse) SendSystemError

func (call *InboundCallResponse) SendSystemError(err error) error

SendSystemError returns a system error response to the peer. The call is considered complete after this method is called, and no further data can be written.

func (*InboundCallResponse) SetApplicationError

func (call *InboundCallResponse) SetApplicationError() error

SetApplicationError marks the response as being an application error. This method can only be called before any arguments have been sent to the calling peer.

func (*InboundCallResponse) WriteArg2

func (call *InboundCallResponse) WriteArg2(arg Output) error

WriteArg2 writes the second argument in the response, blocking until the argument is fully written or an error/timeout has occurred.

func (*InboundCallResponse) WriteArg3

func (call *InboundCallResponse) WriteArg3(arg Output) error

WriteArg3 writes the third argument in the response, blocking until the argument is fully written or an error/timeout has occurred.

type Input

type Input interface {
	// Reads the argument from the given io.Reader
	ReadFrom(r io.Reader) error
}

An Input is able to read an argument from a call body

func NewJSONInput

func NewJSONInput(data interface{}) Input

NewJSONInput reates a new JSONInput around an arbitrary data interface

func NewStreamingInput

func NewStreamingInput(w io.Writer) Input

NewStreamingInput creates a new StreamingInput around an io.Writer

type JSONInput

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

JSONInput reads an interface encoded as a JSON object

func (JSONInput) ReadFrom

func (in JSONInput) ReadFrom(r io.Reader) error

ReadFrom unmarshals the json data into the desired interface

type JSONOutput

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

JSONOutput writes an interface as an encoded JSON object

func (JSONOutput) WriteTo

func (out JSONOutput) WriteTo(w io.Writer) error

WriteTo marshals the data to the output stream in json format

type Logger

type Logger interface {
	// Errorf logs a message at error priority
	Errorf(msg string, args ...interface{})

	// Warnf logs a message at warning priority
	Warnf(msg string, args ...interface{})

	// Infof logs a message at info priority
	Infof(msg string, args ...interface{})

	// Debugf logs a message at debug priority
	Debugf(msg string, args ...interface{})
}

Logger provides an abstract interface for logging from TChannel. Applications can use whatever logging library they prefer as long as they implement this interface

type NullLogger

type NullLogger struct{}

NullLogger is a logger that emits nowhere

func (NullLogger) Debugf

func (l NullLogger) Debugf(msg string, args ...interface{})

Debugf logs a message at debug priority

func (NullLogger) Errorf

func (l NullLogger) Errorf(msg string, args ...interface{})

Errorf logs a message at error priority

func (NullLogger) Infof

func (l NullLogger) Infof(msg string, args ...interface{})

Infof logs a message at info priority

func (NullLogger) Warnf

func (l NullLogger) Warnf(msg string, args ...interface{})

Warnf logs a message at warning priority

type OutboundCall

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

An OutboundCall is an active call to a remote peer. A client makes a call by calling BeginCall on the TChannel, writing argument content via WriteArg2() and WriteArg3(), and then reading reading response data via the ReadArg2() and ReadArg3() methods on the Response() object.

func (*OutboundCall) Response

func (call *OutboundCall) Response() *OutboundCallResponse

Response provides access to the call's response object, which can be used to read response arguments

func (*OutboundCall) WriteArg2

func (call *OutboundCall) WriteArg2(arg Output) error

WriteArg2 writes the the second argument part to the request, blocking until the argument is written

func (*OutboundCall) WriteArg3

func (call *OutboundCall) WriteArg3(arg Output) error

WriteArg3 writes the third argument to the request, blocking until the argument is written

type OutboundCallResponse

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

An OutboundCallResponse is the response to an outbound call

func (*OutboundCallResponse) ApplicationError

func (call *OutboundCallResponse) ApplicationError() bool

ApplicationError returns true if the call resulted in an application level error TODO(mmihic): In current implementation, you must have called ReadArg2 before this method returns the proper value. We should instead have this block until the first fragment is available, if the first fragment hasn't been received.

func (*OutboundCallResponse) ReadArg2

func (call *OutboundCallResponse) ReadArg2(arg Input) error

ReadArg2 reads the second argument from the response, blocking until the argument is read or an error/timeout has occurred.

func (*OutboundCallResponse) ReadArg3

func (call *OutboundCallResponse) ReadArg3(arg Input) error

ReadArg3 reads the third argument from the response, blocking until the argument is read or an error/timeout has occurred.

type Output

type Output interface {
	// Writes the argument to the given io.Writer
	WriteTo(w io.Writer) error
}

An Output is able to write an argument to a call body

func NewJSONOutput

func NewJSONOutput(data interface{}) Output

NewJSONOutput creates a new JSONOutput around an arbitrary data interface

func NewStreamingOutput

func NewStreamingOutput(r io.Reader) Output

NewStreamingOutput creates a new StreamingOutput around an io.Reader

type PeerInfo

type PeerInfo struct {
	// The host and port that can be used to contact the peer, as encoded by net.JoinHostPort
	HostPort string

	// The logical process name for the peer, used for only for logging / debugging
	ProcessName string
}

PeerInfo contains nformation about a TChannel peer

func (PeerInfo) String

func (p PeerInfo) String() string

type ResponseCode

type ResponseCode byte

ResponseCode to a CallReq

type Span

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

Span represents Zipkin-style span

func CurrentSpan

func CurrentSpan(ctx context.Context) *Span

CurrentSpan returns the Span value for the provided Context

func NewRootSpan

func NewRootSpan() *Span

NewRootSpan creates a new top-level Span for a call-graph within the provided context

func (*Span) EnableTracing

func (s *Span) EnableTracing(enabled bool)

EnableTracing controls whether tracing is enabled for this context

func (Span) NewChildSpan

func (s Span) NewChildSpan() *Span

NewChildSpan begins a new child span in the provided Context

func (Span) ParentID

func (s Span) ParentID() uint64

ParentID returns the id of the parent span in this call graph

func (Span) SpanID

func (s Span) SpanID() uint64

SpanID returns the id of this specific RPC

func (Span) String

func (s Span) String() string

func (Span) TraceID

func (s Span) TraceID() uint64

TraceID returns the trace id for the entire call graph of requests. Established at the outermost edge service and propagated through all calls

func (Span) TracingEnabled

func (s Span) TracingEnabled() bool

TracingEnabled checks whether tracing is enabled for this context

type StreamingInput

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

StreamingInput streams the contents of the argument to the given io.Writer

func (StreamingInput) ReadFrom

func (in StreamingInput) ReadFrom(r io.Reader) error

ReadFrom streams the contents of an argument to the output io.Writer

type StreamingOutput

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

StreamingOutput streams the contents of the given io.Reader

func (StreamingOutput) WriteTo

func (out StreamingOutput) WriteTo(w io.Writer) error

WriteTo streams the contents of the io.Reader to the output

type SystemError

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

A SystemError is a system-level error, containing an error code and message TODO(mmihic): Probably we want to hide this interface, and let application code just deal with standard raw errors.

func (SystemError) Error

func (se SystemError) Error() string

Error returns the SystemError message, conforming to the error interface

type SystemErrorCode

type SystemErrorCode byte

A SystemErrorCode indicates how a caller should handle a system error returned from a peer

const (
	// ErrorCodeInvalid is an invalid error code, and should not be used
	ErrorCodeInvalid SystemErrorCode = 0x00

	// ErrorCodeTimeout indicates the peer timed out.  Callers can retry the request
	// on another peer if the request is safe to retry.
	ErrorCodeTimeout SystemErrorCode = 0x01

	// ErrorCodeCancelled indicates that the request was cancelled on the peer.  Callers
	// can retry the request on the same or another peer if the request is safe to retry
	ErrorCodeCancelled SystemErrorCode = 0x02

	// ErrorCodeBusy indicates that the request was not dispatched because the peer
	// was too busy to handle it.  Callers can retry the request on another peer, and should
	// reweight their connections to direct less traffic to this peer until it recovers.
	ErrorCodeBusy SystemErrorCode = 0x03

	// ErrorCodeDeclined indicates that the request not dispatched because the peer
	// declined to handle it, typically because the peer is not yet ready to handle it.
	// Callers can retry the request on another peer, but should not reweight their connections
	// and should continue to send traffic to this peer.
	ErrorCodeDeclined SystemErrorCode = 0x04

	// ErrorCodeUnexpected indicates that the request failed for an unexpected reason, typically
	// a crash or other unexpected handling.  The request may have been processed before the failure;
	// callers should retry the request on this or another peer only if the request is safe to retry
	ErrorCodeUnexpected SystemErrorCode = 0x05

	// ErrorCodeBadRequest indicates that the request was malformed, and could not be processed.
	// Callers should not bother to retry the request, as there
	ErrorCodeBadRequest SystemErrorCode = 0x06

	// ErrorCodeProtocol indincates a fatal protocol error communicating with the peer.  The connection
	// will be terminated.
	ErrorCodeProtocol SystemErrorCode = 0xFF
)

func GetSystemErrorCode

func GetSystemErrorCode(err error) SystemErrorCode

GetSystemErrorCode returns the code to report for the given error. If the error is a SystemError, we can get the code directly. Otherwise treat it as an unexpected error

type TChannel

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

A TChannel is a bi-directional connection to the peering and routing network. Applications can use a TChannel to make service calls to remote peers via BeginCall, or to listen for incoming calls from peers. Once the channel is created, applications should call the ListenAndHandle method to listen for incoming peer connections. Because channels are bi-directional, applications should call ListenAndHandle even if they do not offer any services

func NewChannel

func NewChannel(hostPort string, opts *ChannelOptions) (*TChannel, error)

NewChannel creates a new Channel that will bind to the given host and port. If no port is provided, the channel will start on an OS assigned port

func (*TChannel) BeginCall

func (ch *TChannel) BeginCall(ctx context.Context, hostPort,
	serviceName, operationName string) (*OutboundCall, error)

BeginCall starts a new call to a remote peer, returning an OutboundCall that can be used to write the arguments of the call TODO(mmihic): Support CallOptions such as format, request specific checksums, retries, etc

func (*TChannel) HostPort

func (ch *TChannel) HostPort() string

HostPort returns the host and port on which the Channel is listening

func (*TChannel) ListenAndHandle

func (ch *TChannel) ListenAndHandle() error

ListenAndHandle runs a listener to accept and manage new incoming connections. Blocks until the channel is closed.

func (*TChannel) Register

func (ch *TChannel) Register(h Handler, serviceName, operationName string)

Register regsters a handler for a service+operation pair

func (*TChannel) RoundTrip

func (ch *TChannel) RoundTrip(ctx context.Context, hostPort, serviceName, operationName string,
	reqArg2, reqArg3 Output, resArg2, resArg3 Input) (bool, error)

RoundTrip calls a peer and waits for the response

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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