racersdk

package
v0.24.0 Latest Latest
Warning

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

Go to latest
Published: Nov 12, 2026 License: Apache-2.0 Imports: 25 Imported by: 0

Documentation

Overview

Package racersdk reads objects through a node-local Racer cache and serves cache misses from your own storage.

Racer runs on each node and exposes every cache as two Unix sockets under /run/racer/<cache>/. A Client reads objects through the client socket. An Origin, registered with ServeOrigin on the origin socket, supplies objects that no Racer in the cluster has cached yet. One process may do either or both.

Reading

Create one Client per cache and share it:

client, err := racersdk.NewClient(racersdk.ClientConfig{Cache: "blobs"})
if err != nil {
	return err
}
defer client.Close()

object, err := client.Get(ctx, racersdk.Request{Key: key, Metadata: "bucket/path"})
if err != nil {
	return err
}
defer object.Close()

_, err = io.Copy(w, object)

Client.Get returns once Racer has accepted the read, with the object's Metadata available from Object.Metadata. Client.Stat returns metadata without reading content. Use ReadOptions to read a byte range or to pin a version by ETag.

A Key names an object whose content may change. Each version has its own ETag, and the bytes of a version never change. One Get reads a single version from start to end, but two separate reads of the same key may see different versions. To read the same version more than once, take the ETag from Client.Stat or Object.Metadata and pass it in ReadOptions.

Copy or no copy

An Object can be consumed in exactly two ways. Pick one per object.

Object.Read copies. Each call copies bytes from the socket into your buffer, so your code sees and owns the data: use it to hash, parse, decompress, or transform the content. Copying costs CPU and memory bandwidth proportional to the object size. Pass buffers of 32 KiB or more.

Object.WriteTo does not copy. It forwards the object to a writer. When the writer is backed by a file descriptor and implements io.ReaderFrom, such as an *os.File, a net.Conn, or the http.ResponseWriter of a plain-text HTTP/1 server, the kernel moves the data from Racer's socket to the destination with splice(2) and it never enters process memory. Other writers receive data through a single reused 256 KiB buffer. Your code never sees the bytes, so this is the right choice for proxies and anything that only stores or forwards content. io.Copy uses WriteTo automatically when the source is an Object, so io.Copy(dst, object) is the no-copy path; wrapping the object in another reader, such as io.TeeReader, switches to Read.

Both paths withhold the last byte until Racer confirms the whole range was delivered intact. A read that is cut short therefore always ends in an error rather than a silently truncated result, even for a destination that has already received the rest of the data.

Prefetching

Client.Prefetch submits a ticket of object ranges for Racer to load in the background without returning contents. Racer schedules the missing pages to their owners in the cache cluster, which fill them through the normal read path. Nothing is pinned, so loaded pages are evicted like any other. Tickets are local to the node and kept in memory; after a Racer restart, resubmit the same id and only missing pages are loaded.

status, err := client.Prefetch(ctx, "epoch-17", racersdk.PrefetchSpec{
	Refs: []racersdk.PrefetchRef{{Key: key.String(), Metadata: "bucket/path"}},
})

Resubmitting the same spec is idempotent. Client.PrefetchStatus reports progress and can wait for the ticket to settle, Client.CancelPrefetch releases it, and Client.ListPrefetches lists this cache's tickets. A PrefetchWindow replaces a ticket's desired set as a reader moves forward. SplitPrefetch divides a spec that exceeds Racer's per-ticket limits. Rejected requests return a *PrefetchError.

Serving cache misses

Implement Origin and pass it to ServeOrigin. Racer calls the origin for metadata and for individual pages of up to PageSize bytes. Requests for later pages are pinned to the ETag the origin returned for the first one, so origins only need ranged reads of a fixed version. The SDK validates every response, so a short, long, or mismatched body is reported to Racer instead of being cached.

Errors

Test errors with errors.Is. For Object.WriteTo, check ErrDestination before the other sentinels. It marks a writer failure, so do not retry the Racer read or count it against Racer. The error wraps the writer's own error, which may also match SDK sentinels from a nested Racer read.

Otherwise, test against ErrNotFound, ErrUnauthorized, ErrForbidden, ErrVersionMismatch, ErrRangeNotSatisfiable, ErrUnavailable, and ErrInvalidRequest. Errors caused by an ending context wrap context.Canceled or context.DeadlineExceeded, and calls after Close wrap net.ErrClosed. A WriteTo timeout from the destination's ReadFrom cannot be attributed to either endpoint. It wraps os.ErrDeadlineExceeded and matches neither ErrDestination nor ErrUnavailable. Any other error means Racer or an origin misbehaved; a proxy should report it as a bad gateway. Origins report failures by wrapping the same errors.

