github.com/cloudwego/localsession@v0.0.2/api_test.go (about) 1 // Copyright 2023 CloudWeGo Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package localsession 16 17 import ( 18 "context" 19 "os" 20 "runtime/pprof" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/stretchr/testify/require" 26 ) 27 28 const N = 10 29 30 func TestMain(m *testing.M) { 31 InitDefaultManager(DefaultManagerOptions()) 32 m.Run() 33 } 34 35 func TestResetDefaultManager(t *testing.T) { 36 old := defaultManagerObj 37 38 t.Run("arg", func(t *testing.T) { 39 defaultManagerOnce = sync.Once{} 40 exp := ManagerOptions{ 41 ShardNumber: 1, 42 EnableImplicitlyTransmitAsync: true, 43 GCInterval: time.Second * 2, 44 } 45 InitDefaultManager(exp) 46 act := defaultManagerObj.Options() 47 require.Equal(t, exp, act) 48 }) 49 50 t.Run("arg", func(t *testing.T) { 51 defaultManagerOnce = sync.Once{} 52 env := `true,10,10s` 53 os.Setenv(SESSION_CONFIG_KEY, env) 54 exp := DefaultManagerOptions() 55 InitDefaultManager(exp) 56 act := defaultManagerObj.Options() 57 exp.ShardNumber = 10 58 exp.EnableImplicitlyTransmitAsync = true 59 exp.GCInterval = time.Second * 10 60 require.Equal(t, exp, act) 61 62 defaultManagerOnce = sync.Once{} 63 env = `,1000` 64 os.Setenv(SESSION_CONFIG_KEY, env) 65 exp = DefaultManagerOptions() 66 InitDefaultManager(exp) 67 act = defaultManagerObj.Options() 68 exp.ShardNumber = 1000 69 require.Equal(t, exp, act) 70 71 defaultManagerOnce = sync.Once{} 72 env = `,1,2s` 73 os.Setenv(SESSION_CONFIG_KEY, env) 74 exp = DefaultManagerOptions() 75 InitDefaultManager(exp) 76 act = defaultManagerObj.Options() 77 exp.ShardNumber = 1 78 exp.GCInterval = time.Second * 2 79 require.Equal(t, exp, act) 80 81 defaultManagerOnce = sync.Once{} 82 env = `true,,2s` 83 os.Setenv(SESSION_CONFIG_KEY, env) 84 exp = DefaultManagerOptions() 85 InitDefaultManager(exp) 86 act = defaultManagerObj.Options() 87 exp.EnableImplicitlyTransmitAsync = true 88 exp.GCInterval = time.Second * 2 89 require.Equal(t, exp, act) 90 }) 91 92 defaultManagerObj = old 93 defaultManagerOnce = sync.Once{} 94 } 95 96 //go:nocheckptr 97 func TestTransparentTransmitAsync(t *testing.T) { 98 old := defaultManagerObj 99 InitDefaultManager(ManagerOptions{ 100 ShardNumber: 10, 101 EnableImplicitlyTransmitAsync: true, 102 GCInterval: time.Second * 2, 103 }) 104 s := NewSessionMap(map[interface{}]interface{}{ 105 "a": "b", 106 }) 107 108 labels := pprof.Labels("c", "d") 109 110 // WARNING: pprof.Do() must be called before BindSession(), 111 // otherwise transparently transmitting session will be dysfunctional 112 pprof.Do(context.Background(), labels, func(ctx context.Context) {}) 113 114 BindSession(s) 115 116 wg := sync.WaitGroup{} 117 wg.Add(3) 118 go func() { 119 defer wg.Done() 120 require.Equal(t, "b", mustCurSession().Get("a")) 121 122 go func() { 123 defer wg.Done() 124 require.Equal(t, "b", mustCurSession().Get("a")) 125 }() 126 127 require.Equal(t, "b", mustCurSession().Get("a")) 128 UnbindSession() 129 require.Nil(t, mustCurSession()) 130 131 go func() { 132 defer wg.Done() 133 require.Nil(t, mustCurSession()) 134 }() 135 }() 136 137 wg.Wait() 138 defaultManagerObj = old 139 defaultManagerOnce = sync.Once{} 140 } 141 142 func TestSessionTimeout(t *testing.T) { 143 s := NewSessionCtxWithTimeout(context.Background(), time.Second) 144 ss := s.WithValue(1, 2) 145 m := NewSessionMapWithTimeout(map[interface{}]interface{}{}, time.Second) 146 mm := m.WithValue(1, 2) 147 time.Sleep(time.Second * 2) 148 require.False(t, ss.IsValid()) 149 require.False(t, mm.IsValid()) 150 } 151 152 func TestSessionCtx(t *testing.T) { 153 var ctx = context.Background() 154 var key, v = "a", "b" 155 var key2, v2 = "c", "d" 156 var sig = make(chan struct{}) 157 var sig2 = make(chan struct{}) 158 159 // initialize new session with context 160 var session = NewSessionCtx(ctx) // implementation... 161 162 // set specific key-value and update session 163 start := session.WithValue(key, v) 164 165 // set current session 166 BindSession(start) 167 168 // pass to new goroutine... 169 Go(func() { 170 // read specific key under current session 171 val := mustCurSession().Get(key) // val exists 172 require.Equal(t, v, val) 173 // doSomething.... 174 175 // set specific key-value under current session 176 // NOTICE: current session won't change here 177 next := mustCurSession().WithValue(key2, v2) 178 val2 := mustCurSession().Get(key2) // val2 == nil 179 require.Nil(t, val2) 180 181 // pass both parent session and new session to sub goroutine 182 GoSession(next, func() { 183 // read specific key under current session 184 val := mustCurSession().Get(key) // val exists 185 require.Equal(t, v, val) 186 187 val2 := mustCurSession().Get(key2) // val2 exists 188 require.Equal(t, v2, val2) 189 // doSomething.... 190 191 sig2 <- struct{}{} 192 193 <-sig 194 require.False(t, mustCurSession().IsValid()) // current session is invalid 195 196 println("g2 done") 197 sig2 <- struct{}{} 198 }) 199 200 Go(func() { 201 // read specific key under current session 202 val := mustCurSession().Get(key) // val exists 203 require.Equal(t, v, val) 204 205 val2 := mustCurSession().Get(key2) // val2 == nil 206 require.Nil(t, val2) 207 // doSomething.... 208 209 sig2 <- struct{}{} 210 211 <-sig 212 require.False(t, mustCurSession().IsValid()) // current session is invalid 213 214 println("g3 done") 215 sig2 <- struct{}{} 216 }) 217 218 BindSession(next) 219 val2 = mustCurSession().Get(key2) // val2 exists 220 require.Equal(t, v2, val2) 221 222 sig2 <- struct{}{} 223 224 <-sig 225 require.False(t, next.IsValid()) // next is invalid 226 227 println("g1 done") 228 sig2 <- struct{}{} 229 }) 230 231 <-sig2 232 <-sig2 233 <-sig2 234 235 val2 := mustCurSession().Get(key2) // val2 == nil 236 require.Nil(t, val2) 237 238 // initiatively ends the session, 239 // then all the inherited session (including next) will be disabled 240 session.Disable() 241 close(sig) 242 243 require.False(t, start.IsValid()) // start is invalid 244 245 <-sig2 246 <-sig2 247 <-sig2 248 println("g0 done") 249 250 UnbindSession() 251 } 252 253 func mustCurSession() Session { 254 s, _ := CurSession() 255 return s 256 } 257 258 func TestSessionMap(t *testing.T) { 259 var key, v = "a", "b" 260 var key2, v2 = "c", "d" 261 var sig = make(chan struct{}) 262 var sig2 = make(chan struct{}) 263 264 // initialize new session with context 265 var session = NewSessionMap(map[interface{}]interface{}{}) // implementation... 266 267 // set specific key-value and update session 268 start := session.WithValue(key, v) 269 270 // set current session 271 BindSession(start) 272 273 // pass to new goroutine... 274 Go(func() { 275 // read specific key under current session 276 val := mustCurSession().Get(key) // val exists 277 require.Equal(t, v, val) 278 // doSomething.... 279 280 // set specific key-value under current session 281 // NOTICE: current session won't change here 282 next := mustCurSession().WithValue(key2, v2) 283 val2 := mustCurSession().Get(key2) // val2 exist 284 require.Equal(t, v2, val2) 285 286 // pass both parent session and new session to sub goroutine 287 GoSession(next, func() { 288 // read specific key under current session 289 val := mustCurSession().Get(key) // val exists 290 require.Equal(t, v, val) 291 292 val2 := mustCurSession().Get(key2) // val2 exists 293 require.Equal(t, v2, val2) 294 // doSomething.... 295 296 sig2 <- struct{}{} 297 298 <-sig 299 require.False(t, mustCurSession().IsValid()) // current session is invalid 300 301 println("g2 done") 302 sig2 <- struct{}{} 303 }) 304 305 Go(func() { 306 // read specific key under current session 307 val := mustCurSession().Get(key) // val exists 308 require.Equal(t, v, val) 309 310 val2 := mustCurSession().Get(key2) // val2 exist 311 require.Equal(t, v2, val2) 312 // doSomething.... 313 314 sig2 <- struct{}{} 315 316 <-sig 317 require.False(t, mustCurSession().IsValid()) // current session is invalid 318 319 println("g3 done") 320 sig2 <- struct{}{} 321 }) 322 323 BindSession(next) 324 val2 = mustCurSession().Get(key2) // val2 exists 325 require.Equal(t, v2, val2) 326 327 sig2 <- struct{}{} 328 329 <-sig 330 require.False(t, next.IsValid()) // next is invalid 331 332 println("g1 done") 333 sig2 <- struct{}{} 334 }) 335 336 <-sig2 337 <-sig2 338 <-sig2 339 340 val2 := mustCurSession().Get(key2) // val2 exists 341 require.Equal(t, v2, val2) 342 343 // initiatively ends the session, 344 // then all the inherited session (including next) will be disabled 345 session.Disable() 346 close(sig) 347 348 require.False(t, start.IsValid()) // start is invalid 349 350 <-sig2 351 <-sig2 352 <-sig2 353 println("g0 done") 354 355 UnbindSession() 356 } 357 358 func TestSessionManager_GC(t *testing.T) { 359 inter := time.Second * 2 360 sd := 10 361 manager := NewSessionManager(ManagerOptions{ 362 ShardNumber: sd, 363 GCInterval: inter, 364 }) 365 366 var N = 1000 367 for i := 0; i < N; i++ { 368 m := map[interface{}]interface{}{} 369 s := NewSessionMap(m) 370 manager.BindSession(SessionID(i), s) 371 if i%2 == 1 { 372 s.Disable() 373 } 374 } 375 for _, shard := range manager.shards { 376 shard.lock.Lock() 377 l := len(shard.m) 378 shard.lock.Unlock() 379 require.Equal(t, N/sd, l) 380 } 381 time.Sleep(inter + inter>>1) 382 sum := 0 383 for _, shard := range manager.shards { 384 shard.lock.Lock() 385 l := len(shard.m) 386 shard.lock.Unlock() 387 sum += l 388 } 389 require.Equal(t, N/2, sum) 390 } 391 392 func BenchmarkSessionManager_CurSession(b *testing.B) { 393 s := NewSessionCtx(context.Background()) 394 395 b.Run("sync", func(b *testing.B) { 396 BindSession(s) 397 for i := 0; i < b.N; i++ { 398 _ = mustCurSession() 399 } 400 UnbindSession() 401 }) 402 403 b.Run("parallel", func(b *testing.B) { 404 b.RunParallel(func(p *testing.PB) { 405 BindSession(s) 406 for p.Next() { 407 _ = mustCurSession() 408 } 409 UnbindSession() 410 }) 411 }) 412 } 413 414 func BenchmarkSessionManager_BindSession(b *testing.B) { 415 s := NewSessionCtx(context.Background()) 416 417 b.Run("sync", func(b *testing.B) { 418 for i := 0; i < b.N; i++ { 419 BindSession(s) 420 } 421 }) 422 423 b.Run("parallel", func(b *testing.B) { 424 b.RunParallel(func(p *testing.PB) { 425 for p.Next() { 426 BindSession(s) 427 } 428 }) 429 }) 430 } 431 432 func BenchmarkSessionCtx_WithValue(b *testing.B) { 433 s := NewSessionCtx(context.Background()) 434 var ss Session = s 435 for i := 0; i < N; i++ { 436 ss = ss.WithValue(i, i) 437 } 438 439 b.Run("sync", func(b *testing.B) { 440 for i := 0; i < b.N; i++ { 441 _ = ss.WithValue(N/2, -1) 442 } 443 }) 444 445 b.Run("parallel", func(b *testing.B) { 446 b.RunParallel(func(p *testing.PB) { 447 for p.Next() { 448 _ = ss.WithValue(N/2, -1) 449 } 450 }) 451 }) 452 } 453 454 func BenchmarkSessionCtx_Get(b *testing.B) { 455 s := NewSessionCtx(context.Background()) 456 var ss Session = s 457 for i := 0; i < N; i++ { 458 ss = ss.WithValue(i, i) 459 } 460 461 b.Run("sync", func(b *testing.B) { 462 for i := 0; i < b.N; i++ { 463 _ = ss.Get(N / 2) 464 } 465 }) 466 467 b.Run("parallel", func(b *testing.B) { 468 b.RunParallel(func(p *testing.PB) { 469 for p.Next() { 470 _ = ss.Get(N / 2) 471 } 472 }) 473 }) 474 } 475 476 func BenchmarkSessionMap_WithValue(b *testing.B) { 477 s := NewSessionMap(map[interface{}]interface{}{}) 478 var ss Session = s 479 for i := 0; i < N; i++ { 480 ss = ss.WithValue(i, i) 481 } 482 483 b.Run("sync", func(b *testing.B) { 484 for i := 0; i < b.N; i++ { 485 _ = ss.WithValue(N/2, -1) 486 } 487 }) 488 489 b.Run("parallel", func(b *testing.B) { 490 b.RunParallel(func(p *testing.PB) { 491 for p.Next() { 492 _ = ss.WithValue(N/2, -1) 493 } 494 }) 495 }) 496 } 497 498 func BenchmarkSessionMap_Get(b *testing.B) { 499 s := NewSessionMap(map[interface{}]interface{}{}) 500 var ss Session = s 501 for i := 0; i < N; i++ { 502 ss = ss.WithValue(i, i) 503 } 504 505 b.Run("sync", func(b *testing.B) { 506 for i := 0; i < b.N; i++ { 507 _ = ss.Get(N / 2) 508 } 509 }) 510 511 b.Run("parallel", func(b *testing.B) { 512 b.RunParallel(func(p *testing.PB) { 513 for p.Next() { 514 _ = ss.Get(N / 2) 515 } 516 }) 517 }) 518 } 519 520 func BenchmarkGLS_Get(b *testing.B) { 521 s := NewSessionCtx(context.Background()) 522 var ss Session = s 523 for i := 0; i < N; i++ { 524 ss = ss.WithValue(i, i) 525 } 526 527 b.Run("sync", func(b *testing.B) { 528 BindSession(ss) 529 for i := 0; i < b.N; i++ { 530 _ = mustCurSession().Get(N / 2) 531 } 532 UnbindSession() 533 }) 534 535 b.Run("parallel", func(b *testing.B) { 536 b.RunParallel(func(p *testing.PB) { 537 BindSession(ss) 538 for p.Next() { 539 _ = mustCurSession().Get(N / 2) 540 } 541 UnbindSession() 542 }) 543 }) 544 } 545 546 func BenchmarkGLS_Set(b *testing.B) { 547 s := NewSessionCtx(context.Background()) 548 var ss Session = s 549 550 for i := 0; i < N; i++ { 551 ss = ss.WithValue(i, i) 552 } 553 554 b.Run("sync", func(b *testing.B) { 555 BindSession(ss) 556 for i := 0; i < b.N; i++ { 557 BindSession(mustCurSession().WithValue(N/2, -1)) 558 } 559 UnbindSession() 560 }) 561 562 b.Run("parallel", func(b *testing.B) { 563 b.RunParallel(func(p *testing.PB) { 564 BindSession(ss) 565 for p.Next() { 566 BindSession(mustCurSession().WithValue(N/2, -1)) 567 } 568 UnbindSession() 569 }) 570 }) 571 }