github.com/cloudwego/localsession@v0.0.2/README.md (about)

     1  # LocalSession
     2  
     3  ## Introduction
     4  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.
     5  
     6  ## Usage
     7  ### Session
     8  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:
     9  - `SessionCtx`: use std `context.Context` as underlying storage, which means data from different goroutines are isolated.
    10  - `SessionMap`: use std `map` as underlying storage, which means data from different goroutines are shared.
    11  
    12  Both implementations are **Concurrent Safe**.
    13  
    14  ### SessionManager
    15  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....)`.
    16  ```go
    17  import (
    18  	"context"
    19      "github.com/cloudwego/localsession"
    20  )
    21  
    22  // global manager
    23  var manager = localsession.NewSessionManager(ManagerOptions{
    24  	ShardNumber: 10,
    25  	EnableImplicitlyTransmitAsync: true,
    26  	GCInterval: time.Hour,
    27  })
    28  
    29  // global data
    30  var key, v = "a", "b"
    31  var key2, v2 = "c", "d"
    32  
    33  func ASSERT(v bool) {
    34  	if !v {
    35  		panic("not true!")
    36  	}
    37  }
    38  
    39  func main() {
    40      // get or initialize your context
    41      var ctx = context.Background()
    42      ctx = context.WithValue(ctx, key, v)
    43  
    44  	// initialize new session with context
    45  	var session = localsession.NewSessionCtx(ctx) 
    46  
    47  	// set specific key-value and update session
    48  	start := session.WithValue(key2, v2)
    49  
    50  	// set current session
    51  	manager.BindSession(start)
    52  
    53      // do somethings...
    54      
    55      // no need to pass context!
    56      GetDataX()
    57  }
    58  
    59  // read specific key under current session
    60  func GetDataX() {
    61      // val exists
    62  	val := manager.GetCurSession().Get(key) 
    63  	ASSERT(val == v)
    64  
    65      // val2 exists
    66  	val2 := manager.GetCurSession().Get(key2) 
    67  	ASSERT(val2 == v2)
    68  }
    69  ```
    70  
    71  We provide a globally default manager to manage session between different goroutines, as long as you set `InitDefaultManager()` first.
    72  
    73  ### Explicitly Transmit Async Context (Recommended)
    74  You can use `Go()` or `GoSession()` to explicitly transmit your context to other goroutines.
    75  
    76  ```go
    77  
    78  package main
    79  
    80  import (
    81  	"context"
    82      . "github.com/cloudwego/localsession"
    83  )
    84  
    85  func init() {
    86      // initialize default manager first
    87  	InitDefaultManager(DefaultManagerOptions())
    88  }
    89  
    90  func GetCurSession() Session {
    91  	s, ok := CurSession()
    92  	if !ok {
    93  		panic("can't get current seession!")
    94  	}
    95  	return s
    96  }
    97  
    98  func main() {
    99  	var ctx = context.Background()
   100  	var key, v = "a", "b"
   101  	var key2, v2 = "c", "d"
   102  	var sig = make(chan struct{})
   103  	var sig2 = make(chan struct{})
   104  
   105  	// initialize new session with context
   106  	var session = NewSessionCtx(ctx) // implementation...
   107  
   108  	// set specific key-value and update session
   109  	start := session.WithValue(key, v)
   110  
   111  	// set current session
   112  	BindSession(start)
   113  
   114  	// pass to new goroutine...
   115  	Go(func() {
   116  		// read specific key under current session
   117  		val := GetCurSession().Get(key) // val exists
   118  		ASSERT(val == v)
   119  		// doSomething....
   120  
   121  		// set specific key-value under current session
   122  		// NOTICE: current session won't change here
   123  		next := GetCurSession().WithValue(key2, v2)
   124  		val2 := GetCurSession().Get(key2) // val2 == nil
   125  		ASSERT(val2 == nil)
   126  
   127  		// pass both parent session and new session to sub goroutine
   128  		GoSession(next, func() {
   129  			// read specific key under current session
   130  			val := GetCurSession().Get(key) // val exists
   131  			ASSERT(val == v)
   132  
   133  			val2 := GetCurSession().Get(key2) // val2 exists
   134  			ASSERT(val2 == v2)
   135  			// doSomething....
   136  
   137  			sig2 <- struct{}{}
   138  
   139  			<-sig
   140  			ASSERT(GetCurSession().IsValid() == false) // current session is invalid
   141  
   142  			println("g2 done")
   143  			sig2 <- struct{}{}
   144  		})
   145  
   146  		Go(func() {
   147  			// read specific key under current session
   148  			val := GetCurSession().Get(key) // val exists
   149  			ASSERT(v == val)
   150  
   151  			val2 := GetCurSession().Get(key2) // val2 == nil
   152  			ASSERT(val2 == nil)
   153  			// doSomething....
   154  
   155  			sig2 <- struct{}{}
   156  
   157  			<-sig
   158  			ASSERT(GetCurSession().IsValid() == false) // current session is invalid
   159  
   160  			println("g3 done")
   161  			sig2 <- struct{}{}
   162  		})
   163  
   164  		BindSession(next)
   165  		val2 = GetCurSession().Get(key2) // val2 exists
   166  		ASSERT(v2 == val2)
   167  
   168  		sig2 <- struct{}{}
   169  
   170  		<-sig
   171  		ASSERT(next.IsValid() == false) // next is invalid
   172  
   173  		println("g1 done")
   174  		sig2 <- struct{}{}
   175  	})
   176  
   177  	<-sig2
   178  	<-sig2
   179  	<-sig2
   180  
   181  	val2 := GetCurSession().Get(key2) // val2 == nil
   182  	ASSERT(val2 == nil)
   183  
   184  	// initiatively ends the session,
   185  	// then all the inherited session (including next) will be disabled
   186  	session.Disable()
   187  	close(sig)
   188  
   189  	ASSERT(start.IsValid() == false) // start is invalid
   190  
   191  	<-sig2
   192  	<-sig2
   193  	<-sig2
   194  	println("g0 done")
   195  
   196  	UnbindSession()
   197  }
   198  ```
   199  
   200  ### Implicitly Transmit Async Context 
   201  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.
   202  ```go
   203  func ExampleSessionCtx_EnableImplicitlyTransmitAsync() {
   204  	// EnableImplicitlyTransmitAsync must be true 
   205  	ResetDefaultManager(ManagerOptions{
   206  		ShardNumber: 10,
   207  		EnableImplicitlyTransmitAsync: true,
   208  		GCInterval: time.Hour,
   209  	})
   210  
   211  	// WARNING: if you want to use `pprof.Do()`, it must be called before `BindSession()`, 
   212  	// otherwise transparently transmitting session will be dysfunctional
   213  	// labels := pprof.Labels("c", "d")
   214  	// pprof.Do(context.Background(), labels, func(ctx context.Context){})
   215  	
   216  	s := NewSessionMap(map[interface{}]interface{}{
   217  		"a": "b",
   218  	})
   219  	BindSession(s)
   220  
   221  	wg := sync.WaitGroup{}
   222  	wg.Add(3)
   223  	go func() {
   224  		defer wg.Done()
   225  		ASSERT("b" == mustCurSession().Get("a"))
   226  
   227  		go func() {
   228  			defer wg.Done()
   229  			ASSERT("b" == mustCurSession().Get("a"))
   230  		}()
   231  
   232  		ASSERT("b" == mustCurSession().Get("a"))
   233  		UnbindSession()
   234  		ASSERT(nil == mustCurSession())
   235  
   236  		go func() {
   237  			defer wg.Done()
   238  			ASSERT(nil == mustCurSession())
   239  		}()
   240  
   241  	}()
   242  	wg.Wait()
   243  }
   244  ```
   245  
   246  ## Community
   247  - Email: [conduct@cloudwego.io](conduct@cloudwego.io)
   248  - How to become a member: [COMMUNITY MEMBERSHIP](https://github.com/cloudwego/community/blob/main/COMMUNITY_MEMBERSHIP.md)
   249  - Issues: [Issues](https://github.com/cloudwego/localsession/issues)
   250  - Slack: Join our CloudWeGo community [Slack Channel](https://join.slack.com/t/cloudwego/shared_invite/zt-tmcbzewn-UjXMF3ZQsPhl7W3tEDZboA).