{"id":19666597,"url":"https://github.com/xaionaro-go/gorex","last_synced_at":"2026-06-03T23:31:27.986Z","repository":{"id":57516665,"uuid":"245843085","full_name":"xaionaro-go/gorex","owner":"xaionaro-go","description":"goroutine mutual exclusion (aka recursive mutex for Go)","archived":false,"fork":false,"pushed_at":"2024-10-10T20:58:05.000Z","size":64,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-27T04:17:25.657Z","etag":null,"topics":["context","ctx","deadline","deadlock","debug","go","golang","goroutine","lock","locker","mutex","recursive","reentry","timeout","try","trylock"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xaionaro-go.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-03-08T15:53:12.000Z","updated_at":"2024-10-10T20:58:08.000Z","dependencies_parsed_at":"2022-09-26T18:01:23.417Z","dependency_job_id":null,"html_url":"https://github.com/xaionaro-go/gorex","commit_stats":null,"previous_names":["xaionaro-go/goroutine"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/xaionaro-go/gorex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fgorex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fgorex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fgorex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fgorex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xaionaro-go","download_url":"https://codeload.github.com/xaionaro-go/gorex/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xaionaro-go%2Fgorex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":285704833,"owners_count":27217837,"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","status":"online","status_checked_at":"2025-11-21T02:00:06.175Z","response_time":61,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["context","ctx","deadline","deadlock","debug","go","golang","goroutine","lock","locker","mutex","recursive","reentry","timeout","try","trylock"],"created_at":"2024-11-11T16:28:14.191Z","updated_at":"2025-11-21T23:02:50.600Z","avatar_url":"https://github.com/xaionaro-go.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![GoDoc](https://godoc.org/github.com/xaionaro-go/gorex?status.svg)](https://pkg.go.dev/github.com/xaionaro-go/gorex?tab=doc)\n[![go report](https://goreportcard.com/badge/github.com/xaionaro-go/gorex)](https://goreportcard.com/report/github.com/xaionaro-go/gorex)\n[![Build Status](https://travis-ci.org/xaionaro-go/gorex.svg?branch=master)](https://travis-ci.org/xaionaro-go/gorex)\n[![Coverage Status](https://coveralls.io/repos/github/xaionaro-go/gorex/badge.svg?branch=master)](https://coveralls.io/github/xaionaro-go/gorex?branch=master)\n\u003cp xmlns:dct=\"http://purl.org/dc/terms/\" xmlns:vcard=\"http://www.w3.org/2001/vcard-rdf/3.0#\"\u003e\n  \u003ca rel=\"license\"\n     href=\"http://creativecommons.org/publicdomain/zero/1.0/\"\u003e\n    \u003cimg src=\"http://i.creativecommons.org/p/zero/1.0/88x31.png\" style=\"border-style: none;\" alt=\"CC0\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n# gorex\n\n### `gorex == GORoutine mutual EXclusion`\n\nThis package implements `Mutex` and `RWMutex`. They are similar to `sync.Mutex` and `sync.RWMutex`, but\nthey track which goroutine locked the mutex and will not cause a deadlock if\nthe same goroutine will try to lock the same mutex again.\n\n```go\ntype myEntity struct {\n    gorex.Mutex\n}\n\nfunc (ent *myEntity) func1() {\n    ent.Lock()\n    defer ent.Unlock()\n\n    .. some stuff ..\n    ent.func2() // will not get a deadlock here!\n    .. other stuff ..\n}\n\nfunc (ent *myEntity) func2() {\n    ent.Lock()\n    defer ent.Unlock()\n\n    .. more stuff ..\n}\n```\n\nThe same in other syntax:\n```go\ntype myEntity struct {\n    gorex.Mutex\n}\n\nfunc (ent *myEntity) func1() {\n    ent.LockDo(func() {\n        .. some stuff ..\n        ent.func2() // will not get a deadlock here!\n        .. other stuff ..\n    })\n}\n\nfunc (ent *myEntity) func2() {\n    ent.LockDo(func(){\n        .. more stuff ..\n    })\n}\n```\n\n```go\nlocker := \u0026goroutine.RWMutex{}\n\nlocker.RLockDo(func() {\n    .. do some read-only stuff ..\n    if cond {\n      return\n    }\n    locker.LockDo(func() { // will not get a deadlock here!\n        .. do write stuff ..\n    })\n})\n```\n\n#### But...\n\nBut you still will get a deadlock if you do this way:\n```go\nvar locker = \u0026gorex.RWMutex{}\n\nfunc someFunc() {\n    locker.RLockDo(func() {\n        .. do some read-only stuff ..\n        if cond {\n          return\n        }\n        locker.LockDo(func() { // you will get a deadlock here!\n            .. do write stuff ..\n        })\n    })\n}()\n\nfunc main() {\n    go someFunc()\n    go someFunc()\n}\n```\nbecause there could be a situation that a resource is blocked by a `RLockDo` from\nboth goroutines and both goroutines waits (on `LockDo`) until other goroutine\nwill finish `RLockDo`. But still you will easily see the reason of deadlocks due\nto `LockDo`-s in the call stack trace.\n\n#### Benchmark\n\nIt's essentially slower than bare `sync.Mutex`/`sync.RWMutex`:\n\n```\ngoos: linux\ngoarch: amd64\npkg: github.com/xaionaro-go/gorex\nBenchmark/Lock-Unlock/single/sync.Mutex-8         \t77933413\t        15.2 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/Lock-Unlock/single/sync.RWMutex-8       \t46052574\t        26.1 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/Lock-Unlock/single/Mutex-8              \t20281420\t        58.6 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/Lock-Unlock/single/RWMutex-8            \t13518639\t        87.1 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/Lock-Unlock/parallel/sync.Mutex-8       \t10836991\t       111 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/Lock-Unlock/parallel/sync.RWMutex-8     \t 9065725\t       133 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/Lock-Unlock/parallel/Mutex-8            \t 9425310\t       123 ns/op\t       2 B/op\t       0 allocs/op\nBenchmark/Lock-Unlock/parallel/RWMutex-8          \t 5309696\t       213 ns/op\t       4 B/op\t       0 allocs/op\nBenchmark/RLock-RUnlock/single/sync.RWMutex-8     \t76609815\t        15.2 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/RLock-RUnlock/single/RWMutex-8          \t25071478\t        47.9 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/RLock-RUnlock/parallel/sync.RWMutex-8   \t25705654\t        48.3 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/RLock-RUnlock/parallel/RWMutex-8        \t14786738\t        80.9 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/Lock-ed:Lock-Unlock/single/Mutex-8      \t31392260\t        38.2 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/Lock-ed:Lock-Unlock/single/RWMutex-8    \t32588916\t        37.6 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/RLock-ed:RLock-RUnlock/single/RWMutex-8 \t26416754\t        46.1 ns/op\t       0 B/op\t       0 allocs/op\nBenchmark/RLock-ed:RLock-RUnlock/parallel/RWMutex-8         \t13113901\t        88.7 ns/op\t       0 B/op\t       0 allocs/op\nPASS\nok  \tgithub.com/xaionaro-go/gorex\t20.321s\n```\n\nBut sometimes it allows you to think more about strategic problems\n(\"this stuff should be edited atomically, so I'll be able to...\")\ninstead of wasting time on tactical problems (\"how to handle those locks\") :)\n\n## If you still have a Deadlock...\n\nOf course this package does not solve all possible reasons of deadlocks,\nbut it also provides a way to debug what's going on. First of all,\nI recommend you to use `LockDo` instead of bare `Lock` when possible:\n* It will make sure you haven't forgot to unlock the mutex.\n* It will be shown in the call stack trace (so you'll see what's going on).\n\nIf you already have a problem with a deadlock, then I recommend you to write\nand unit/integration test which can reproduce the deadlock situation and then\nlimit by time [`gorex.DefaultInfiniteContext`](https://pkg.go.dev/github.com/xaionaro-go/gorex?tab=doc#pkg-variables).\nOn a deadlock it will panic and will show the call stack trace of every routine\nwhich holds the lock.\n\nFor example in my case I saw:\n```\nmonopolized by:\n/home/xaionaro/.gimme/versions/go1.13.linux.amd64/src/runtime/proc.go:2664 (runtime.goexit1)\npanic: The InfiniteContext is done...\n```\nSo it seems a routine already exited (and never released the lock). So a support\nof the build tag `deadlockdebug` was added, which will print a call\nstack trace of a lock which was never released (and goroutine already exited). Specifically\nin my case it printed:\n```\n$ go test ./... -timeout 1s -bench=. -benchtime=100ms -tags deadlockdebug\n...\nan opened lock {LockerPtr:824636154976 IsWrite:true} which was never released (and the goroutine already exited):\n/home/xaionaro/go/pkg/mod/github.com/xaionaro-go/gorex@v0.0.0-20200308222358-b650fa4b5b14/rw_mutex.go:72 (github.com/xaionaro-go/gorex.(*RWMutex).lock)\n/home/xaionaro/go/pkg/mod/github.com/xaionaro-go/gorex@v0.0.0-20200308222358-b650fa4b5b14/rw_mutex.go:43 (github.com/xaionaro-go/gorex.(*RWMutex).Lock)\n/home/xaionaro/go/src/github.com/xaionaro-go/secureio/session.go:1480 (github.com/xaionaro-go/secureio.(*Session).sendDelayedNow)\n/home/xaionaro/go/src/github.com/xaionaro-go/secureio/session.go:1774 (github.com/xaionaro-go/secureio.(*Session).startKeyExchange.func2)\n/home/xaionaro/go/src/github.com/xaionaro-go/secureio/key_exchanger.go:449 (github.com/xaionaro-go/secureio.(*keyExchanger).sendSuccessNotifications)\n/home/xaionaro/go/src/github.com/xaionaro-go/secureio/key_exchanger.go:408 (github.com/xaionaro-go/secureio.(*keyExchanger).Handle.func2)\n/home/xaionaro/go/src/github.com/xaionaro-go/secureio/key_exchanger.go:242 (github.com/xaionaro-go/secureio.(*keyExchanger).LockDo)\n...\n```\nSo I opened line `session.go:1480` added `defer sess.delayedWriteBuf.Unlock()` and it fixed the problem :)\n\n## Comparison with other implementations\n\nI found 2 other implementations:\n* https://github.com/90TechSAS/go-recursive-mutex\n* https://github.com/vxcontrol/rmx\n\nThe first one is broken:\n```\npanic: unsupported go version go1.13\n```\n\nThe second one sleeps in terms of millisecond, which:\n* Give good result if the lock is short-living: performance is much better (than here).\n* Continuously consumes CPU resources on long-living locks, while I'm developing an application for mobile phones and would like to avoid such problems.\n* Does not support `RLock`/`RUnlock`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxaionaro-go%2Fgorex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxaionaro-go%2Fgorex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxaionaro-go%2Fgorex/lists"}