{"id":19674716,"url":"https://github.com/cloudwego/localsession","last_synced_at":"2025-04-29T02:30:24.660Z","repository":{"id":183421523,"uuid":"667784159","full_name":"cloudwego/localsession","owner":"cloudwego","description":"transparently transmit context within or between goroutines","archived":false,"fork":false,"pushed_at":"2025-01-10T11:31:44.000Z","size":42,"stargazers_count":23,"open_issues_count":1,"forks_count":3,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-04-05T12:33:10.726Z","etag":null,"topics":["concurrent-programming","localstorage"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloudwego.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-07-18T09:42:49.000Z","updated_at":"2025-04-04T03:55:52.000Z","dependencies_parsed_at":"2024-06-19T14:34:13.021Z","dependency_job_id":"c0b34ddb-4b5b-4ce6-8690-8655a8fe660f","html_url":"https://github.com/cloudwego/localsession","commit_stats":null,"previous_names":["cloudwego/localsession"],"tags_count":5,"template":false,"template_full_name":"cloudwego/.github","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudwego%2Flocalsession","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudwego%2Flocalsession/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudwego%2Flocalsession/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudwego%2Flocalsession/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloudwego","download_url":"https://codeload.github.com/cloudwego/localsession/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251420861,"owners_count":21586693,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["concurrent-programming","localstorage"],"created_at":"2024-11-11T17:19:31.319Z","updated_at":"2025-04-29T02:30:24.389Z","avatar_url":"https://github.com/cloudwego.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LocalSession\n\n## Introduction\nLocalSession 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.\n\n## Usage\n### Session\nSession 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:\n- `SessionCtx`: use std `context.Context` as underlying storage, which means data from different goroutines are isolated.\n- `SessionMap`: use std `map` as underlying storage, which means data from different goroutines are shared.\n\nBoth implementations are **Concurrent Safe**.\n\n### SessionManager\nSessionManager 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....)`.\n```go\nimport (\n\t\"context\"\n    \"github.com/cloudwego/localsession\"\n)\n\n// global manager\nvar manager = localsession.NewSessionManager(ManagerOptions{\n\tShardNumber: 10,\n\tEnableImplicitlyTransmitAsync: true,\n\tGCInterval: time.Hour,\n})\n\n// global data\nvar key, v = \"a\", \"b\"\nvar key2, v2 = \"c\", \"d\"\n\nfunc ASSERT(v bool) {\n\tif !v {\n\t\tpanic(\"not true!\")\n\t}\n}\n\nfunc main() {\n    // get or initialize your context\n    var ctx = context.Background()\n    ctx = context.WithValue(ctx, key, v)\n\n\t// initialize new session with context\n\tvar session = localsession.NewSessionCtx(ctx) \n\n\t// set specific key-value and update session\n\tstart := session.WithValue(key2, v2)\n\n\t// set current session\n\tmanager.BindSession(start)\n\n    // do somethings...\n    \n    // no need to pass context!\n    GetDataX()\n}\n\n// read specific key under current session\nfunc GetDataX() {\n    // val exists\n\tval := manager.GetCurSession().Get(key) \n\tASSERT(val == v)\n\n    // val2 exists\n\tval2 := manager.GetCurSession().Get(key2) \n\tASSERT(val2 == v2)\n}\n```\n\nWe provide a globally default manager to manage session between different goroutines, as long as you set `InitDefaultManager()` first.\n\n### Explicitly Transmit Async Context (Recommended)\nYou can use `Go()` or `GoSession()` to explicitly transmit your context to other goroutines.\n\n```go\n\npackage main\n\nimport (\n\t\"context\"\n    . \"github.com/cloudwego/localsession\"\n)\n\nfunc init() {\n    // initialize default manager first\n\tInitDefaultManager(DefaultManagerOptions())\n}\n\nfunc GetCurSession() Session {\n\ts, ok := CurSession()\n\tif !ok {\n\t\tpanic(\"can't get current seession!\")\n\t}\n\treturn s\n}\n\nfunc main() {\n\tvar ctx = context.Background()\n\tvar key, v = \"a\", \"b\"\n\tvar key2, v2 = \"c\", \"d\"\n\tvar sig = make(chan struct{})\n\tvar sig2 = make(chan struct{})\n\n\t// initialize new session with context\n\tvar session = NewSessionCtx(ctx) // implementation...\n\n\t// set specific key-value and update session\n\tstart := session.WithValue(key, v)\n\n\t// set current session\n\tBindSession(start)\n\n\t// pass to new goroutine...\n\tGo(func() {\n\t\t// read specific key under current session\n\t\tval := GetCurSession().Get(key) // val exists\n\t\tASSERT(val == v)\n\t\t// doSomething....\n\n\t\t// set specific key-value under current session\n\t\t// NOTICE: current session won't change here\n\t\tnext := GetCurSession().WithValue(key2, v2)\n\t\tval2 := GetCurSession().Get(key2) // val2 == nil\n\t\tASSERT(val2 == nil)\n\n\t\t// pass both parent session and new session to sub goroutine\n\t\tGoSession(next, func() {\n\t\t\t// read specific key under current session\n\t\t\tval := GetCurSession().Get(key) // val exists\n\t\t\tASSERT(val == v)\n\n\t\t\tval2 := GetCurSession().Get(key2) // val2 exists\n\t\t\tASSERT(val2 == v2)\n\t\t\t// doSomething....\n\n\t\t\tsig2 \u003c- struct{}{}\n\n\t\t\t\u003c-sig\n\t\t\tASSERT(GetCurSession().IsValid() == false) // current session is invalid\n\n\t\t\tprintln(\"g2 done\")\n\t\t\tsig2 \u003c- struct{}{}\n\t\t})\n\n\t\tGo(func() {\n\t\t\t// read specific key under current session\n\t\t\tval := GetCurSession().Get(key) // val exists\n\t\t\tASSERT(v == val)\n\n\t\t\tval2 := GetCurSession().Get(key2) // val2 == nil\n\t\t\tASSERT(val2 == nil)\n\t\t\t// doSomething....\n\n\t\t\tsig2 \u003c- struct{}{}\n\n\t\t\t\u003c-sig\n\t\t\tASSERT(GetCurSession().IsValid() == false) // current session is invalid\n\n\t\t\tprintln(\"g3 done\")\n\t\t\tsig2 \u003c- struct{}{}\n\t\t})\n\n\t\tBindSession(next)\n\t\tval2 = GetCurSession().Get(key2) // val2 exists\n\t\tASSERT(v2 == val2)\n\n\t\tsig2 \u003c- struct{}{}\n\n\t\t\u003c-sig\n\t\tASSERT(next.IsValid() == false) // next is invalid\n\n\t\tprintln(\"g1 done\")\n\t\tsig2 \u003c- struct{}{}\n\t})\n\n\t\u003c-sig2\n\t\u003c-sig2\n\t\u003c-sig2\n\n\tval2 := GetCurSession().Get(key2) // val2 == nil\n\tASSERT(val2 == nil)\n\n\t// initiatively ends the session，\n\t// then all the inherited session (including next) will be disabled\n\tsession.Disable()\n\tclose(sig)\n\n\tASSERT(start.IsValid() == false) // start is invalid\n\n\t\u003c-sig2\n\t\u003c-sig2\n\t\u003c-sig2\n\tprintln(\"g0 done\")\n\n\tUnbindSession()\n}\n```\n\n### Implicitly Transmit Async Context \nYou can also set option `EnableImplicitlyTransmitAsync` as true to transparently transmit context. Once the option is enabled, every goroutine will inherit their parent's session.\n```go\nfunc ExampleSessionCtx_EnableImplicitlyTransmitAsync() {\n\t// EnableImplicitlyTransmitAsync must be true \n\tResetDefaultManager(ManagerOptions{\n\t\tShardNumber: 10,\n\t\tEnableImplicitlyTransmitAsync: true,\n\t\tGCInterval: time.Hour,\n\t})\n\n\t// WARNING: if you want to use `pprof.Do()`, it must be called before `BindSession()`, \n\t// otherwise transparently transmitting session will be dysfunctional\n\t// labels := pprof.Labels(\"c\", \"d\")\n\t// pprof.Do(context.Background(), labels, func(ctx context.Context){})\n\t\n\ts := NewSessionMap(map[interface{}]interface{}{\n\t\t\"a\": \"b\",\n\t})\n\tBindSession(s)\n\n\twg := sync.WaitGroup{}\n\twg.Add(3)\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tASSERT(\"b\" == mustCurSession().Get(\"a\"))\n\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tASSERT(\"b\" == mustCurSession().Get(\"a\"))\n\t\t}()\n\n\t\tASSERT(\"b\" == mustCurSession().Get(\"a\"))\n\t\tUnbindSession()\n\t\tASSERT(nil == mustCurSession())\n\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tASSERT(nil == mustCurSession())\n\t\t}()\n\n\t}()\n\twg.Wait()\n}\n```\n\n## Community\n- Email: [conduct@cloudwego.io](conduct@cloudwego.io)\n- How to become a member: [COMMUNITY MEMBERSHIP](https://github.com/cloudwego/community/blob/main/COMMUNITY_MEMBERSHIP.md)\n- Issues: [Issues](https://github.com/cloudwego/localsession/issues)\n- Slack: Join our CloudWeGo community [Slack Channel](https://join.slack.com/t/cloudwego/shared_invite/zt-tmcbzewn-UjXMF3ZQsPhl7W3tEDZboA).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudwego%2Flocalsession","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloudwego%2Flocalsession","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudwego%2Flocalsession/lists"}