Testing

Package github.com/Azure/unbounded/pkg/racersdk/racersdktest runs an in-process Racer for tests, connecting a real Client to your Origin.

Index

Examples

Constants

View Source
const PageSize = 16 << 20

PageSize is the unit in which Racer caches and transfers objects (16 MiB). Objects no larger than PageSize may be read with ReadOptions.SmallObject.

Variables

View Source
var (
	// ErrInvalidRequest reports an invalid key, request value, option, or
	// configuration.
	ErrInvalidRequest = errors.New("racersdk: invalid request")
	// ErrUnauthorized reports that the origin rejected or required credentials.
	ErrUnauthorized = errors.New("racersdk: unauthorized")
	// ErrForbidden reports that the origin denied access.
	ErrForbidden = errors.New("racersdk: forbidden")
	// ErrNotFound reports that the object does not exist.
	ErrNotFound = errors.New("racersdk: not found")
	// ErrVersionMismatch reports that the requested ETag is no longer
	// available.
	ErrVersionMismatch = errors.New("racersdk: version mismatch")
	// ErrRangeNotSatisfiable reports a range outside the object.
	ErrRangeNotSatisfiable = errors.New("racersdk: range not satisfiable")
	// ErrUnavailable reports a temporary failure: Racer or the origin is
	// overloaded or unreachable, or a transfer was cut short. Retrying may
	// succeed.
	ErrUnavailable = errors.New("racersdk: unavailable")
	// ErrDestination reports that the writer passed to [Object.WriteTo]
	// failed, for example because a downstream client disconnected, a disk
	// is full, or a write timed out. Racer and the origin are not at fault,
	// and retrying the read does not help unless the destination recovers.
	// Check ErrDestination before other sentinels: the error also wraps the
	// writer's own error, which may match SDK sentinels from a nested Racer read.
	// Timeouts from the destination's ReadFrom remain ambiguous and match
	// neither ErrDestination nor [ErrUnavailable].
	ErrDestination = errors.New("racersdk: destination failed")
)

Errors returned by Client methods and Object reads, and, except for ErrDestination, recognized when returned by an Origin. Test for them with errors.Is.

Other failures wrap the cause: context.Canceled or context.DeadlineExceeded when a context ends, and net.ErrClosed after Client.Close or Object.Close. When Object.WriteTo times out and cannot tell whether Racer or the destination stalled, the error wraps os.ErrDeadlineExceeded and matches neither ErrUnavailable nor ErrDestination. Anything else is a protocol or origin failure that callers usually report as a bad gateway.

Functions

func ServeOrigin

func ServeOrigin(ctx context.Context, config OriginConfig, origin Origin) error

ServeOrigin serves origin to Racer until ctx is canceled, then closes all connections and bodies and returns ctx.Err(). It does not wait for callbacks that ignore cancellation. On return it removes the socket it created. A process that exits without returning leaves the socket behind; see OriginConfig.RecoverStaleSocket.

ServeOrigin does not need root and does not create directories. The socket directory /run/racer/<cache>/origin must already exist, be owned by the effective user, and not be group or other writable. The Racer dataplane creates it owned by UID/GID 65532 with mode 0700, so origins must run as that user. ServeOrigin creates the socket there with mode 0600, owned by its effective user. The privileged dataplane connects to it through CAP_DAC_OVERRIDE.

Example

An origin serving a single in-memory object.

package main

import (
	"bytes"
	"context"
	"io"
	"log"
	"time"

	"github.com/Azure/unbounded/pkg/racersdk"
)

var key, _ = racersdk.ParseKey("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae")

func main() { //nolint:testableexamples // Requires a provisioned Racer origin directory.
	origin := exampleMemoryOrigin([]byte("hello, racer"))
	if err := racersdk.ServeOrigin(context.Background(), racersdk.OriginConfig{Cache: "cache"}, origin); err != nil {
		log.Fatal(err)
	}
}

