Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/sandbox-link-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The Sandbox I/O service runs `sandboxlink.Serve` with a `ServeConfig`:

- `URL`, `Credential` and `Resource` come from the [bootstrap input](sandbox-bootstrap.md). The credential identifies the service, so the Hello carries no peer ID. `ServerInstanceID` is a new ID whenever the service starts without its operation and handle registries.
- `Services` holds one handler per offered service and version. A handler receives the `Bind`, which carries the authorized binding including a File stream's exports, and the stream. It owns the stream and returns when it is done with it. Its context ends when the attachment closes or `Serve` returns.
- `Serve` reconnects with jittered exponential backoff, sending the same `ServerInstanceID`, whenever the link drops. It returns when its context ends or when the relay refuses the Hello with any failure other than `ServiceUnavailable` or `LimitExceeded`, for example `AuthenticationFailed` after the credential is withdrawn or `StaleGeneration` after a newer sandbox took over the resource. Before returning it cancels every handler's context and waits for the handlers.
- `Serve` reconnects with jittered exponential backoff, sending the same `ServerInstanceID`, whenever the link drops. It returns when its context ends or when the relay refuses the Hello with a failure that is not [retryable](#failures), for example `AuthenticationFailed` after the credential is withdrawn or `StaleGeneration` after a newer sandbox took over the resource. Before returning it cancels every handler's context and waits for the handlers.
- `OnAttachmentLost` fires when an attachment's last open stream ends while the attachment is still open, such as when the link drops. `OnAttachmentRestored` fires when a stream binds a lost attachment again. `OnAttachmentClosed` fires with the reason when the relay reports `AttachmentClosed`. Losing a socket is not closing an attachment: the service keeps an attachment's state until it is closed.
- Closing is final. A `Bind` the relay sent before a close can arrive after the `AttachmentClosed`, so `Serve` refuses a `Bind` for an attachment closed within the last `sandboxlink.HandshakeTimeout` with `LeaseExpired`. A stream of a closed attachment that ends later never marks another attachment lost.

Expand Down Expand Up @@ -277,6 +277,8 @@ The relay copies each direction through a 32 KiB buffer and holds at most one 25
| 11 | `LimitExceeded` | A link's stream limit or its limit of renewals being decided is reached |
| 12 | `ProtocolViolation` | A message is malformed, not allowed where it arrived, or carries a request ID that does not increase |

`ServiceUnavailable` and `LimitExceeded` are transient: the same request may succeed later, and `Code.Retryable` reports them. Every other code is final: repeating the request with the same credential, attachment and generation fails again.

## Verification

`go test ./internal/sandboxlink/...` covers the golden frames, decode rejection, and the relay's authorization, generation, lease, revocation, renewal bound and reconnect behavior, including orderly end and abort propagation. `go test -run '^$' -fuzz FuzzDecode ./internal/sandboxlink` fuzzes the decoder.
6 changes: 6 additions & 0 deletions internal/sandboxlink/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ func (c Code) String() string {

func (c Code) Error() string { return "sandbox link: " + c.String() }

// Retryable reports whether the same request may succeed later.
// ServiceUnavailable and LimitExceeded are transient; every other code is
// final, and repeating the request with the same credential, attachment and
// generation fails again.
func (c Code) Retryable() bool { return c == ServiceUnavailable || c == LimitExceeded }

// Error is a typed Link failure with its effect. Cause is the local error that
// produced it, such as a transport failure or a canceled context; it is never
// sent.
Expand Down
6 changes: 3 additions & 3 deletions internal/sandboxlink/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ type ServeConfig struct {

// Serve connects to the relay and serves bound streams until parent ends. It
// reconnects with backoff, sending the same ServerInstanceID, until the relay
// refuses the Hello with a failure other than ServiceUnavailable or
// LimitExceeded; it then returns that *Error. Before returning it cancels
// refuses the Hello with a failure that is not [Code.Retryable]; it then
// returns that *Error. Before returning it cancels
// every handler's context and waits for the handlers.
func Serve(parent context.Context, cfg ServeConfig) error {
hello := ServeHello{Version: Version, Credential: cfg.Credential, Resource: cfg.Resource, ServerInstanceID: cfg.ServerInstanceID}
Expand Down Expand Up @@ -95,7 +95,7 @@ func Serve(parent context.Context, cfg ServeConfig) error {
return stop(parent.Err())
}
var refused *Error
if !connected && errors.As(err, &refused) && refused.Code != ServiceUnavailable && refused.Code != LimitExceeded {
if !connected && errors.As(err, &refused) && !refused.Code.Retryable() {
return stop(refused)
}
if cfg.OnDisconnected != nil {
Expand Down
Loading