Skip to content
This repository has been archived by the owner on Jun 26, 2023. It is now read-only.

Commit

Permalink
switch to storing sessions in the context
Browse files Browse the repository at this point in the history
This patch allows associating a "session key" with a context by calling
`NewSession(ctx)`. When the session is used in a request, the exchange can call
`GetOrCreateSession(ctx)` to make the request in the appropriate session, or create a
new session with the given session key.

Implements ipfs/kubo#7198
  • Loading branch information
Stebalien committed Apr 23, 2020
1 parent a30d3ce commit 2e8ddd2
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 0 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ require (
github.com/ipfs/go-block-format v0.0.2
github.com/ipfs/go-cid v0.0.5
)

go 1.13
76 changes: 76 additions & 0 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package exchange

import (
"context"
"fmt"
"io"
"sync/atomic"

blocks "github.com/ipfs/go-block-format"
cid "github.com/ipfs/go-cid"
Expand Down Expand Up @@ -35,3 +37,77 @@ type SessionExchange interface {
Interface
NewSession(context.Context) Fetcher
}

// SessionID is an opaque type uniquely identifying a session.
type SessionID struct {
// Opaque type to ensure users don't cook this up and break things.
id uint64
}

// String formats the session ID as a string.
func (id SessionID) String() string {
return fmt.Sprintf("session-%d", id.id)
}

// IsZero returns true if this SessionId is the zero value (not a valid session
// ID).
func (id SessionID) IsZero() bool {
return id.id == 0
}

type sessionContextKey struct{}
type sessionContextValue struct {
sesID SessionID
sesCtx context.Context
}

// NewSession registers a new session with the context. The session will be
// closed when the passed-in context is canceled.
//
// If there's already a session associated with the context, the existing
// session will be used.
//
// This function does not initialize any state, it just reserves a new SessionID
// associates it with the context.
func NewSession(ctx context.Context) context.Context {
if _, ok := ctx.Value(sessionContextKey{}).(*sessionContextValue); ok {
return ctx
}
_, ctx = createSession(ctx)
return ctx
}

// last allocated session ID. 0 is _never_ used.
var lastSessionID uint64

// GetOrCreateSession loads the session from the context, or creates one if
// there is no associated session.
//
// This function also returns the context used to create the session. The
// session should be stopped when this context is canceled.
func GetOrCreateSession(ctx context.Context) (SessionID, context.Context) {
if s, ok := ctx.Value(sessionContextKey{}).(*sessionContextValue); ok {
return s.sesID, s.sesCtx
}
return createSession(ctx)
}

func createSession(ctx context.Context) (SessionID, context.Context) {
// Allocate a new session ID
id := SessionID{atomic.AddUint64(&lastSessionID, 1)}

// Create a spot to spot to hold the session information.
ctxValue := &sessionContextValue{sesID: id}

// Derive a new context with this information.
ctx = context.WithValue(ctx, sessionContextKey{}, ctxValue)

// Cyclically reference the session context so the session's context
// also references the session.
//
// We could reference the original context, but that doesn't have the
// session attached to it.
ctxValue.sesCtx = ctx

return id, ctx
}

0 comments on commit 2e8ddd2

Please sign in to comment.