func exampleMemoryOrigin(content []byte) racersdk.Origin {
	return func(_ context.Context, r racersdk.OriginRequest) (racersdk.Metadata, io.ReadCloser, error) {

		metadata := racersdk.Metadata{
			Size:      int64(len(content)),
			ETag:      `"v1"`,
			ExpiresAt: time.Now().Add(time.Hour),
		}

		if r.Key != key {
			return racersdk.Metadata{}, nil, racersdk.ErrNotFound
		}

		if r.ETag != "" && r.ETag != metadata.ETag {
			return racersdk.Metadata{}, nil, racersdk.ErrVersionMismatch
		}

		if r.Head {
			return metadata, nil, nil
		}

		start := min(r.Offset, metadata.Size)

		length := min(r.Length, metadata.Size-start)
		if length == 0 {
			return metadata, nil, nil
		}

		return metadata, io.NopCloser(bytes.NewReader(content[start : start+length])), nil
	}
}

Types

type Client

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

Client reads objects from a Racer cache over its local Unix socket. It is safe for concurrent use; create one per cache and share it.

func NewClient

func NewClient(config ClientConfig) (*Client, error)

NewClient returns a client for config.Cache. It does not connect until the first request, so Racer need not be running yet.

func (*Client) CancelPrefetch added in v0.21.0

func (c *Client) CancelPrefetch(ctx context.Context, id string) (PrefetchStatus, error)

CancelPrefetch cancels ticket id and returns its canceled status. Racer stops scheduling its pages but does not evict anything already loaded.

func (*Client) Close

func (c *Client) Close() error

Close closes idle connections and fails in-progress and future calls with an error wrapping net.ErrClosed. It is safe to call more than once.

func (*Client) Get

func (c *Client) Get(ctx context.Context, request Request, options ...ReadOptions) (*Object, error)

Get starts reading an object and returns once Racer has accepted the read and reported the object's metadata. Read the contents with Object.Read or Object.WriteTo and always call Object.Close.

Pass at most one ReadOptions. ctx applies to the whole transfer, not just the call to Get. However, Object.WriteTo cannot interrupt a blocked destination write unless the writer supports write deadlines.

func (*Client) HostRead added in v0.24.0

func (c *Client) HostRead(ctx context.Context, request Request, file *os.File, options HostReadOptions) (HostReadResult, error)

HostRead asks Racer to write an object range straight into file, normally a memfd, instead of streaming it over the socket. The file must be a regular file opened for writing without O_APPEND. Racer writes with positioned writes, except when file is a memfd opened read-write, sealed with F_SEAL_SHRINK, not sealed with F_SEAL_WRITE or F_SEAL_FUTURE_WRITE, and at least as long as the destination window: Racer then maps the file and decrypts straight into it, skipping a copy. Create such a memfd with MFD_ALLOW_SEALING and add F_SEAL_SHRINK after sizing it. The descriptor is passed to Racer with SCM_RIGHTS on the client socket, so the caller and Racer must be on the same host.

HostRead returns once Racer confirms every byte is in the file. On error the file may hold partial data. Racers that predate host-read answer with ErrNotFound or ErrInvalidRequest; callers that must work with them can fall back to Client.Get.

func (*Client) ListPrefetches added in v0.21.0

func (c *Client) ListPrefetches(ctx context.Context) ([]PrefetchStatus, error)

ListPrefetches returns summaries of this cache's tickets on the local node, sorted by id.

func (*Client) NewPrefetchWindow added in v0.21.0

func (c *Client) NewPrefetchWindow(id string, spec PrefetchSpec) *PrefetchWindow

NewPrefetchWindow returns a window for ticket id. spec supplies every field except Refs; its Generation and Sequence are used for the first submission.

func (*Client) Prefetch added in v0.21.0

func (c *Client) Prefetch(ctx context.Context, id string, spec PrefetchSpec) (PrefetchStatus, error)

Prefetch creates or updates ticket id. Racer schedules the missing pages and fills them in the background; nothing is pinned, so pages may be evicted later. Resubmitting the same spec is idempotent. Update a ticket with a higher Sequence, or supersede it with a higher Generation; see PrefetchWindow. Prefetch shares Stat's lane.

func (*Client) PrefetchStatus added in v0.21.0

func (c *Client) PrefetchStatus(ctx context.Context, id string, opts StatusOptions) (PrefetchStatus, error)

PrefetchStatus returns the status of ticket id.

func (*Client) Stat

func (c *Client) Stat(ctx context.Context, request Request) (Metadata, error)

Stat returns the metadata of the current version of an object without reading its contents. Pass the returned ETag to ReadOptions to read exactly that version later.

Example

A later range of a version found earlier, pinned by its ETag.

package main

import (
	"context"
	"errors"
	"io"
	"log"
	"os"

	"github.com/Azure/unbounded/pkg/racersdk"
)

