sess

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Feb 28, 2026 License: MIT Imports: 4 Imported by: 0

README

Go Reference Go Report Card Coverage Status

English | 简体中文 | Español | 日本語 | Français

sess

Session-typed communication protocols via algebraic effects on kont.

Overview

Session types assign a type to each step of a communication protocol. Each operation — send, receive, select, offer, close — is individually well-typed via Go generics, and protocol composition within a single endpoint is type-safe. Duality (matching operations across endpoints) is a programmer responsibility: the programmer writes dual protocols, and mismatches manifest at runtime as type assertion failures or deadlocks.

sess encodes session types as algebraic effects evaluated by the kont effect system. Each protocol step — send, receive, select, offer, close — is an effect that suspends the computation until the transport completes the operation. The transport returns iox.ErrWouldBlock at computational boundaries, allowing proactor event loops (e.g., io_uring) to multiplex execution without thread-blocking.

Two equivalent APIs: Cont (closure-based, straightforward composition) and Expr (frame-based, amortized zero-allocation for hot paths).

Installation

go get code.hybscloud.com/sess

Requires Go 1.26+.

Session Operations

Each operation has a dual. When one endpoint performs an operation, the other must perform its dual.

Operation Dual Suspends?
Send[T] — send a value Recv[T] — receive a value iox.ErrWouldBlock
SelectL / SelectR — choose a branch Offer — follow the peer's choice iox.ErrWouldBlock
Close — end the session Close Never

Usage

Use Run for protocol prototyping and validation. Use Exec for externally managed endpoints. Use the Expr API (RunExpr/ExecExpr) for stepping control or to minimize allocation overhead on hot paths.

Send and Receive

One side sends a value; the dual side receives it.

client := sess.SendThen(42, sess.CloseDone("ok"))
server := sess.RecvBind(func(n int) kont.Eff[string] {
    return sess.CloseDone(fmt.Sprintf("got %d", n))
})
a, b := sess.Run(client, server) // "ok", "got 42"

Expr equivalent: ExprSendThen, ExprRecvBind, ExprCloseDone, RunExpr.

Branching

One side selects a branch; the dual side offers both branches and follows the selection.

client := sess.SelectLThen(sess.SendThen(1, sess.CloseDone("left")))
server := sess.OfferBranch(
    func() kont.Eff[string] {
        return sess.RecvBind(func(n int) kont.Eff[string] {
            return sess.CloseDone(fmt.Sprintf("left %d", n))
        })
    },
    func() kont.Eff[string] { return sess.CloseDone("right") },
)
a, b := sess.Run(client, server)
Recursive Protocols

Protocols that repeat use Loop with Either: Left continues the loop, Right terminates.

counter := sess.Loop(0, func(i int) kont.Eff[kont.Either[int, string]] {
    if i >= 3 {
        return sess.CloseDone(kont.Right[int, string]("done"))
    }
    return sess.SendThen(i, kont.Pure(kont.Left[int, string](i+1)))
})
Delegation

Transfer an endpoint to a third party by sending it; accept delegation by receiving it.

delegator := sess.SendThen(endpoint, sess.CloseDone("delegated"))
acceptor := sess.RecvBind(func(ep *sess.Endpoint) kont.Eff[string] {
    return sess.CloseDone("accepted")
})
Stepping

For proactor event loops (e.g., io_uring), Step and Advance evaluate one effect at a time. Unlike Run and Exec — which synchronously wait for progress — the stepping API yields iox.ErrWouldBlock to the caller, letting the event loop reschedule.

ep, _ := sess.New()
protocol := sess.ExprSendThen(42, sess.ExprCloseDone[struct{}](struct{}{}))
_, susp := sess.Step[struct{}](protocol)
// In a proactor event loop (e.g., io_uring), yield on boundary:
_, nextSusp, err := sess.Advance(ep, susp)
if err != nil {
    return susp // yield to event loop, reschedule when ready
}
susp = nextSusp
Error Handling

Compose session protocols with error effects. Throw eagerly short-circuits the protocol and discards the pending suspension.

clientResult, serverResult := sess.RunError[string, string, string](client, server)
// Either[string, string]: Right on success, Left on Throw

Execution Model

Function Description
Run / RunExpr Run both sides on one goroutine, creating an endpoint pair internally
Exec / ExecExpr Run one side on a pre-created endpoint
Step + Advance Evaluate one effect at a time for external event loops

