diff --git a/dataext/casMutex.go b/dataext/casMutex.go new file mode 100644 index 0000000..4782a54 --- /dev/null +++ b/dataext/casMutex.go @@ -0,0 +1,254 @@ +package dataext + +import ( + "context" + "golang.org/x/sync/semaphore" + "runtime" + "sync" + "sync/atomic" + "time" + "unsafe" +) + +// from https://github.com/viney-shih/go-lock/blob/2f19fd8ce335e33e0ab9dccb1ff2ce820c3da332/cas.go + +// CASMutex is the struct implementing RWMutex with CAS mechanism. +type CASMutex struct { + state casState + turnstile *semaphore.Weighted + + broadcastChan chan struct{} + broadcastMut sync.RWMutex +} + +func NewCASMutex() *CASMutex { + return &CASMutex{ + state: casStateNoLock, + turnstile: semaphore.NewWeighted(1), + broadcastChan: make(chan struct{}), + } +} + +type casState int32 + +const ( + casStateUndefined casState = iota - 2 // -2 + casStateWriteLock // -1 + casStateNoLock // 0 + casStateReadLock // >= 1 +) + +func (m *CASMutex) getState(n int32) casState { + switch st := casState(n); { + case st == casStateWriteLock: + fallthrough + case st == casStateNoLock: + return st + case st >= casStateReadLock: + return casStateReadLock + default: + // actually, it should not happened. + return casStateUndefined + } +} + +func (m *CASMutex) listen() <-chan struct{} { + m.broadcastMut.RLock() + defer m.broadcastMut.RUnlock() + + return m.broadcastChan +} + +func (m *CASMutex) broadcast() { + newCh := make(chan struct{}) + + m.broadcastMut.Lock() + ch := m.broadcastChan + m.broadcastChan = newCh + m.broadcastMut.Unlock() + + close(ch) +} + +func (m *CASMutex) tryLock(ctx context.Context) bool { + for { + broker := m.listen() + if atomic.CompareAndSwapInt32( + (*int32)(unsafe.Pointer(&m.state)), + int32(casStateNoLock), + int32(casStateWriteLock), + ) { + return true + } + + if ctx == nil { + return false + } + + select { + case <-ctx.Done(): + // timeout or cancellation + return false + case <-broker: + // waiting for signal triggered by m.broadcast() and trying again. + } + } +} + +// TryLockWithContext attempts to acquire the lock, blocking until resources +// are available or ctx is done (timeout or cancellation). +func (m *CASMutex) TryLockWithContext(ctx context.Context) bool { + if err := m.turnstile.Acquire(ctx, 1); err != nil { + // Acquire failed due to timeout or cancellation + return false + } + + defer m.turnstile.Release(1) + + return m.tryLock(ctx) +} + +// Lock acquires the lock. +// If it is currently held by others, Lock will wait until it has a chance to acquire it. +func (m *CASMutex) Lock() { + ctx := context.Background() + + m.TryLockWithContext(ctx) +} + +// TryLock attempts to acquire the lock without blocking. +// Return false if someone is holding it now. +func (m *CASMutex) TryLock() bool { + if !m.turnstile.TryAcquire(1) { + return false + } + + defer m.turnstile.Release(1) + + return m.tryLock(nil) +} + +// TryLockWithTimeout attempts to acquire the lock within a period of time. +// Return false if spending time is more than duration and no chance to acquire it. +func (m *CASMutex) TryLockWithTimeout(duration time.Duration) bool { + ctx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + return m.TryLockWithContext(ctx) +} + +// Unlock releases the lock. +func (m *CASMutex) Unlock() { + if ok := atomic.CompareAndSwapInt32( + (*int32)(unsafe.Pointer(&m.state)), + int32(casStateWriteLock), + int32(casStateNoLock), + ); !ok { + panic("Unlock failed") + } + + m.broadcast() +} + +func (m *CASMutex) rTryLock(ctx context.Context) bool { + for { + broker := m.listen() + n := atomic.LoadInt32((*int32)(unsafe.Pointer(&m.state))) + st := m.getState(n) + switch st { + case casStateNoLock, casStateReadLock: + if atomic.CompareAndSwapInt32((*int32)(unsafe.Pointer(&m.state)), n, n+1) { + return true + } + } + + if ctx == nil { + return false + } + + select { + case <-ctx.Done(): + // timeout or cancellation + return false + default: + switch st { + // read-lock failed due to concurrence issue, try again immediately + case casStateNoLock, casStateReadLock: + runtime.Gosched() // allow other goroutines to do stuff. + continue + } + } + + select { + case <-ctx.Done(): + // timeout or cancellation + return false + case <-broker: + // waiting for signal triggered by m.broadcast() and trying again. + } + } +} + +// RTryLockWithContext attempts to acquire the read lock, blocking until resources +// are available or ctx is done (timeout or cancellation). +func (m *CASMutex) RTryLockWithContext(ctx context.Context) bool { + if err := m.turnstile.Acquire(ctx, 1); err != nil { + // Acquire failed due to timeout or cancellation + return false + } + + m.turnstile.Release(1) + + return m.rTryLock(ctx) +} + +// RLock acquires the read lock. +// If it is currently held by others writing, RLock will wait until it has a chance to acquire it. +func (m *CASMutex) RLock() { + ctx := context.Background() + + m.RTryLockWithContext(ctx) +} + +// RTryLock attempts to acquire the read lock without blocking. +// Return false if someone is writing it now. +func (m *CASMutex) RTryLock() bool { + if !m.turnstile.TryAcquire(1) { + return false + } + + m.turnstile.Release(1) + + return m.rTryLock(nil) +} + +// RTryLockWithTimeout attempts to acquire the read lock within a period of time. +// Return false if spending time is more than duration and no chance to acquire it. +func (m *CASMutex) RTryLockWithTimeout(duration time.Duration) bool { + ctx, cancel := context.WithTimeout(context.Background(), duration) + defer cancel() + + return m.RTryLockWithContext(ctx) +} + +// RUnlock releases the read lock. +func (m *CASMutex) RUnlock() { + n := atomic.AddInt32((*int32)(unsafe.Pointer(&m.state)), -1) + switch m.getState(n) { + case casStateUndefined, casStateWriteLock: + panic("RUnlock failed") + case casStateNoLock: + m.broadcast() + } +} + +// RLocker returns a Locker interface that implements the Lock and Unlock methods +// by calling CASMutex.RLock and CASMutex.RUnlock. +func (m *CASMutex) RLocker() sync.Locker { + return (*rlocker)(m) +} + +type rlocker CASMutex + +func (r *rlocker) Lock() { (*CASMutex)(r).RLock() } +func (r *rlocker) Unlock() { (*CASMutex)(r).RUnlock() } diff --git a/dataext/syncMap.go b/dataext/syncMap.go index 4d1db41..ecf6b8a 100644 --- a/dataext/syncMap.go +++ b/dataext/syncMap.go @@ -50,6 +50,22 @@ func (s *SyncMap[TKey, TData]) Get(key TKey) (TData, bool) { } } +func (s *SyncMap[TKey, TData]) GetAndSetIfNotContains(key TKey, data TData) TData { + s.lock.Lock() + defer s.lock.Unlock() + + if s.data == nil { + s.data = make(map[TKey]TData) + } + + if v, ok := s.data[key]; ok { + return v + } else { + s.data[key] = data + return data + } +} + func (s *SyncMap[TKey, TData]) Delete(key TKey) bool { s.lock.Lock() defer s.lock.Unlock() diff --git a/go.mod b/go.mod index de8ce19..135d1f5 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,19 @@ module gogs.mikescher.com/BlackForestBytes/goext go 1.22 require ( - github.com/gin-gonic/gin v1.9.1 + github.com/gin-gonic/gin v1.10.0 github.com/glebarez/go-sqlite v1.22.0 // only needed for tests -.- github.com/jmoiron/sqlx v1.4.0 github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.32.0 go.mongodb.org/mongo-driver v1.15.0 - golang.org/x/crypto v0.22.0 - golang.org/x/sys v0.19.0 - golang.org/x/term v0.19.0 + golang.org/x/crypto v0.23.0 + golang.org/x/sys v0.20.0 + golang.org/x/term v0.20.0 ) +require golang.org/x/sync v0.7.0 + require ( github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect @@ -47,11 +49,10 @@ require ( github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76 // indirect - golang.org/x/arch v0.7.0 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.34.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/text v0.15.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.37.6 // indirect modernc.org/mathutil v1.6.0 // indirect diff --git a/go.sum b/go.sum index e66a193..6047c68 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -191,6 +193,8 @@ go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGc golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc= golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -207,6 +211,8 @@ golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -223,6 +229,8 @@ golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= @@ -248,6 +256,8 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= @@ -258,6 +268,8 @@ golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -266,6 +278,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -279,6 +293,8 @@ google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGm google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/goextVersion.go b/goextVersion.go index a1895e8..c2fac30 100644 --- a/goextVersion.go +++ b/goextVersion.go @@ -1,5 +1,5 @@ package goext -const GoextVersion = "0.0.445" +const GoextVersion = "0.0.446" -const GoextVersionTimestamp = "2024-05-03T15:28:53+0200" +const GoextVersionTimestamp = "2024-05-10T21:31:36+0200"