From 38990f6e1cf9ca0785709d03b66237a713338d0b Mon Sep 17 00:00:00 2001 From: wdvxdr Date: Sun, 20 Mar 2022 15:07:54 +0800 Subject: [PATCH] client: use generic version sync.Map --- client/client.go | 10 +- client/global.go | 7 +- client/handler_map_gen.go | 387 -------------------------------------- client/network.go | 2 +- go.mod | 1 + go.sum | 2 + 6 files changed, 13 insertions(+), 396 deletions(-) delete mode 100644 client/handler_map_gen.go diff --git a/client/client.go b/client/client.go index 430cb000..8b747945 100644 --- a/client/client.go +++ b/client/client.go @@ -13,6 +13,8 @@ import ( "github.com/pkg/errors" "go.uber.org/atomic" + "github.com/RomiChan/syncx" + "github.com/Mrs4s/MiraiGo/binary" "github.com/Mrs4s/MiraiGo/client/internal/auth" "github.com/Mrs4s/MiraiGo/client/internal/highway" @@ -23,8 +25,6 @@ import ( "github.com/Mrs4s/MiraiGo/utils" ) -//go:generate go run github.com/a8m/syncmap -o "handler_map_gen.go" -pkg client -name HandlerMap "map[uint16]*handlerInfo" - type QQClient struct { Uin int64 PasswordMd5 [16]byte @@ -57,8 +57,8 @@ type QQClient struct { logger Logger // internal state - handlers HandlerMap - waiters sync.Map + handlers syncx.Map[uint16, *handlerInfo] + waiters syncx.Map[string, func(any, error)] servers []*net.TCPAddr currServerIndex int retryTimes int @@ -111,7 +111,7 @@ type QQClient struct { lastC2CMsgTime int64 transCache *utils.Cache[unit] groupSysMsgCache *GroupSystemMessages - msgBuilders sync.Map + msgBuilders syncx.Map[int32, *messageBuilder] onlinePushCache *utils.Cache[unit] heartbeatEnabled bool requestPacketRequestID atomic.Int32 diff --git a/client/global.go b/client/global.go index 514473eb..d1c5db79 100644 --- a/client/global.go +++ b/client/global.go @@ -244,14 +244,15 @@ func (c *QQClient) parseTempMessage(msg *msg.Message) *message.TempMessage { } func (c *QQClient) messageBuilder(seq int32) *messageBuilder { - builder := &messageBuilder{} - actual, ok := c.msgBuilders.LoadOrStore(seq, builder) + actual, ok := c.msgBuilders.Load(seq) if !ok { + builder := &messageBuilder{} + actual, _ = c.msgBuilders.LoadOrStore(seq, builder) time.AfterFunc(time.Minute, func() { c.msgBuilders.Delete(seq) // delete avoid memory leak }) } - return actual.(*messageBuilder) + return actual } type messageBuilder struct { diff --git a/client/handler_map_gen.go b/client/handler_map_gen.go deleted file mode 100644 index d88a52fb..00000000 --- a/client/handler_map_gen.go +++ /dev/null @@ -1,387 +0,0 @@ -// Code generated by syncmap; DO NOT EDIT. - -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package client - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -// Map is like a Go map[interface{}]interface{} but is safe for concurrent use -// by multiple goroutines without additional locking or coordination. -// Loads, stores, and deletes run in amortized constant time. -// -// The Map type is specialized. Most code should use a plain Go map instead, -// with separate locking or coordination, for better type safety and to make it -// easier to maintain other invariants along with the map content. -// -// The Map type is optimized for two common use cases: (1) when the entry for a given -// key is only ever written once but read many times, as in caches that only grow, -// or (2) when multiple goroutines read, write, and overwrite entries for disjoint -// sets of keys. In these two cases, use of a Map may significantly reduce lock -// contention compared to a Go map paired with a separate Mutex or RWMutex. -// -// The zero Map is empty and ready for use. A Map must not be copied after first use. -type HandlerMap struct { - mu sync.Mutex - - // read contains the portion of the map's contents that are safe for - // concurrent access (with or without mu held). - // - // The read field itself is always safe to load, but must only be stored with - // mu held. - // - // Entries stored in read may be updated concurrently without mu, but updating - // a previously-expunged entry requires that the entry be copied to the dirty - // map and unexpunged with mu held. - read atomic.Value // readOnly - - // dirty contains the portion of the map's contents that require mu to be - // held. To ensure that the dirty map can be promoted to the read map quickly, - // it also includes all of the non-expunged entries in the read map. - // - // Expunged entries are not stored in the dirty map. An expunged entry in the - // clean map must be unexpunged and added to the dirty map before a new value - // can be stored to it. - // - // If the dirty map is nil, the next write to the map will initialize it by - // making a shallow copy of the clean map, omitting stale entries. - dirty map[uint16]*entryHandlerMap - - // misses counts the number of loads since the read map was last updated that - // needed to lock mu to determine whether the key was present. - // - // Once enough misses have occurred to cover the cost of copying the dirty - // map, the dirty map will be promoted to the read map (in the unamended - // state) and the next store to the map will make a new dirty copy. - misses int -} - -// readOnly is an immutable struct stored atomically in the Map.read field. -type readOnlyHandlerMap struct { - m map[uint16]*entryHandlerMap - amended bool // true if the dirty map contains some key not in m. -} - -// expunged is an arbitrary pointer that marks entries which have been deleted -// from the dirty map. -var expungedHandlerMap = unsafe.Pointer(new(*handlerInfo)) - -// An entry is a slot in the map corresponding to a particular key. -type entryHandlerMap struct { - // p points to the interface{} value stored for the entry. - // - // If p == nil, the entry has been deleted and m.dirty == nil. - // - // If p == expunged, the entry has been deleted, m.dirty != nil, and the entry - // is missing from m.dirty. - // - // Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty - // != nil, in m.dirty[key]. - // - // An entry can be deleted by atomic replacement with nil: when m.dirty is - // next created, it will atomically replace nil with expunged and leave - // m.dirty[key] unset. - // - // An entry's associated value can be updated by atomic replacement, provided - // p != expunged. If p == expunged, an entry's associated value can be updated - // only after first setting m.dirty[key] = e so that lookups using the dirty - // map find the entry. - p unsafe.Pointer // *interface{} -} - -func newEntryHandlerMap(i *handlerInfo) *entryHandlerMap { - return &entryHandlerMap{p: unsafe.Pointer(&i)} -} - -// Load returns the value stored in the map for a key, or nil if no -// value is present. -// The ok result indicates whether value was found in the map. -func (m *HandlerMap) Load(key uint16) (value *handlerInfo, ok bool) { - read, _ := m.read.Load().(readOnlyHandlerMap) - e, ok := read.m[key] - if !ok && read.amended { - m.mu.Lock() - // Avoid reporting a spurious miss if m.dirty got promoted while we were - // blocked on m.mu. (If further loads of the same key will not miss, it's - // not worth copying the dirty map for this key.) - read, _ = m.read.Load().(readOnlyHandlerMap) - e, ok = read.m[key] - if !ok && read.amended { - e, ok = m.dirty[key] - // Regardless of whether the entry was present, record a miss: this key - // will take the slow path until the dirty map is promoted to the read - // map. - m.missLocked() - } - m.mu.Unlock() - } - if !ok { - return value, false - } - return e.load() -} - -func (e *entryHandlerMap) load() (value *handlerInfo, ok bool) { - p := atomic.LoadPointer(&e.p) - if p == nil || p == expungedHandlerMap { - return value, false - } - return *(**handlerInfo)(p), true -} - -// Store sets the value for a key. -func (m *HandlerMap) Store(key uint16, value *handlerInfo) { - read, _ := m.read.Load().(readOnlyHandlerMap) - if e, ok := read.m[key]; ok && e.tryStore(&value) { - return - } - - m.mu.Lock() - read, _ = m.read.Load().(readOnlyHandlerMap) - if e, ok := read.m[key]; ok { - if e.unexpungeLocked() { - // The entry was previously expunged, which implies that there is a - // non-nil dirty map and this entry is not in it. - m.dirty[key] = e - } - e.storeLocked(&value) - } else if e, ok := m.dirty[key]; ok { - e.storeLocked(&value) - } else { - if !read.amended { - // We're adding the first new key to the dirty map. - // Make sure it is allocated and mark the read-only map as incomplete. - m.dirtyLocked() - m.read.Store(readOnlyHandlerMap{m: read.m, amended: true}) - } - m.dirty[key] = newEntryHandlerMap(value) - } - m.mu.Unlock() -} - -// tryStore stores a value if the entry has not been expunged. -// -// If the entry is expunged, tryStore returns false and leaves the entry -// unchanged. -func (e *entryHandlerMap) tryStore(i **handlerInfo) bool { - for { - p := atomic.LoadPointer(&e.p) - if p == expungedHandlerMap { - return false - } - if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) { - return true - } - } -} - -// unexpungeLocked ensures that the entry is not marked as expunged. -// -// If the entry was previously expunged, it must be added to the dirty map -// before m.mu is unlocked. -func (e *entryHandlerMap) unexpungeLocked() (wasExpunged bool) { - return atomic.CompareAndSwapPointer(&e.p, expungedHandlerMap, nil) -} - -// storeLocked unconditionally stores a value to the entry. -// -// The entry must be known not to be expunged. -func (e *entryHandlerMap) storeLocked(i **handlerInfo) { - atomic.StorePointer(&e.p, unsafe.Pointer(i)) -} - -// LoadOrStore returns the existing value for the key if present. -// Otherwise, it stores and returns the given value. -// The loaded result is true if the value was loaded, false if stored. -func (m *HandlerMap) LoadOrStore(key uint16, value *handlerInfo) (actual *handlerInfo, loaded bool) { - // Avoid locking if it's a clean hit. - read, _ := m.read.Load().(readOnlyHandlerMap) - if e, ok := read.m[key]; ok { - actual, loaded, ok := e.tryLoadOrStore(value) - if ok { - return actual, loaded - } - } - - m.mu.Lock() - read, _ = m.read.Load().(readOnlyHandlerMap) - if e, ok := read.m[key]; ok { - if e.unexpungeLocked() { - m.dirty[key] = e - } - actual, loaded, _ = e.tryLoadOrStore(value) - } else if e, ok := m.dirty[key]; ok { - actual, loaded, _ = e.tryLoadOrStore(value) - m.missLocked() - } else { - if !read.amended { - // We're adding the first new key to the dirty map. - // Make sure it is allocated and mark the read-only map as incomplete. - m.dirtyLocked() - m.read.Store(readOnlyHandlerMap{m: read.m, amended: true}) - } - m.dirty[key] = newEntryHandlerMap(value) - actual, loaded = value, false - } - m.mu.Unlock() - - return actual, loaded -} - -// tryLoadOrStore atomically loads or stores a value if the entry is not -// expunged. -// -// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and -// returns with ok==false. -func (e *entryHandlerMap) tryLoadOrStore(i *handlerInfo) (actual *handlerInfo, loaded, ok bool) { - p := atomic.LoadPointer(&e.p) - if p == expungedHandlerMap { - return actual, false, false - } - if p != nil { - return *(**handlerInfo)(p), true, true - } - - // Copy the interface after the first load to make this method more amenable - // to escape analysis: if we hit the "load" path or the entry is expunged, we - // shouldn't bother heap-allocating. - ic := i - for { - if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) { - return i, false, true - } - p = atomic.LoadPointer(&e.p) - if p == expungedHandlerMap { - return actual, false, false - } - if p != nil { - return *(**handlerInfo)(p), true, true - } - } -} - -// LoadAndDelete deletes the value for a key, returning the previous value if any. -// The loaded result reports whether the key was present. -func (m *HandlerMap) LoadAndDelete(key uint16) (value *handlerInfo, loaded bool) { - read, _ := m.read.Load().(readOnlyHandlerMap) - e, ok := read.m[key] - if !ok && read.amended { - m.mu.Lock() - read, _ = m.read.Load().(readOnlyHandlerMap) - e, ok = read.m[key] - if !ok && read.amended { - e, ok = m.dirty[key] - delete(m.dirty, key) - // Regardless of whether the entry was present, record a miss: this key - // will take the slow path until the dirty map is promoted to the read - // map. - m.missLocked() - } - m.mu.Unlock() - } - if ok { - return e.delete() - } - return value, false -} - -// Delete deletes the value for a key. -func (m *HandlerMap) Delete(key uint16) { - m.LoadAndDelete(key) -} - -func (e *entryHandlerMap) delete() (value *handlerInfo, ok bool) { - for { - p := atomic.LoadPointer(&e.p) - if p == nil || p == expungedHandlerMap { - return value, false - } - if atomic.CompareAndSwapPointer(&e.p, p, nil) { - return *(**handlerInfo)(p), true - } - } -} - -// Range calls f sequentially for each key and value present in the map. -// If f returns false, range stops the iteration. -// -// Range does not necessarily correspond to any consistent snapshot of the Map's -// contents: no key will be visited more than once, but if the value for any key -// is stored or deleted concurrently, Range may reflect any mapping for that key -// from any point during the Range call. -// -// Range may be O(N) with the number of elements in the map even if f returns -// false after a constant number of calls. -func (m *HandlerMap) Range(f func(key uint16, value *handlerInfo) bool) { - // We need to be able to iterate over all of the keys that were already - // present at the start of the call to Range. - // If read.amended is false, then read.m satisfies that property without - // requiring us to hold m.mu for a long time. - read, _ := m.read.Load().(readOnlyHandlerMap) - if read.amended { - // m.dirty contains keys not in read.m. Fortunately, Range is already O(N) - // (assuming the caller does not break out early), so a call to Range - // amortizes an entire copy of the map: we can promote the dirty copy - // immediately! - m.mu.Lock() - read, _ = m.read.Load().(readOnlyHandlerMap) - if read.amended { - read = readOnlyHandlerMap{m: m.dirty} - m.read.Store(read) - m.dirty = nil - m.misses = 0 - } - m.mu.Unlock() - } - - for k, e := range read.m { - v, ok := e.load() - if !ok { - continue - } - if !f(k, v) { - break - } - } -} - -func (m *HandlerMap) missLocked() { - m.misses++ - if m.misses < len(m.dirty) { - return - } - m.read.Store(readOnlyHandlerMap{m: m.dirty}) - m.dirty = nil - m.misses = 0 -} - -func (m *HandlerMap) dirtyLocked() { - if m.dirty != nil { - return - } - - read, _ := m.read.Load().(readOnlyHandlerMap) - m.dirty = make(map[uint16]*entryHandlerMap, len(read.m)) - for k, e := range read.m { - if !e.tryExpungeLocked() { - m.dirty[k] = e - } - } -} - -func (e *entryHandlerMap) tryExpungeLocked() (isExpunged bool) { - p := atomic.LoadPointer(&e.p) - for p == nil { - if atomic.CompareAndSwapPointer(&e.p, nil, expungedHandlerMap) { - return true - } - p = atomic.LoadPointer(&e.p) - } - return p == expungedHandlerMap -} diff --git a/client/network.go b/client/network.go index 9fab6e76..4a546155 100644 --- a/client/network.go +++ b/client/network.go @@ -352,7 +352,7 @@ func (c *QQClient) netLoop() { if ok { info.fun(decoded, err) } else if f, ok := c.waiters.Load(pkt.CommandName); ok { // 在不存在handler的情况下触发wait - f.(func(any, error))(decoded, err) + f(decoded, err) } } else if f, ok := c.handlers.LoadAndDelete(pkt.SequenceId); ok { // does not need decoder diff --git a/go.mod b/go.mod index 915578c7..23143ef4 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/RomiChan/protobuf v0.0.0-20220318113238-d8a99598f896 + github.com/RomiChan/syncx v0.0.0-20220320065321-8d448f958257 github.com/fumiama/imgsz v0.0.2 github.com/pierrec/lz4/v4 v4.1.11 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index 14044917..a10b7bb7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/RomiChan/protobuf v0.0.0-20220318113238-d8a99598f896 h1:UFAqSbH6VqW5mEzQV2HVB7+p3k9JfTbidWJ/9F15yz0= github.com/RomiChan/protobuf v0.0.0-20220318113238-d8a99598f896/go.mod h1:CKKOWC7mBxd36zxsCB1V8DTrwlTNRQvkSVbYqyUiGEE= +github.com/RomiChan/syncx v0.0.0-20220320065321-8d448f958257 h1:fdMod+DEoiICoTtS1Wij/wl1d57FPvKVmretLi2sGD8= +github.com/RomiChan/syncx v0.0.0-20220320065321-8d448f958257/go.mod h1:KqZzu7slNKROh3TSYEH/IUMG6f4M+1qubZ5e52QypsE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=