localsession

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Aug 2, 2023 License: Apache-2.0 Imports: 10 Imported by: 1

README

LocalSession

Introduction

LocalSession is used to implicitly manage and transmit context within or between goroutines. In canonical way, Go recommands developers to explicitly pass context.Context between functions to ensure the downstream callee get desired information from upstream. However this is tedious and ineffecient, resulting in many developers forget (or just don't want) to follow this practice. We have found many cases like that, especially in framework. Therefore, we design and implement a way to implicitly pass application context from root caller to end callee, without troubling intermediate implementation to always bring context.

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 provide a globally default manager to manage session between different goroutines, as long as you set InitDefaultManager() first.

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


package main

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

func init() {
    // initialize default manager first
	InitDefaultManager(DefaultManagerOptions())
}

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() {
	// EnableImplicitlyTransmitAsync must be true 
	ResetDefaultManager(ManagerOptions{
		ShardNumber: 10,
		EnableImplicitlyTransmitAsync: true,
		GCInterval: time.Hour,
	})

	// WARNING: if you want to use `pprof.Do()`, it 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)

	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 Pprof_Label_Session_ID = "go_session_id"
View Source
const SESSION_CONFIG_KEY = "CLOUDWEGO_SESSION_CONFIG_KEY"

SESSION_CONFIG_KEY is the env key for configuring default session manager.

Value format: [EnableImplicitlyTransmitAsync][,ShardNumber][,GCInterval]
- EnableImplicitlyTransmitAsync: 'true' means enabled, otherwist means disabled
- ShardNumber: integer > 0
- GCInterval: Golang time.Duration format, such as '10m' means ten minutes for each GC

Once the key is set, default option values will be set if the option value doesn't exist.

Variables

This section is empty.

Functions

func BindSession

func BindSession(s Session)

BindSession binds the session with current goroutine

NOTICE: MUST call `InitDefaultManager()` once before using this API

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 InitDefaultManager added in v0.0.2

func InitDefaultManager(opts ManagerOptions)

InitDefaultManager update and restart default manager. It accept argument opts and env config both.

NOTICE:

  • It use env SESSION_CONFIG_KEY prior to argument opts;
  • If both env and opts are empty, it won't reset manager;
  • For concurrent safety, you can only successfully reset manager ONCE.

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

NOTICE: MUST call `InitDefaultManager()` once before using this API

Types

type ManagerOptions

type ManagerOptions struct {
	// EnableImplicitlyTransmitAsync enables transparently transmit
	// current session to children goroutines
	//
	// WARNING: Once this option enables, if you want to use `pprof.Do()`, it 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

func DefaultManagerOptions added in v0.0.2

func DefaultManagerOptions() ManagerOptions

DefaultManagerOptions returns default options for the default manager

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

NOTICE: MUST call `InitDefaultManager()` once before using this API

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
// initialize default manager first
InitDefaultManager(DefaultManagerOptions())

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()
Output:

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) Export added in v0.0.2

func (self SessionCtx) Export() context.Context

Export exports underlying context

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 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) Export added in v0.0.2

func (self *SessionMap) Export() map[interface{}]interface{}

Export COPIES and exports underlying map

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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