Skip to content

Commit

Permalink
refator: DON'T expose default manager object (#2)
Browse files Browse the repository at this point in the history
* refator: DON'T expose default manager object

* add nocheckptr

* reduce race test

* update env format
  • Loading branch information
AsterDY authored Jul 26, 2023
1 parent 7279f24 commit 66bf42e
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 33 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
# ${{ runner.os }}-go-

- name: Unit Test
run: go test -race -covermode=atomic -coverprofile=coverage.out ./...
run: go test -covermode=atomic -coverprofile=coverage.out ./...

- name: Benchmark
run: go test -bench=. -benchmem -run=none ./...
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,11 @@ You can also set option `EnableImplicitlyTransmitAsync` as true to transparently
```go
func ExampleSessionCtx_EnableImplicitlyTransmitAsync() {
// rest DefaultManager with new Options
SetDefaultManager(NewSessionManager(ManagerOptions{
ResetDefaultManager(ManagerOptions{
ShardNumber: 10,
EnableImplicitlyTransmitAsync: true,
GCInterval: time.Hour,
}))
})

// WARNING: pprof.Do() must be called before BindSession(),
// otherwise transparently transmitting session will be dysfunctional
Expand Down
72 changes: 69 additions & 3 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package localsession

import (
"context"
"os"
"runtime/pprof"
"sync"
"testing"
Expand All @@ -26,12 +27,76 @@ import (

const N = 10

func TestResetDefaultManager(t *testing.T) {
old := defaultManagerObj

t.Run("arg", func(t *testing.T) {
defaultManagerOnce = sync.Once{}
exp := ManagerOptions{
ShardNumber: 1,
EnableImplicitlyTransmitAsync: true,
GCInterval: time.Second*2,
}
ResetDefaultManager(exp)
act := defaultManagerObj.Options()
require.Equal(t, exp, act)
})

t.Run("arg", func(t *testing.T) {
defaultManagerOnce = sync.Once{}
env := `true,10,10s`
os.Setenv(SESSION_CONFIG_KEY, env)
ResetDefaultManager(ManagerOptions{})
act := defaultManagerObj.Options()
exp := DefaultManagerOptions()
exp.ShardNumber = 10
exp.EnableImplicitlyTransmitAsync = true
exp.GCInterval = time.Second*10
require.Equal(t, exp, act)

defaultManagerOnce = sync.Once{}
env = `,1000`
os.Setenv(SESSION_CONFIG_KEY, env)
ResetDefaultManager(ManagerOptions{})
act = defaultManagerObj.Options()
exp = DefaultManagerOptions()
exp.ShardNumber = 1000
require.Equal(t, exp, act)

defaultManagerOnce = sync.Once{}
env = `,1,2s`
os.Setenv(SESSION_CONFIG_KEY, env)
ResetDefaultManager(ManagerOptions{})
act = defaultManagerObj.Options()
exp = DefaultManagerOptions()
exp.ShardNumber = 1
exp.GCInterval = time.Second*2
require.Equal(t, exp, act)

defaultManagerOnce = sync.Once{}
env = `true,,2s`
os.Setenv(SESSION_CONFIG_KEY, env)
ResetDefaultManager(ManagerOptions{})
act = defaultManagerObj.Options()
exp = DefaultManagerOptions()
exp.EnableImplicitlyTransmitAsync = true
exp.GCInterval = time.Second*2
require.Equal(t, exp, act)
})

defaultManagerObj = old
defaultManagerOnce = sync.Once{}
}

//
//go:nocheckptr
func TestTransparentTransmitAsync(t *testing.T) {
old := defaultManagerObj
SetDefaultManager(NewSessionManager(ManagerOptions{
ResetDefaultManager(ManagerOptions{
ShardNumber: 10,
EnableImplicitlyTransmitAsync: true,
}))
GCInterval: time.Hour,
})
s := NewSessionMap(map[interface{}]interface{}{
"a": "b",
})
Expand Down Expand Up @@ -66,7 +131,8 @@ func TestTransparentTransmitAsync(t *testing.T) {
}()

wg.Wait()
SetDefaultManager(*old)
defaultManagerObj = old
defaultManagerOnce = sync.Once{}
}

func TestSessionTimeout(t *testing.T) {
Expand Down
4 changes: 2 additions & 2 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ func GetCurSession() Session {

func ExampleSessionCtx_EnableImplicitlyTransmitAsync() {
// rest DefaultManager with new Options
SetDefaultManager(NewSessionManager(ManagerOptions{
ResetDefaultManager(ManagerOptions{
ShardNumber: 10,
EnableImplicitlyTransmitAsync: true,
GCInterval: time.Hour,
}))
})

// WARNING: pprof.Do() must be called before BindSession(),
// otherwise transparently transmitting session will be dysfunctional
Expand Down
88 changes: 63 additions & 25 deletions gls.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,45 +16,83 @@ package localsession

import (
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
)

const (
// DefaultShardNum set the sharding number of id->session map for default SessionManager
DefaultShardNum = 100

// DefaultGCInterval set the GC interval for default SessionManager
DefaultGCInterval = time.Hour

// DefaultEnableImplicitlyTransmitAsync enables TransparentTransmitAsync for default SessionManager
DefaultEnableImplicitlyTransmitAsync = false
)
// 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 '1h' means one hour
// Once the key is set, default option values will be set if the option value doesn't exist.
const SESSION_CONFIG_KEY = "CLOUDWEGO_SESSION_CONFIG_KEY"

var (
defaultManagerObj *SessionManager
defaultManagerObj *SessionManager
defaultManagerOnce sync.Once
)

func init() {
obj := NewSessionManager(ManagerOptions{
EnableImplicitlyTransmitAsync: DefaultEnableImplicitlyTransmitAsync,
ShardNumber: DefaultShardNum,
GCInterval: DefaultGCInterval,
})
obj := NewSessionManager(DefaultManagerOptions())
defaultManagerObj = &obj
}

// SetDefaultManager updates default SessionManager to m
func SetDefaultManager(m SessionManager) {
if defaultManagerObj != nil {
defaultManagerObj.Close()
// DefaultManagerOptions returns default options for the default manager
func DefaultManagerOptions() ManagerOptions {
return ManagerOptions{
ShardNumber: 100,
GCInterval: time.Hour,
EnableImplicitlyTransmitAsync: false,
}
defaultManagerObj = &m
}

// GetDefaultManager returns a copy of default SessionManager
// warning: use it only for state check
func GetDefaultManager() SessionManager {
return *defaultManagerObj
// ResetDefaultManager update and restart manager,
// which means previous sessions (if any) will be cleared.
// 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.
//
//go:nocheckptr
func ResetDefaultManager(opts ManagerOptions) {
// check env first
if env := os.Getenv(SESSION_CONFIG_KEY); env != "" {
envs := strings.Split(env, ",")
opts = DefaultManagerOptions()

// parse first option as EnableTransparentTransmitAsync
if strings.ToLower(envs[0]) == "true" {
opts.EnableImplicitlyTransmitAsync = true
}

// parse first option as ShardNumber
if len(envs) > 1 {
if opt, err := strconv.Atoi(envs[1]); err == nil {
opts.ShardNumber = opt
}
}

// parse third option as EnableTransparentTransmitAsync
if len(envs) > 2 {
if d, err := time.ParseDuration(envs[2]); err == nil && d > time.Second {
opts.GCInterval = d
}
}
}

defaultManagerOnce.Do(func() {
if defaultManagerObj != nil {
defaultManagerObj.Close()
}
obj := NewSessionManager(opts)
defaultManagerObj = &obj
})
}

// CurSession gets the session for current goroutine
Expand Down
2 changes: 2 additions & 0 deletions manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func (self SessionManager) Options() ManagerOptions {
// SessionID is the identity of a session
type SessionID uint64

//go:nocheckptr
func (s *shard) Load(id SessionID) (Session, bool) {
s.lock.RLock()
session, ok := s.m[id]
Expand Down Expand Up @@ -180,6 +181,7 @@ func (self SessionManager) GC() {
atomic.StoreUint32(&self.inGC, 0)
}


// startGC start a scheduled goroutine to call GC() according to GCInterval
func (self *SessionManager) startGC() {
if self.opts.GCInterval < time.Second {
Expand Down

0 comments on commit 66bf42e

Please sign in to comment.