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).