localsession

package module
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2023 License: Apache-2.0 Imports: 8 Imported by: 1

README

LocalSession

Introduction

LocalSession is used to implicitly manage and transmit context within or between goroutines.

Usage

Session

Session is an interface to carry and transmit your context. It has Get() and WithValue() methods to manipulate your data. And IsValid() method to tells your if it is valid at present. We provides two implementations by default:

  • SessionCtx: use std context.Context as underlying storage, which means data from different goroutines are isolated.
  • SessionMap: use std map as underlying storage, which means data from different goroutines are shared.

Both implementations are Concurrent Safe.

SessionManager

SessionManager is a global manager of sessions. Through BindSession() and CurSession() methods it provides, you can transmit your session within the thread implicitly, without using explicit codes like CallXXX(context.Context, args....).

import (
	"context"
    "github.com/cloudwego/localsession"
)

// global manager
var manager = localsession.NewSessionManager(ManagerOptions{
	ShardNumber: 10,
	EnableImplicitlyTransmitAsync: true,
	GCInterval: time.Hour,
})

// global data
var key, v = "a", "b"
var key2, v2 = "c", "d"

func ASSERT(v bool) {
	if !v {
		panic("not true!")
	}
}

func main() {
    // get or initialize your context
    var ctx = context.Background()
    ctx = context.WithValue(ctx, key, v)

	// initialize new session with context
	var session = localsession.NewSessionCtx(ctx) 

	// set specific key-value and update session
	start := session.WithValue(key2, v2)

	// set current session
	manager.BindSession(start)

    // do somethings...
    
    // no need to pass context!
    GetDataX()
}

// read specific key under current session
func GetDataX() {
    // val exists
	val := manager.GetCurSession().Get(key) 
	ASSERT(val == v)

    // val2 exists
	val2 := manager.GetCurSession().Get(key2) 
	ASSERT(val2 == v2)
}

We provides a defaultManager to manage session between different goroutines, thus you don't need to make a SessionManager by your own.

You can use Go() or GoSession() to initiatively transmit your context to other goroutines.


package main

import (
	"context"
    . "github.com/cloudwego/localsession"
)

func GetCurSession() Session {
	s, ok := CurSession()
	if !ok {
		panic("can't get current seession!")
	}
	return s
}

func main() {
	var ctx = context.Background()
	var key, v = "a", "b"
	var key2, v2 = "c", "d"
	var sig = make(chan struct{})
	var sig2 = make(chan struct{})

	// initialize new session with context
	var session = NewSessionCtx(ctx) // implementation...

	// set specific key-value and update session
	start := session.WithValue(key, v)

	// set current session
	BindSession(start)

	// pass to new goroutine...
	Go(func() {
		// read specific key under current session
		val := GetCurSession().Get(key) // val exists
		ASSERT(val == v)
		// doSomething....

		// set specific key-value under current session
		// NOTICE: current session won't change here
		next := GetCurSession().WithValue(key2, v2)
		val2 := GetCurSession().Get(key2) // val2 == nil
		ASSERT(val2 == nil)

		// pass both parent session and new session to sub goroutine
		GoSession(next, func() {
			// read specific key under current session
			val := GetCurSession().Get(key) // val exists
			ASSERT(val == v)

			val2 := GetCurSession().Get(key2) // val2 exists
			ASSERT(val2 == v2)
			// doSomething....

			sig2 <- struct{}{}

			<-sig
			ASSERT(GetCurSession().IsValid() == false) // current session is invalid

			println("g2 done")
			sig2 <- struct{}{}
		})

		Go(func() {
			// read specific key under current session
			val := GetCurSession().Get(key) // val exists
			ASSERT(v == val)

			val2 := GetCurSession().Get(key2) // val2 == nil
			ASSERT(val2 == nil)
			// doSomething....

			sig2 <- struct{}{}

			<-sig
			ASSERT(GetCurSession().IsValid() == false) // current session is invalid

			println("g3 done")
			sig2 <- struct{}{}
		})

		BindSession(next)
		val2 = GetCurSession().Get(key2) // val2 exists
		ASSERT(v2 == val2)

		sig2 <- struct{}{}

		<-sig
		ASSERT(next.IsValid() == false) // next is invalid

		println("g1 done")
		sig2 <- struct{}{}
	})

	<-sig2
	<-sig2
	<-sig2

	val2 := GetCurSession().Get(key2) // val2 == nil
	ASSERT(val2 == nil)

	// initiatively ends the session,
	// then all the inherited session (including next) will be disabled
	session.Disable()
	close(sig)

	ASSERT(start.IsValid() == false) // start is invalid

	<-sig2
	<-sig2
	<-sig2
	println("g0 done")

	UnbindSession()
}
Implicitly Transmit Async Context

