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  }