var key, _ = racersdk.ParseKey("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae")

func main() { //nolint:testableexamples // Requires a running Racer cache.
	client, err := racersdk.NewClient(racersdk.ClientConfig{Cache: "cache"})
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	ctx := context.Background()

	metadata, err := client.Stat(ctx, racersdk.Request{Key: key})
	if err != nil {
		log.Fatal(err)
	}

	object, err := client.Get(ctx, racersdk.Request{Key: key}, racersdk.ReadOptions{
		Offset: metadata.Size / 2,
		ETag:   metadata.ETag,
	})
	if errors.Is(err, racersdk.ErrVersionMismatch) {
		log.Fatal("object changed since Stat")
	} else if err != nil {
		log.Fatal(err)
	}
	defer object.Close()

	if _, err := io.Copy(os.Stdout, object); err != nil {
		log.Fatal(err)
	}
}

type ClientConfig

type ClientConfig struct {
	// Cache names the Racer cache, a DNS subdomain such as "blobs". The
	// client connects to /run/racer/<Cache>/client/socket.
	Cache string
	// MaxConnections bounds concurrent [Client.Get] transfers without
	// [ReadOptions.SmallObject]. Zero means 64. SmallObject transfers have a
	// separate four-slot lane. [Client.Stat] and the prefetch calls share another
	// lane with MaxConnections slots, so every transfer can be preceded by a
	// Stat. Each lane has a bounded queue; calls fail with [ErrUnavailable] if
	// it is full or the wait times out.
	MaxConnections int
	// PageCredits is how many pages Racer may send ahead of the reader on
	// each [Client.Get]. Racer's fetch window for a read is the smaller of
	// this and its RACER_RANGE_WINDOW_PAGES, so it bounds single-read
	// throughput. Zero means 2; the maximum is 64. Each credit lets Racer hold
	// one more 16 MiB page for the read.
	PageCredits int
}

ClientConfig configures a Client.

type HostReadOptions added in v0.24.0

type HostReadOptions struct {
	// Offset is the first object byte to read.
	Offset int64
	// Length is the number of bytes to read. Zero reads to the end of the
	// object.
	Length int64
	// ETag pins the read to one version, as in [ReadOptions].
	ETag string
	// DestinationOffset is the file offset of the first byte written.
	DestinationOffset int64
	// DestinationLength bounds the file window Racer may write, starting at
	// DestinationOffset. It must be at least the range length. Zero means
	// the file size minus DestinationOffset.
	DestinationLength int64
	// OnPage, if set, is called after Racer reports that a page is in the
	// file, with the object offset and length of the bytes written. It runs
	// on the calling goroutine and should be quick.
	OnPage func(offset, length int64)
}

HostReadOptions selects the part of an object a Client.HostRead reads and where in the file it lands. Object offset o is written at file offset DestinationOffset + (o - Offset).

type HostReadResult added in v0.24.0

type HostReadResult struct {
	// Metadata describes the version that was read.
	Metadata Metadata
	// Offset and Length are the object range that was written.
	Offset, Length int64
}

HostReadResult reports a completed Client.HostRead.

type Key

type Key [32]byte

Key names an object in a Racer cache. The object's content may change over time; each version is identified by the ETag in its Metadata and must never change. A key is often a content digest, such as the SHA-256 of a blob, in which case the bytes cannot change, but the origin may report a new ETag for the same bytes.

func ParseKey

func ParseKey(s string) (Key, error)

ParseKey parses 64 lowercase hexadecimal characters.

func (Key) String

func (k Key) String() string

String returns the 64-character lowercase hexadecimal form of k.

type Metadata

type Metadata struct {
	// Size is the object length in bytes.
	Size int64
	// ETag is a strong HTTP entity tag such as "\"v1\"". Racer uses it to
	// ensure every page of a read comes from the same version.
	ETag string
	// ContentType is optional.
	ContentType string
	// ExpiresAt is when Racer must stop serving this version from cache. It
	// is required and kept at millisecond precision.
	ExpiresAt time.Time
}

Metadata describes one version of an object. The bytes of a version, identified by its ETag, must never change.

type Object

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

Object is an in-progress read returned by Client.Get. Consume it with either Object.Read or Object.WriteTo and always call Object.Close.

Read copies data into your buffer, so you can inspect, hash, or transform it. WriteTo hands data straight to a destination and, when the destination is backed by a file descriptor, never copies it into process memory. Prefer WriteTo whenever you only forward bytes; see the package documentation for details.