Cont vs Expr: Cont is closure-based and straightforward to compose. Expr is frame-based with amortized zero-allocation, suited for hot paths.

API

Category Cont Expr
Constructors SendThen, RecvBind, CloseDone, SelectLThen, SelectRThen, OfferBranch ExprSendThen, ExprRecvBind, ExprCloseDone, ExprSelectLThen, ExprSelectRThen, ExprOfferBranch
Recursion Loop ExprLoop
Execution Exec, Run ExecExpr, RunExpr
Error execution ExecError, RunError ExecErrorExpr, RunErrorExpr
Stepping Step, Advance, StepError, AdvanceError
Bridge Reify (Cont→Expr), Reflect (Expr→Cont)
Transport New(*Endpoint, *Endpoint)

References

Dependencies

License

MIT — see LICENSE.

©2026 Hayabusa Cloud Co., Ltd.

Documentation

Overview

Package sess provides session-typed communication protocols via algebraic effects on code.hybscloud.com/kont.

Protocols are composed of typed operations dispatched on a session endpoint.

Architecture

API Topologies

Integration

  • Stepping: Step and Advance (or StepError/AdvanceError) evaluate computations one effect at a time, making them easy to integrate with a proactor loop.
  • Blocking: Exec, Run (and Error/Expr variants) wait past boundaries using adaptive backoff.

Example

epA, _ := sess.New()
protocol := sess.ExprSendThen(42, sess.ExprCloseDone[struct{}](struct{}{}))
_, susp := sess.Step[struct{}](protocol)
for susp != nil {
	var err error
	if _, susp, err = sess.Advance(epA, susp); err != nil {
		continue // retry on ErrWouldBlock
	}
}

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Advance

func Advance[R any](ep *Endpoint, susp *kont.Suspension[R]) (R, *kont.Suspension[R], error)

Advance dispatches the suspended session operation on the endpoint. DispatchSession is non-blocking: returns iox.ErrWouldBlock when the bounded SPSC queue cannot make progress (the I/O boundary).

On success (nil error), the suspension is consumed and the protocol advances to the next effect or completion. On iox.ErrWouldBlock, the suspension is unconsumed and may be retried after the peer makes progress.

func AdvanceError

func AdvanceError[E, R any](ep *Endpoint, susp *kont.Suspension[kont.Either[E, R]]) (kont.Either[E, R], *kont.Suspension[kont.Either[E, R]], error)

AdvanceError dispatches the suspended operation on the endpoint. Session ops are non-blocking (ErrWouldBlock). Error ops are eager: Throw discards the suspension and returns Left.

func CloseDone

func CloseDone[A any](a A) kont.Eff[A]

CloseDone closes the session and returns a. Fuses Perform(Close{}) + Then + Pure.

func Exec

func Exec[R any](ep *Endpoint, protocol kont.Eff[R]) R

Exec runs a Cont-world session protocol on a pre-created endpoint. Blocks on iox.ErrWouldBlock via adaptive backoff (iox.Backoff), without spawning goroutines or creating channels.

func ExecError

func ExecError[E, R any](ep *Endpoint, protocol kont.Eff[R]) kont.Either[E, R]

ExecError runs a Cont-world session protocol with error handling on a pre-created endpoint. Returns Either[E, R] — Right on success, Left on Throw. Blocks on iox.ErrWouldBlock via adaptive backoff (iox.Backoff), without spawning goroutines or creating channels.

func ExecErrorExpr

func ExecErrorExpr[E, R any](ep *Endpoint, protocol kont.Expr[R]) kont.Either[E, R]

ExecErrorExpr runs an Expr-world session protocol with error handling on a pre-created endpoint. Returns Either[E, R] — Right on success, Left on Throw. Blocks on iox.ErrWouldBlock via adaptive backoff (iox.Backoff), without spawning goroutines or creating channels.

func ExecExpr

func ExecExpr[R any](ep *Endpoint, protocol kont.Expr[R]) R

ExecExpr runs an Expr-world session protocol on a pre-created endpoint. Blocks on iox.ErrWouldBlock via adaptive backoff (iox.Backoff), without spawning goroutines or creating channels.

func ExprCloseDone

func ExprCloseDone[A any](a A) kont.Expr[A]

ExprCloseDone closes the session and returns a. Fuses ExprPerform(Close{}) + ExprThen + ExprReturn.

func ExprLoop

func ExprLoop[S, A any](initial S, step func(S) kont.Expr[kont.Either[S, A]]) kont.Expr[A]