You can also set option EnableImplicitlyTransmitAsync as true to transparently transmit context. Once the option is enabled, every goroutine will inherit their parent's session.

func ExampleSessionCtx_EnableImplicitlyTransmitAsync() {
	// rest DefaultManager with new Options
	SetDefaultManager(NewSessionManager(ManagerOptions{
		ShardNumber: 10,
		EnableImplicitlyTransmitAsync: true,
		GCInterval: time.Hour,
	}))

	// WARNING: pprof.Do() must be called before BindSession(), 
	// otherwise transparently transmitting session will be dysfunctional
	labels := pprof.Labels("c", "d")
	pprof.Do(context.Background(), labels, func(ctx context.Context){})
	
	s := NewSessionMap(map[interface{}]interface{}{
		"a": "b",
	})
	BindSession(s)

	// WARNING: pprof.Do() must be called before BindSession(), 
	// otherwise transparently transmitting session will be dysfunctional
	// labels := pprof.Labels("c", "d")
	// pprof.Do(context.Background(), labels, func(ctx context.Context){})

	wg := sync.WaitGroup{}
	wg.Add(3)
	go func() {
		defer wg.Done()
		ASSERT("b" == mustCurSession().Get("a"))

		go func() {
			defer wg.Done()
			ASSERT("b" == mustCurSession().Get("a"))
		}()

		ASSERT("b" == mustCurSession().Get("a"))
		UnbindSession()
		ASSERT(nil == mustCurSession())

		go func() {
			defer wg.Done()
			ASSERT(nil == mustCurSession())
		}()

	}()
	wg.Wait()
}

Community

Documentation

Overview

Copyright 2023 CloudWeGo Authors

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Index

Examples

Constants

View Source
const (
	// DefaultShardNum set the sharding number of id->session map for default SessionManager
	DefaultShardNum = 100

	// DefaultGCInterval set the GC interval for default SessionManager
	DefaultGCInterval = time.Hour

	// DefaultEnableImplicitlyTransmitAsync enables TransparentTransmitAsync for default SessionManager
	DefaultEnableImplicitlyTransmitAsync = false
)
View Source
const Pprof_Label_Session_ID = "go_session_id"

Variables

This section is empty.

Functions

func BindSession

func BindSession(s Session)

BindSession binds the session with current goroutine

func Go

func Go(f func())

Go calls f asynchronously and pass caller's session to the new goroutine

func GoSession

func GoSession(s Session, f func())

SessionGo calls f asynchronously and pass s session to the new goroutine

func SetDefaultManager

func SetDefaultManager(m SessionManager)

SetDefaultManager updates default SessionManager to m

func UnbindSession

func UnbindSession()

UnbindSession unbind a session (if any) with current goroutine

Notice: If you want to end the session, please call `Disable()` (or whatever make the session invalid) on your session's implementation

Types

type ManagerOptions

type ManagerOptions struct {
	// EnableImplicitlyTransmitAsync enables transparently transmit
	// current session to children goroutines
	//
	// WARNING: Once this option enables, `pprof.Do()` must be called before `BindSession()`,
	// otherwise transmitting will be dysfunctional
	EnableImplicitlyTransmitAsync bool

	// ShardNumber is used to shard session id, it must be larger than zero
	ShardNumber int

	// GCInterval decides the GC interval for SessionManager,
	// it must be larger than 1s or zero means disable GC
	GCInterval time.Duration
}

ManagerOptions for SessionManager

type Session

type Session interface {
	// IsValid tells if the session is valid at present
	IsValid() bool

	// Get returns value for specific key
	Get(key interface{}) interface{}

	// WithValue sets value for specific key,and return newly effective session
	WithValue(key interface{}, val interface{}) Session
}

Session represents a local storage for one session

func CurSession

func CurSession() (Session, bool)

CurSession gets the session for current goroutine

type SessionCtx

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

SessionCtx implements Session with context, which means children session WON'T affect parent and sibling sessions

Example
var ctx = context.Background()
var key, v = "a", "b"
var key2, v2 = "c", "d"
var sig = make(chan struct{})
var sig2 = make(chan struct{})

// initialize new session with context
var session = NewSessionCtx(ctx) // implementation...

// set specific key-value and update session
start := session.WithValue(key, v)

// set current session
BindSession(start)