Both methods withhold the last byte of the read until Racer confirms the transfer completed, so a successful read is never silently truncated.

One goroutine at a time may call Read or WriteTo. Metadata and Close may be called concurrently with them.

func (*Object) Close

func (o *Object) Close() error

Close stops the read and closes its Racer connection. It is safe to call more than once and concurrently with Read or WriteTo. Close does not wait for those calls to return and always returns nil. An interrupted Read or WriteTo returns an error wrapping net.ErrClosed.

If WriteTo is already blocked in a destination write without supported write deadlines, that write must return on its own before WriteTo can return.

func (*Object) Metadata

func (o *Object) Metadata() Metadata

Metadata describes the version being read. Size is the whole object, not the selected range.

func (*Object) Progress added in v0.19.0

func (o *Object) Progress() int64

Progress returns how many bytes of the object Read and WriteTo have handed to the caller so far. It is safe to call from any goroutine, including while WriteTo runs, so callers can report progress on long reads.

func (*Object) Read

func (o *Object) Read(p []byte) (int, error)

Read copies the next bytes of the object into p. It returns io.EOF once the whole selected range has been read and verified complete.

Every byte is copied from the kernel into p. Reads smaller than 4 KiB are staged through an internal buffer and copied twice; use buffers of 32 KiB or more for throughput.

Example

Read copies the object into your buffer, for when you need to look at the bytes, here to hash them.

package main

import (
	"context"
	"crypto/sha256"
	"fmt"
	"io"
	"log"

	"github.com/Azure/unbounded/pkg/racersdk"
)

var key, _ = racersdk.ParseKey("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae")

func main() { //nolint:testableexamples // Requires a running Racer cache.
	client, err := racersdk.NewClient(racersdk.ClientConfig{Cache: "cache"})
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	object, err := client.Get(context.Background(), racersdk.Request{Key: key})
	if err != nil {
		log.Fatal(err)
	}
	defer object.Close()

	hash := sha256.New()
	if _, err := io.CopyBuffer(hash, object, make([]byte, 256<<10)); err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%x\n", hash.Sum(nil))
}

func (*Object) WriteTo

func (o *Object) WriteTo(w io.Writer) (int64, error)

WriteTo writes the rest of the object to w and returns the number of bytes written. io.Copy calls it automatically. A nil error means the whole selected range was written and Racer confirmed it complete. A failure from w.Write, including a short write or a write timeout, matches ErrDestination and wraps w's error. A timeout from w.ReadFrom cannot be attributed to either endpoint: it wraps os.ErrDeadlineExceeded and matches neither ErrDestination nor ErrUnavailable.

If w implements io.ReaderFrom and is backed by a file descriptor, such as a *net.TCPConn, an *os.File, or an HTTP/1 http.ResponseWriter, data moves from Racer's socket to w inside the kernel with splice(2), with no copy into process memory. Other writers receive data through a reused 256 KiB buffer.

When w supports SetWriteDeadline, directly or through an HTTP response controller, WriteTo takes over w's write deadline for the whole call. It replaces any deadline already set on w, including one derived from http.Server.WriteTimeout, with a fixed timeout for each write, and moves it to the present to interrupt a blocked write if the Get context ends. Before returning, it clears the deadline, so w has no write deadline afterward. To keep an overall write budget, bound the Get context and set the deadline again after WriteTo returns. WriteTo never closes w. Set HTTP response headers, including Content-Length, before calling WriteTo. Without supported write deadlines, neither context cancellation nor Object.Close can interrupt an already-blocked write to w; that write must return on its own.

Example

WriteTo forwards the object without copying it through your process, here to an HTTP response.

package main

import (
	"errors"
	"log"
	"net/http"
	"strconv"

	"github.com/Azure/unbounded/pkg/racersdk"
)

var key, _ = racersdk.ParseKey("2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae")

func main() { //nolint:testableexamples // Requires a running Racer cache.
	client, err := racersdk.NewClient(racersdk.ClientConfig{Cache: "cache"})
	if err != nil {
		log.Fatal(err)
	}
	defer client.Close()

	http.HandleFunc("/blob", func(w http.ResponseWriter, r *http.Request) {
		object, err := client.Get(r.Context(), racersdk.Request{Key: key})
		if errors.Is(err, racersdk.ErrNotFound) {
			http.NotFound(w, r)
			return
		} else if err != nil {
			http.Error(w, "unavailable", http.StatusBadGateway)
			return
		}
		defer object.Close()

		w.Header().Set("Content-Length", strconv.FormatInt(object.Metadata().Size, 10))

		if _, err := object.WriteTo(w); err != nil {
			// Headers are sent; abort so the client sees a truncated body.
			panic(http.ErrAbortHandler)
		}
	})
}