ExprLoop runs a recursive session protocol (Expr-world). step returns Left(nextState) to continue or Right(result) to finish. Stack-safe: pure completed steps are iterated without Go stack growth; effectful steps are trampolined through evalFrames via UnwindFrame.

func ExprOfferBranch

func ExprOfferBranch[A any](onLeft func() kont.Expr[A], onRight func() kont.Expr[A]) kont.Expr[A]

ExprOfferBranch waits for the peer's choice and calls onLeft or onRight. Fuses ExprPerform(Offer{}) + ExprBind + Either branch.

func ExprRecvBind

func ExprRecvBind[T, B any](f func(T) kont.Expr[B]) kont.Expr[B]

ExprRecvBind receives a value and passes it to f. Fuses ExprPerform(Recv[T]{}) + ExprBind.

func ExprSelectLThen

func ExprSelectLThen[B any](next kont.Expr[B]) kont.Expr[B]

ExprSelectLThen selects the left branch and continues with next. Fuses ExprPerform(SelectL{}) + ExprThen.

func ExprSelectRThen

func ExprSelectRThen[B any](next kont.Expr[B]) kont.Expr[B]

ExprSelectRThen selects the right branch and continues with next. Fuses ExprPerform(SelectR{}) + ExprThen.

func ExprSendThen

func ExprSendThen[T, B any](v T, next kont.Expr[B]) kont.Expr[B]

ExprSendThen sends a value and then continues with next. Fuses ExprPerform(Send[T]{Value: v}) + ExprThen.

func Loop

func Loop[S, A any](initial S, step func(S) kont.Eff[kont.Either[S, A]]) kont.Eff[A]

Loop runs a recursive session protocol (Cont-world). step returns Left(nextState) to continue or Right(result) to finish. Stack-safe: delegates recursion to ExprLoop's iterative trampoline via Reify/Reflect, avoiding Go stack growth on deep pure Left chains.

func New

func New() (*Endpoint, *Endpoint)

New creates a connected pair of session endpoints. Internal transport uses bounded lock-free SPSC queues: two for data (A→B, B→A), two for branch choice (A→B, B→A), and a shared atomic counter for close signaling.

Session operations are non-blocking: DispatchSession returns iox.ErrWouldBlock when the peer has not yet produced or consumed.

func OfferBranch

func OfferBranch[A any](onLeft func() kont.Eff[A], onRight func() kont.Eff[A]) kont.Eff[A]

OfferBranch waits for the peer's choice and calls onLeft or onRight. Fuses Perform(Offer{}) + Bind + Either branch.

func RecvBind

func RecvBind[T, B any](f func(T) kont.Eff[B]) kont.Eff[B]

RecvBind receives a value and passes it to f. Fuses Perform(Recv[T]{}) + Bind.

func Reflect

func Reflect[A any](m kont.Expr[A]) kont.Eff[A]

Reflect converts an Expr-world session protocol to Cont-world. The resulting Eff can be evaluated with Exec or Run.

func Reify

func Reify[A any](m kont.Eff[A]) kont.Expr[A]

Reify converts a Cont-world session protocol to Expr-world. The resulting Expr can be evaluated with ExecExpr, RunExpr, or stepped with Step and Advance.

func Run

func Run[A, B any](a kont.Eff[A], b kont.Eff[B]) (A, B)

Run creates a session pair, runs both Cont-world protocols, and returns both results. Interleaves execution of both sides on the calling goroutine using adaptive backoff (iox.Backoff) when neither side can make progress. Does not spawn goroutines or create channels.

func RunError

func RunError[E, A, B any](a kont.Eff[A], b kont.Eff[B]) (kont.Either[E, A], kont.Either[E, B])

RunError creates a session pair, runs both Cont-world protocols with error handling, and returns both results as Either values. Interleaves execution of both sides on the calling goroutine using adaptive backoff (iox.Backoff). Does not spawn goroutines or create channels.

func RunErrorExpr

func RunErrorExpr[E, A, B any](a kont.Expr[A], b kont.Expr[B]) (kont.Either[E, A], kont.Either[E, B])

RunErrorExpr creates a session pair, runs both Expr-world protocols with error handling, and returns both results as Either values. Interleaves execution of both sides on the calling goroutine using adaptive backoff (iox.Backoff). Does not spawn goroutines or create channels.

func RunExpr

func RunExpr[A, B any](a kont.Expr[A], b kont.Expr[B]) (A, B)