// pass to new goroutine...
Go(func() {
	// read specific key under current session
	val := GetCurSession().Get(key) // val exists
	ASSERT(val == v)
	// doSomething....

	// set specific key-value under current session
	// NOTICE: current session won't change here
	next := GetCurSession().WithValue(key2, v2)
	val2 := GetCurSession().Get(key2) // val2 == nil
	ASSERT(val2 == nil)

	// pass both parent session and new session to sub goroutine
	GoSession(next, func() {
		// read specific key under current session
		val := GetCurSession().Get(key) // val exists
		ASSERT(val == v)

		val2 := GetCurSession().Get(key2) // val2 exists
		ASSERT(val2 == v2)
		// doSomething....

		sig2 <- struct{}{}

		<-sig
		ASSERT(GetCurSession().IsValid() == false) // current session is invalid

		println("g2 done")
		sig2 <- struct{}{}
	})

	Go(func() {
		// read specific key under current session
		val := GetCurSession().Get(key) // val exists
		ASSERT(v == val)

		val2 := GetCurSession().Get(key2) // val2 == nil
		ASSERT(val2 == nil)
		// doSomething....

		sig2 <- struct{}{}

		<-sig
		ASSERT(GetCurSession().IsValid() == false) // current session is invalid

		println("g3 done")
		sig2 <- struct{}{}
	})

	BindSession(next)
	val2 = GetCurSession().Get(key2) // val2 exists
	ASSERT(v2 == val2)

	sig2 <- struct{}{}

	<-sig
	ASSERT(next.IsValid() == false) // next is invalid

	println("g1 done")
	sig2 <- struct{}{}
})

<-sig2
<-sig2
<-sig2

val2 := GetCurSession().Get(key2) // val2 == nil
ASSERT(val2 == nil)

// initiatively ends the session,
// then all the inherited session (including next) will be disabled
session.Disable()
close(sig)

ASSERT(start.IsValid() == false) // start is invalid

<-sig2
<-sig2
<-sig2
println("g0 done")

UnbindSession()

func NewSessionCtx

func NewSessionCtx(ctx context.Context) *SessionCtx

NewSessionCtx creates and enables a SessionCtx

func NewSessionCtxWithTimeout

func NewSessionCtxWithTimeout(ctx context.Context, timeout time.Duration) *SessionCtx

NewSessionCtx creates and enables a SessionCtx, and disable the session after timeout

func (SessionCtx) Disable

func (self SessionCtx) Disable()

Disable ends the session

func (*SessionCtx) Get

func (self *SessionCtx) Get(key interface{}) interface{}

Get value for specific key

func (*SessionCtx) IsValid

func (self *SessionCtx) IsValid() bool

IsValid tells if the session is valid at present

func (SessionCtx) WithValue

func (self SessionCtx) WithValue(key interface{}, val interface{}) Session

Set value for specific key,and return newly effective session

type SessionID

type SessionID uint64

SessionID is the identity of a session

type SessionManager

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

SessionManager maintain and manage sessions

func GetDefaultManager

func GetDefaultManager() SessionManager

GetDefaultManager returns a copy of default SessionManager

warning: use it only for state check

func NewSessionManager

func NewSessionManager(opts ManagerOptions) SessionManager

NewSessionManager creates a SessionManager with default containers If opts.GCInterval > 0, it will start scheduled GC() loop automatically

func (*SessionManager) BindSession

func (self *SessionManager) BindSession(id SessionID, s Session)

BindSession binds the session with current goroutine

func (SessionManager) Close

func (self SessionManager) Close()

Close stop persistent work for the manager, like GC

func (SessionManager) GC

func (self SessionManager) GC()

GC sweep invalid sessions and release unused memory

func (*SessionManager) GetSession

func (self *SessionManager) GetSession(id SessionID) (Session, bool)

Get gets specific session or get inherited session if option EnableImplicitlyTransmitAsync is true

func (SessionManager) Options

func (self SessionManager) Options() ManagerOptions

Options shows the manager's Options

func (*SessionManager) UnbindSession

func (self *SessionManager) UnbindSession(id SessionID)

UnbindSession clears current session

Notice: If you want to end the session, please call `Disable()` (or whatever make the session invalid) on your session's implementation

type SessionMap

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

NewSessionMap implements Session with map, which means children session WILL affect parent session and sibling sessions

func NewSessionMap

func NewSessionMap(m map[interface{}]interface{}) *SessionMap

NewSessionMap creates and enables a SessionMap

func NewSessionMapWithTimeout

func NewSessionMapWithTimeout(m map[interface{}]interface{}, timeout time.Duration) *SessionMap

NewSessionCtx creates and enables a SessionCtx, and disable the session after timeout

func (*SessionMap) Disable

func (self *SessionMap) Disable()

Disable ends the session

func (*SessionMap) Get

func (self *SessionMap) Get(key interface{}) interface{}

Get value for specific key

func (*SessionMap) IsValid

func (self *SessionMap) IsValid() bool

IsValid tells if the session is valid at present

func (*SessionMap) WithValue

func (self *SessionMap) WithValue(key interface{}, val interface{}) Session

Set value for specific key,and return itself

Jump to

Keyboard shortcuts

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