type Origin

type Origin func(ctx context.Context, request OriginRequest) (Metadata, io.ReadCloser, error)

Origin loads objects for Racer on a cache miss. It is called concurrently, once per request, and must honor ctx.

For a metadata request (Head is set) return the current metadata and a nil body. Otherwise return the metadata of the version you are serving and a body that yields exactly the bytes in [Offset, min(Offset+Length, Size)). When that range is empty, return a nil body. If ETag is set, serve only that version and return ErrVersionMismatch if it is gone; returning metadata with a different ETag has the same effect.

Report failures by wrapping one of the package errors, for example fmt.Errorf("%w: %w", racersdk.ErrNotFound, err). Any other error is reported to Racer as an internal error.

The SDK takes ownership of a non-nil body even when err is non-nil, and closes it exactly once, possibly concurrently with Read to abort a transfer. Panics are recovered.

type OriginConfig

type OriginConfig struct {
	// Cache names the Racer cache. The origin listens on
	// /run/racer/<Cache>/origin/socket. The directory must already exist and
	// its ancestors must not be symlinks.
	Cache string
	// CacheUID optionally binds the origin to one ClusterCache UID. Origin
	// sockets are named by cache name, so a ClusterCache that is deleted and
	// recreated under the same name reuses the path while an origin for the
	// old UID may still be listening. When CacheUID is set, requests whose
	// Racer-Cache header names a different or no UID get 503 Service
	// Unavailable instead of being served. Empty accepts any cache UID.
	CacheUID string
	// MaxConcurrentRequests bounds concurrent content requests; Racer receives
	// a retryable error beyond it. Zero means 64. Metadata requests have a
	// small separate limit.
	MaxConcurrentRequests int
	// RecoverStaleSocket lets ServeOrigin replace a socket left behind by an
	// earlier ServeOrigin with this option that exited without cleanup, such
	// as after a crash or SIGKILL. Enable it when a supervisor restarts the
	// origin. The directory must be owned by this process's effective user
	// and must not be writable by group or others. ServeOrigin keeps
	// .racer-origin.lock and .racer-origin.socket in the directory to
	// recognize its own sockets, and it never replaces a socket that is
	// still accepting connections or that it did not create with this
	// option. Turning the option on after a crash with it off therefore
	// does not remove the old socket.
	//
	// Without it, an existing socket path is an error. ServeOrigin removes
	// its socket only when it returns, so if the process exits without
	// returning, later calls fail until the socket is removed by hand or by
	// a startup step that knows no other origin is serving.
	RecoverStaleSocket bool
}

OriginConfig configures ServeOrigin.

type OriginRequest

type OriginRequest struct {
	Request
	// Head asks for metadata only.
	Head bool
	// ETag, when set, is the only version that may be served.
	ETag string
	// Offset is the first byte to return. It is always a multiple of
	// [PageSize].
	Offset int64
	// Length is the maximum number of bytes to return, at most [PageSize].
	// The range may extend past the end of the object.
	Length int64
}

OriginRequest is a request from Racer to an Origin. The embedded Request carries the key and the opaque values a client passed to Client.Get or Client.Stat.

func (OriginRequest) Format

func (r OriginRequest) Format(s fmt.State, _ rune)

Format prints the request with Metadata and Authorization redacted.

type PrefetchBytes added in v0.21.0

type PrefetchBytes struct {
	LogicalRequested  int64
	AlignedRequested  int64
	AlreadyResident   int64
	Queued            int64
	InFlight          int64
	Joined            int64
	FetchedFromOrigin int64
	Admitted          int64
	Failed            int64
}

PrefetchBytes is a ticket's byte accounting. Every value except LogicalRequested is a multiple of PageSize.

type PrefetchCoverage added in v0.21.0

type PrefetchCoverage struct {
	ResidentBytes int64
	ObservedAt    time.Time
}

PrefetchCoverage reports probed residency of the desired set.

type PrefetchError added in v0.21.0

type PrefetchError struct {
	// Status is the HTTP status code.
	Status int
	// Code is the machine-readable code, for example "stale_sequence". It
	// is empty if the response had no valid error body.
	Code string
	// Message is human-readable text with no stable contract.
	Message string
	// RetryAfter is the server's Retry-After, if any.
	RetryAfter time.Duration
}