RunExpr creates a session pair, runs both Expr-world protocols, and returns both results. Interleaves execution of both sides on the calling goroutine using adaptive backoff (iox.Backoff) when neither side can make progress. Does not spawn goroutines or create channels.

func SelectLThen

func SelectLThen[B any](next kont.Eff[B]) kont.Eff[B]

SelectLThen selects the left branch and continues with next. Fuses Perform(SelectL{}) + Then.

func SelectRThen

func SelectRThen[B any](next kont.Eff[B]) kont.Eff[B]

SelectRThen selects the right branch and continues with next. Fuses Perform(SelectR{}) + Then.

func SendThen

func SendThen[T, B any](v T, next kont.Eff[B]) kont.Eff[B]

SendThen sends a value and then continues with next. Fuses Perform(Send[T]{Value: v}) + Then.

func Step

func Step[R any](protocol kont.Expr[R]) (R, *kont.Suspension[R])

Step evaluates a session protocol until the first effect suspension. Returns (result, nil) on completion, or (zero, suspension) if pending.

func StepError

func StepError[E, R any](protocol kont.Expr[R]) (kont.Either[E, R], *kont.Suspension[kont.Either[E, R]])

StepError evaluates a session protocol with error support until the first effect suspension. Returns (Either[E, R], nil) on completion or error, or (zero, suspension) if pending.

Types

type Close

type Close struct {
	kont.Phantom[struct{}]
}

Close is the effect operation for closing the session. Perform(Close{}) signals session termination.

func (Close) DispatchSession

func (Close) DispatchSession(ctx *sessionContext) (kont.Resumed, error)

DispatchSession handles Close on the session transport. Atomically increments the shared close counter. Never blocks.

type Endpoint

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

Endpoint represents one side of a session-typed channel pair. Transport is backed by bounded lock-free SPSC queues from lfq.

func (*Endpoint) Serial

func (ep *Endpoint) Serial() Serial

Serial returns the serial number assigned to this endpoint's session.

type Offer

type Offer struct {
	kont.Phantom[kont.Either[struct{}, struct{}]]
}

Offer is the effect operation for receiving a branch choice from the peer. Perform(Offer{}) receives the peer's Left or Right selection.

func (Offer) DispatchSession

func (Offer) DispatchSession(ctx *sessionContext) (kont.Resumed, error)

DispatchSession handles Offer on the session transport. Non-blocking: returns iox.ErrWouldBlock if the choice queue is empty. true → Left (peer selected left), false → Right (peer selected right).

type Recv

type Recv[T any] struct {
	kont.Phantom[T]
}

Recv is the effect operation for receiving a value of type T. Perform(Recv[T]{}) receives a typed value from the peer.

func (Recv[T]) DispatchSession

func (Recv[T]) DispatchSession(ctx *sessionContext) (kont.Resumed, error)

DispatchSession handles Recv on the session transport. Non-blocking: returns iox.ErrWouldBlock if the bounded SPSC queue is empty.

type SelectL

type SelectL struct {
	kont.Phantom[struct{}]
}

SelectL is the effect operation for choosing the left branch. Perform(SelectL{}) signals the left choice to the peer.

func (SelectL) DispatchSession

func (SelectL) DispatchSession(ctx *sessionContext) (kont.Resumed, error)

DispatchSession handles SelectL on the session transport. Non-blocking: returns iox.ErrWouldBlock if the choice queue is full.

type SelectR

type SelectR struct {
	kont.Phantom[struct{}]
}

SelectR is the effect operation for choosing the right branch. Perform(SelectR{}) signals the right choice to the peer.

func (SelectR) DispatchSession

func (SelectR) DispatchSession(ctx *sessionContext) (kont.Resumed, error)

DispatchSession handles SelectR on the session transport. Non-blocking: returns iox.ErrWouldBlock if the choice queue is full.

type Send

type Send[T any] struct {
	kont.Phantom[struct{}]
	Value T
}

Send is the effect operation for sending a value of type T. Perform(Send[T]{Value: v}) sends v to the peer endpoint.

func (Send[T]) DispatchSession

func (s Send[T]) DispatchSession(ctx *sessionContext) (kont.Resumed, error)

DispatchSession handles Send on the session transport. Non-blocking: returns iox.ErrWouldBlock if the bounded SPSC queue is full.

type Serial

type Serial = uint32

Serial is a monotonically increasing session identifier. Each call to New assigns the next serial value.

Jump to

Keyboard shortcuts

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