PrefetchError is a non-2xx prefetch response. It matches ErrInvalidRequest for 400 and 413, ErrNotFound for 404, and ErrUnavailable for 429 and 503.

func (*PrefetchError) Error added in v0.21.0

func (e *PrefetchError) Error() string

func (*PrefetchError) Is added in v0.21.0

func (e *PrefetchError) Is(target error) bool

Is maps the status onto the SDK sentinels.

type PrefetchPart added in v0.21.0

type PrefetchPart struct {
	ID   string
	Spec PrefetchSpec
}

PrefetchPart is one ticket produced by SplitPrefetch.

func SplitPrefetch added in v0.21.0

func SplitPrefetch(id string, spec PrefetchSpec, maxRefs, maxBodyBytes int) []PrefetchPart

SplitPrefetch splits spec into tickets that each fit maxRefs refs and a maxBodyBytes JSON body. Zero or negative limits use Racer's defaults (16384 refs, 4 MiB). Part ids are "<id>.<n>" counting from 0, even for a single part, so they stay stable as the ref list grows. It returns nil if spec is invalid, a part id would be invalid, or a single ref cannot fit.

type PrefetchRef added in v0.21.0

type PrefetchRef struct {
	// Key is the object key as 64 lowercase hexadecimal characters.
	Key string
	// ETag optionally pins a version. Empty resolves the current version once
	// at submit.
	ETag string
	// Offset is the first byte. It must be less than the object length.
	Offset int64
	// Length is the number of bytes. Zero means to the end of the object.
	Length int64
	// Metadata is passed to the origin like [Request.Metadata].
	Metadata string
}

PrefetchRef is one object range to prefetch.

type PrefetchRefStatus added in v0.21.0

type PrefetchRefStatus struct {
	// Index is the ref position in the submitted spec.
	Index int
	Key   string
	// ETag is the resolved version, empty if resolution failed.
	ETag   string
	Offset int64
	Length int64
	// Outcome is "succeeded", "failed", or "pending".
	Outcome string
	// Error is set for failed refs, for example "version_changed".
	Error string
}

PrefetchRefStatus is the outcome of one ref.

type PrefetchSpec added in v0.21.0

type PrefetchSpec struct {
	// CacheUID, if set, must equal the live ClusterCache UID.
	CacheUID string
	// Priority is "high", "normal", or "low". Empty means "normal".
	Priority string
	// Generation and Sequence order updates of one ticket; see
	// [PrefetchWindow]. Each is at most 2^53-1.
	Generation uint64
	Sequence   uint64
	// TTL is how long the ticket lives without an update. It is rounded up to
	// whole seconds. Zero means the server default (600s); Racer caps it.
	TTL time.Duration
	// MaxPendingBytes bounds queued and in-flight bytes for the ticket. Zero
	// means the server default.
	MaxPendingBytes int64
	// Authorization is passed to the origin for every ref, like
	// [Request.Authorization]. Racer keeps it in memory only.
	Authorization string
	// Refs lists the ranges to prefetch. It must not be empty.
	Refs []PrefetchRef
}

PrefetchSpec describes a prefetch ticket.

type PrefetchState added in v0.21.0

type PrefetchState string

PrefetchState is the state of a ticket.

const (
	PrefetchQueued    PrefetchState = "QUEUED"
	PrefetchRunning   PrefetchState = "RUNNING"
	PrefetchSucceeded PrefetchState = "SUCCEEDED"
	PrefetchPartial   PrefetchState = "PARTIAL"
	PrefetchFailed    PrefetchState = "FAILED"
	PrefetchCanceled  PrefetchState = "CANCELLED"
)

Ticket states.

type PrefetchStatus added in v0.21.0

type PrefetchStatus struct {
	ID       string
	CacheUID string
	State    PrefetchState
	// Reason is set only for canceled tickets: "client", "expired",
	// "superseded", "cache_removed", or "passthrough".
	Reason     string
	Priority   string
	Generation uint64
	Sequence   uint64
	// Settled reports that nothing for the current window is queued or in
	// flight. Success means pages were admitted, not that they stay resident.
	Settled       bool
	TTL           time.Duration
	ExpiresAt     time.Time
	CreatedAt     time.Time
	UpdatedAt     time.Time
	RefsTotal     int64
	RefsSucceeded int64
	RefsFailed    int64
	RefsPending   int64
	Bytes         PrefetchBytes
	// Coverage is set only when requested.
	Coverage *PrefetchCoverage
	// Refs is set only when requested; continue with NextCursor.
	Refs       []PrefetchRefStatus
	NextCursor string
	// Created reports that [Client.Prefetch] created the ticket (HTTP 201).
	Created bool
}

PrefetchStatus mirrors Racer's ticket status. List summaries leave the counters, timestamps other than ExpiresAt, Coverage, and Refs zero.

type PrefetchWindow added in v0.21.0

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

PrefetchWindow submits a rolling window as one ticket whose desired set is replaced by each update. It is safe for concurrent use.

Counters only move forward, even when a call fails: the server may have applied a request whose response was lost, and resubmitting different refs at the same position would be rejected as a payload mismatch.

func (*PrefetchWindow) ID added in v0.21.0

func (w *PrefetchWindow) ID() string

ID returns the ticket id.

func (*PrefetchWindow) Position added in v0.21.0

func (w *PrefetchWindow) Position() (generation, sequence uint64)

Position returns the generation and sequence of the last submission, or of the next one before any submission.

func (*PrefetchWindow) Reset added in v0.21.0

func (w *PrefetchWindow) Reset(ctx context.Context, refs []PrefetchRef) (PrefetchStatus, error)

Reset supersedes the window with refs at the next generation and sequence 0.

func (*PrefetchWindow) Update added in v0.21.0

func (w *PrefetchWindow) Update(ctx context.Context, refs []PrefetchRef) (PrefetchStatus, error)

Update replaces the desired set with refs at the next sequence number. The first submission uses the position given to Client.NewPrefetchWindow.

type ReadOptions

type ReadOptions struct {
	// Offset is the first byte to read.
	Offset int64
	// Length is the number of bytes to read. Zero reads to the end of the
	// object. A range that extends past the end fails with
	// [ErrRangeNotSatisfiable].
	Length int64
	// ETag pins the read to one version, typically from [Client.Stat]. Use it
	// when several reads of a key must see the same bytes, since separate
	// unpinned reads may see different versions. If that version is no
	// longer available, Get fails with [ErrVersionMismatch].
	ETag string
	// SmallObject declares that the object is at most [PageSize] bytes. Small
	// reads use a separate admission queue so they are not delayed behind
	// large transfers. Get fails with [ErrInvalidRequest] if the object is
	// larger.
	SmallObject bool
}

ReadOptions selects part of an object or a specific version. The zero value reads the whole current version.

type Request

type Request struct {
	Key Key
	// Metadata tells the origin how to locate the object, for example a bucket
	// and path. Racer does not interpret it.
	Metadata string
	// Authorization is a credential for the origin. Racer does not interpret it.
	Authorization string
}

Request identifies an object and carries the opaque values that Racer forwards unchanged to the Origin on a cache miss.

Metadata and Authorization may be empty; otherwise, each is limited to 8 KiB with no leading or trailing ASCII space (0x20), bytes below 0x20, or byte 0x7f. Bytes above 0x7f are allowed; values need not be valid UTF-8. Formatting a Request with the fmt package redacts both values.

func (Request) Format

func (r Request) Format(s fmt.State, _ rune)

Format prints the key and redacts Metadata and Authorization.

type StatusOptions added in v0.21.0

type StatusOptions struct {
	// Wait blocks until the ticket settles or Wait elapses, whichever is
	// first. Racer caps it (default 60s) and rejects larger values.
	Wait time.Duration
	// Coverage probes owners for resident bytes of the desired set.
	Coverage bool
	// Refs is "", "failed", or "all" and selects per-ref outcomes.
	Refs string
	// Cursor continues a previous page of refs from
	// [PrefetchStatus.NextCursor].
	Cursor string
}

StatusOptions selects what Client.PrefetchStatus returns.

Directories

Path Synopsis
internal
fakeracer
Package fakeracer implements a sequential, noncaching Racer test daemon.
Package fakeracer implements a sequential, noncaching Racer test daemon.
sdkhook
Package sdkhook connects racersdktest to private SDK construction seams.
Package sdkhook connects racersdktest to private SDK construction seams.
wire
Package wire implements Racer HTTP heads, metadata grammar, and subscription framing.
Package wire implements Racer HTTP heads, metadata grammar, and subscription framing.
Package racersdktest runs an in-process fake Racer so tests can exercise a real racersdk.Client and racersdk.Origin without a deployed Racer.
Package racersdktest runs an in-process fake Racer so tests can exercise a real racersdk.Client and racersdk.Origin without a deployed Racer.

Jump to

Keyboard shortcuts

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