mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 19:06:51 +00:00
peer: rename mruinvmap, mrunoncemap to lruinvmap, lrunoncemap
The names mean that the caches contain the Most Recently Used entries. But they behave in a Least Recently Used fashion.
This commit is contained in:
parent
e45c34fbfc
commit
70dd37e644
@ -14,10 +14,10 @@ import (
|
||||
"github.com/decred/dcrd/wire"
|
||||
)
|
||||
|
||||
// mruInventoryMap provides a concurrency safe map that is limited to a maximum
|
||||
// lruInventoryMap provides a concurrency safe map that is limited to a maximum
|
||||
// number of items with eviction for the oldest entry when the limit is
|
||||
// exceeded.
|
||||
type mruInventoryMap struct {
|
||||
type lruInventoryMap struct {
|
||||
invMtx sync.Mutex
|
||||
invMap map[wire.InvVect]*list.Element // nearly O(1) lookups
|
||||
invList *list.List // O(1) insert, update, delete
|
||||
@ -27,7 +27,7 @@ type mruInventoryMap struct {
|
||||
// String returns the map as a human-readable string.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruInventoryMap) String() string {
|
||||
func (m *lruInventoryMap) String() string {
|
||||
m.invMtx.Lock()
|
||||
defer m.invMtx.Unlock()
|
||||
|
||||
@ -49,7 +49,7 @@ func (m *mruInventoryMap) String() string {
|
||||
// Exists returns whether or not the passed inventory item is in the map.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruInventoryMap) Exists(iv *wire.InvVect) bool {
|
||||
func (m *lruInventoryMap) Exists(iv *wire.InvVect) bool {
|
||||
m.invMtx.Lock()
|
||||
_, exists := m.invMap[*iv]
|
||||
m.invMtx.Unlock()
|
||||
@ -62,7 +62,7 @@ func (m *mruInventoryMap) Exists(iv *wire.InvVect) bool {
|
||||
// item makes it the most recently used item.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruInventoryMap) Add(iv *wire.InvVect) {
|
||||
func (m *lruInventoryMap) Add(iv *wire.InvVect) {
|
||||
m.invMtx.Lock()
|
||||
defer m.invMtx.Unlock()
|
||||
|
||||
@ -105,7 +105,7 @@ func (m *mruInventoryMap) Add(iv *wire.InvVect) {
|
||||
// Delete deletes the passed inventory item from the map (if it exists).
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruInventoryMap) Delete(iv *wire.InvVect) {
|
||||
func (m *lruInventoryMap) Delete(iv *wire.InvVect) {
|
||||
m.invMtx.Lock()
|
||||
if node, exists := m.invMap[*iv]; exists {
|
||||
m.invList.Remove(node)
|
||||
@ -114,12 +114,12 @@ func (m *mruInventoryMap) Delete(iv *wire.InvVect) {
|
||||
m.invMtx.Unlock()
|
||||
}
|
||||
|
||||
// newMruInventoryMap returns a new inventory map that is limited to the number
|
||||
// newLruInventoryMap returns a new inventory map that is limited to the number
|
||||
// of entries specified by limit. When the number of entries exceeds the limit,
|
||||
// the oldest (least recently used) entry will be removed to make room for the
|
||||
// new entry.
|
||||
func newMruInventoryMap(limit uint) *mruInventoryMap {
|
||||
m := mruInventoryMap{
|
||||
func newLruInventoryMap(limit uint) *lruInventoryMap {
|
||||
m := lruInventoryMap{
|
||||
invMap: make(map[wire.InvVect]*list.Element),
|
||||
invList: list.New(),
|
||||
limit: limit,
|
||||
@ -14,11 +14,11 @@ import (
|
||||
"github.com/decred/dcrd/wire"
|
||||
)
|
||||
|
||||
// TestMruInventoryMap ensures the MruInventoryMap behaves as expected including
|
||||
// TestLruInventoryMap ensures the LruInventoryMap behaves as expected including
|
||||
// limiting, eviction of least-recently used entries, specific entry removal,
|
||||
// and existence tests.
|
||||
func TestMruInventoryMap(t *testing.T) {
|
||||
// Create a bunch of fake inventory vectors to use in testing the mru
|
||||
func TestLruInventoryMap(t *testing.T) {
|
||||
// Create a bunch of fake inventory vectors to use in testing the lru
|
||||
// inventory code.
|
||||
numInvVects := 10
|
||||
invVects := make([]*wire.InvVect, 0, numInvVects)
|
||||
@ -42,19 +42,19 @@ func TestMruInventoryMap(t *testing.T) {
|
||||
|
||||
testLoop:
|
||||
for i, test := range tests {
|
||||
// Create a new mru inventory map limited by the specified test
|
||||
// Create a new lru inventory map limited by the specified test
|
||||
// limit and add all of the test inventory vectors. This will
|
||||
// cause evicition since there are more test inventory vectors
|
||||
// than the limits.
|
||||
mruInvMap := newMruInventoryMap(uint(test.limit))
|
||||
lruInvMap := newLruInventoryMap(uint(test.limit))
|
||||
for j := 0; j < numInvVects; j++ {
|
||||
mruInvMap.Add(invVects[j])
|
||||
lruInvMap.Add(invVects[j])
|
||||
}
|
||||
|
||||
// Ensure the limited number of most recent entries in the
|
||||
// inventory vector list exist.
|
||||
for j := numInvVects - test.limit; j < numInvVects; j++ {
|
||||
if !mruInvMap.Exists(invVects[j]) {
|
||||
if !lruInvMap.Exists(invVects[j]) {
|
||||
t.Errorf("Exists #%d (%s) entry %s does not "+
|
||||
"exist", i, test.name, *invVects[j])
|
||||
continue testLoop
|
||||
@ -64,7 +64,7 @@ testLoop:
|
||||
// Ensure the entries before the limited number of most recent
|
||||
// entries in the inventory vector list do not exist.
|
||||
for j := 0; j < numInvVects-test.limit; j++ {
|
||||
if mruInvMap.Exists(invVects[j]) {
|
||||
if lruInvMap.Exists(invVects[j]) {
|
||||
t.Errorf("Exists #%d (%s) entry %s exists", i,
|
||||
test.name, *invVects[j])
|
||||
continue testLoop
|
||||
@ -80,16 +80,16 @@ testLoop:
|
||||
// This check needs at least 2 entries.
|
||||
if test.limit > 1 {
|
||||
origLruIndex := numInvVects - test.limit
|
||||
mruInvMap.Add(invVects[origLruIndex])
|
||||
lruInvMap.Add(invVects[origLruIndex])
|
||||
|
||||
iv := wire.NewInvVect(wire.InvTypeBlock,
|
||||
&chainhash.Hash{0x00, 0x01})
|
||||
mruInvMap.Add(iv)
|
||||
lruInvMap.Add(iv)
|
||||
|
||||
// Ensure the original lru entry still exists since it
|
||||
// was updated and should've have become the mru entry.
|
||||
if !mruInvMap.Exists(invVects[origLruIndex]) {
|
||||
t.Errorf("MRU #%d (%s) entry %s does not exist",
|
||||
// was updated and should've have become the lru entry.
|
||||
if !lruInvMap.Exists(invVects[origLruIndex]) {
|
||||
t.Errorf("LRU #%d (%s) entry %s does not exist",
|
||||
i, test.name, *invVects[origLruIndex])
|
||||
continue testLoop
|
||||
}
|
||||
@ -97,8 +97,8 @@ testLoop:
|
||||
// Ensure the entry that should've become the new lru
|
||||
// entry was evicted.
|
||||
newLruIndex := origLruIndex + 1
|
||||
if mruInvMap.Exists(invVects[newLruIndex]) {
|
||||
t.Errorf("MRU #%d (%s) entry %s exists", i,
|
||||
if lruInvMap.Exists(invVects[newLruIndex]) {
|
||||
t.Errorf("LRU #%d (%s) entry %s exists", i,
|
||||
test.name, *invVects[newLruIndex])
|
||||
continue testLoop
|
||||
}
|
||||
@ -108,8 +108,8 @@ testLoop:
|
||||
// including those that don't exist in the map, and ensure they
|
||||
// no longer exist.
|
||||
for j := 0; j < numInvVects; j++ {
|
||||
mruInvMap.Delete(invVects[j])
|
||||
if mruInvMap.Exists(invVects[j]) {
|
||||
lruInvMap.Delete(invVects[j])
|
||||
if lruInvMap.Exists(invVects[j]) {
|
||||
t.Errorf("Delete #%d (%s) entry %s exists", i,
|
||||
test.name, *invVects[j])
|
||||
continue testLoop
|
||||
@ -118,38 +118,38 @@ testLoop:
|
||||
}
|
||||
}
|
||||
|
||||
// TestMruInventoryMapStringer tests the stringized output for the
|
||||
// MruInventoryMap type.
|
||||
func TestMruInventoryMapStringer(t *testing.T) {
|
||||
// Create a couple of fake inventory vectors to use in testing the mru
|
||||
// TestLruInventoryMapStringer tests the stringized output for the
|
||||
// LruInventoryMap type.
|
||||
func TestLruInventoryMapStringer(t *testing.T) {
|
||||
// Create a couple of fake inventory vectors to use in testing the lru
|
||||
// inventory stringer code.
|
||||
hash1 := &chainhash.Hash{0x01}
|
||||
hash2 := &chainhash.Hash{0x02}
|
||||
iv1 := wire.NewInvVect(wire.InvTypeBlock, hash1)
|
||||
iv2 := wire.NewInvVect(wire.InvTypeBlock, hash2)
|
||||
|
||||
// Create new mru inventory map and add the inventory vectors.
|
||||
mruInvMap := newMruInventoryMap(uint(2))
|
||||
mruInvMap.Add(iv1)
|
||||
mruInvMap.Add(iv2)
|
||||
// Create new lru inventory map and add the inventory vectors.
|
||||
lruInvMap := newLruInventoryMap(uint(2))
|
||||
lruInvMap.Add(iv1)
|
||||
lruInvMap.Add(iv2)
|
||||
|
||||
// Ensure the stringer gives the expected result. Since map iteration
|
||||
// is not ordered, either entry could be first, so account for both
|
||||
// cases.
|
||||
wantStr1 := fmt.Sprintf("<%d>[%s, %s]", 2, *iv1, *iv2)
|
||||
wantStr2 := fmt.Sprintf("<%d>[%s, %s]", 2, *iv2, *iv1)
|
||||
gotStr := mruInvMap.String()
|
||||
gotStr := lruInvMap.String()
|
||||
if gotStr != wantStr1 && gotStr != wantStr2 {
|
||||
t.Fatalf("unexpected string representation - got %q, want %q "+
|
||||
"or %q", gotStr, wantStr1, wantStr2)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMruInventoryList performs basic benchmarks on the most recently
|
||||
// BenchmarkLruInventoryList performs basic benchmarks on the most recently
|
||||
// used inventory handling.
|
||||
func BenchmarkMruInventoryList(b *testing.B) {
|
||||
func BenchmarkLruInventoryList(b *testing.B) {
|
||||
// Create a bunch of fake inventory vectors to use in benchmarking
|
||||
// the mru inventory code.
|
||||
// the lru inventory code.
|
||||
b.StopTimer()
|
||||
numInvVects := 100000
|
||||
invVects := make([]*wire.InvVect, 0, numInvVects)
|
||||
@ -164,8 +164,8 @@ func BenchmarkMruInventoryList(b *testing.B) {
|
||||
|
||||
// Benchmark the add plus evicition code.
|
||||
limit := 20000
|
||||
mruInvMap := newMruInventoryMap(uint(limit))
|
||||
lruInvMap := newLruInventoryMap(uint(limit))
|
||||
for i := 0; i < b.N; i++ {
|
||||
mruInvMap.Add(invVects[i%numInvVects])
|
||||
lruInvMap.Add(invVects[i%numInvVects])
|
||||
}
|
||||
}
|
||||
@ -12,10 +12,10 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// mruNonceMap provides a concurrency safe map that is limited to a maximum
|
||||
// lruNonceMap provides a concurrency safe map that is limited to a maximum
|
||||
// number of items with eviction for the oldest entry when the limit is
|
||||
// exceeded.
|
||||
type mruNonceMap struct {
|
||||
type lruNonceMap struct {
|
||||
mtx sync.Mutex
|
||||
nonceMap map[uint64]*list.Element // nearly O(1) lookups
|
||||
nonceList *list.List // O(1) insert, update, delete
|
||||
@ -25,7 +25,7 @@ type mruNonceMap struct {
|
||||
// String returns the map as a human-readable string.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruNonceMap) String() string {
|
||||
func (m *lruNonceMap) String() string {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
@ -47,7 +47,7 @@ func (m *mruNonceMap) String() string {
|
||||
// Exists returns whether or not the passed nonce is in the map.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruNonceMap) Exists(nonce uint64) bool {
|
||||
func (m *lruNonceMap) Exists(nonce uint64) bool {
|
||||
m.mtx.Lock()
|
||||
_, exists := m.nonceMap[nonce]
|
||||
m.mtx.Unlock()
|
||||
@ -60,7 +60,7 @@ func (m *mruNonceMap) Exists(nonce uint64) bool {
|
||||
// makes it the most recently used item.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruNonceMap) Add(nonce uint64) {
|
||||
func (m *lruNonceMap) Add(nonce uint64) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
|
||||
@ -103,7 +103,7 @@ func (m *mruNonceMap) Add(nonce uint64) {
|
||||
// Delete deletes the passed nonce from the map (if it exists).
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (m *mruNonceMap) Delete(nonce uint64) {
|
||||
func (m *lruNonceMap) Delete(nonce uint64) {
|
||||
m.mtx.Lock()
|
||||
if node, exists := m.nonceMap[nonce]; exists {
|
||||
m.nonceList.Remove(node)
|
||||
@ -112,12 +112,12 @@ func (m *mruNonceMap) Delete(nonce uint64) {
|
||||
m.mtx.Unlock()
|
||||
}
|
||||
|
||||
// newMruNonceMap returns a new nonce map that is limited to the number of
|
||||
// newLruNonceMap returns a new nonce map that is limited to the number of
|
||||
// entries specified by limit. When the number of entries exceeds the limit,
|
||||
// the oldest (least recently used) entry will be removed to make room for the
|
||||
// new entry.
|
||||
func newMruNonceMap(limit uint) *mruNonceMap {
|
||||
m := mruNonceMap{
|
||||
func newLruNonceMap(limit uint) *lruNonceMap {
|
||||
m := lruNonceMap{
|
||||
nonceMap: make(map[uint64]*list.Element),
|
||||
nonceList: list.New(),
|
||||
limit: limit,
|
||||
@ -10,11 +10,11 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMruNonceMap ensures the mruNonceMap behaves as expected including
|
||||
// TestLruNonceMap ensures the lruNonceMap behaves as expected including
|
||||
// limiting, eviction of least-recently used entries, specific entry removal,
|
||||
// and existence tests.
|
||||
func TestMruNonceMap(t *testing.T) {
|
||||
// Create a bunch of fake nonces to use in testing the mru nonce code.
|
||||
func TestLruNonceMap(t *testing.T) {
|
||||
// Create a bunch of fake nonces to use in testing the lru nonce code.
|
||||
numNonces := 10
|
||||
nonces := make([]uint64, 0, numNonces)
|
||||
for i := 0; i < numNonces; i++ {
|
||||
@ -35,18 +35,18 @@ func TestMruNonceMap(t *testing.T) {
|
||||
|
||||
testLoop:
|
||||
for i, test := range tests {
|
||||
// Create a new mru nonce map limited by the specified test
|
||||
// Create a new lru nonce map limited by the specified test
|
||||
// limit and add all of the test nonces. This will cause
|
||||
// evicition since there are more test nonces than the limits.
|
||||
mruNonceMap := newMruNonceMap(uint(test.limit))
|
||||
lruNonceMap := newLruNonceMap(uint(test.limit))
|
||||
for j := 0; j < numNonces; j++ {
|
||||
mruNonceMap.Add(nonces[j])
|
||||
lruNonceMap.Add(nonces[j])
|
||||
}
|
||||
|
||||
// Ensure the limited number of most recent entries in the list
|
||||
// exist.
|
||||
for j := numNonces - test.limit; j < numNonces; j++ {
|
||||
if !mruNonceMap.Exists(nonces[j]) {
|
||||
if !lruNonceMap.Exists(nonces[j]) {
|
||||
t.Errorf("Exists #%d (%s) entry %d does not "+
|
||||
"exist", i, test.name, nonces[j])
|
||||
continue testLoop
|
||||
@ -56,7 +56,7 @@ testLoop:
|
||||
// Ensure the entries before the limited number of most recent
|
||||
// entries in the list do not exist.
|
||||
for j := 0; j < numNonces-test.limit; j++ {
|
||||
if mruNonceMap.Exists(nonces[j]) {
|
||||
if lruNonceMap.Exists(nonces[j]) {
|
||||
t.Errorf("Exists #%d (%s) entry %d exists", i,
|
||||
test.name, nonces[j])
|
||||
continue testLoop
|
||||
@ -72,14 +72,14 @@ testLoop:
|
||||
// This check needs at least 2 entries.
|
||||
if test.limit > 1 {
|
||||
origLruIndex := numNonces - test.limit
|
||||
mruNonceMap.Add(nonces[origLruIndex])
|
||||
lruNonceMap.Add(nonces[origLruIndex])
|
||||
|
||||
mruNonceMap.Add(uint64(numNonces) + 1)
|
||||
lruNonceMap.Add(uint64(numNonces) + 1)
|
||||
|
||||
// Ensure the original lru entry still exists since it
|
||||
// was updated and should've have become the mru entry.
|
||||
if !mruNonceMap.Exists(nonces[origLruIndex]) {
|
||||
t.Errorf("MRU #%d (%s) entry %d does not exist",
|
||||
// was updated and should've have become the lru entry.
|
||||
if !lruNonceMap.Exists(nonces[origLruIndex]) {
|
||||
t.Errorf("LRU #%d (%s) entry %d does not exist",
|
||||
i, test.name, nonces[origLruIndex])
|
||||
continue testLoop
|
||||
}
|
||||
@ -87,8 +87,8 @@ testLoop:
|
||||
// Ensure the entry that should've become the new lru
|
||||
// entry was evicted.
|
||||
newLruIndex := origLruIndex + 1
|
||||
if mruNonceMap.Exists(nonces[newLruIndex]) {
|
||||
t.Errorf("MRU #%d (%s) entry %d exists", i,
|
||||
if lruNonceMap.Exists(nonces[newLruIndex]) {
|
||||
t.Errorf("LRU #%d (%s) entry %d exists", i,
|
||||
test.name, nonces[newLruIndex])
|
||||
continue testLoop
|
||||
}
|
||||
@ -97,8 +97,8 @@ testLoop:
|
||||
// Delete all of the entries in the list, including those that
|
||||
// don't exist in the map, and ensure they no longer exist.
|
||||
for j := 0; j < numNonces; j++ {
|
||||
mruNonceMap.Delete(nonces[j])
|
||||
if mruNonceMap.Exists(nonces[j]) {
|
||||
lruNonceMap.Delete(nonces[j])
|
||||
if lruNonceMap.Exists(nonces[j]) {
|
||||
t.Errorf("Delete #%d (%s) entry %d exists", i,
|
||||
test.name, nonces[j])
|
||||
continue testLoop
|
||||
@ -107,34 +107,34 @@ testLoop:
|
||||
}
|
||||
}
|
||||
|
||||
// TestMruNonceMapStringer tests the stringized output for the mruNonceMap type.
|
||||
func TestMruNonceMapStringer(t *testing.T) {
|
||||
// Create a couple of fake nonces to use in testing the mru nonce
|
||||
// TestLruNonceMapStringer tests the stringized output for the lruNonceMap type.
|
||||
func TestLruNonceMapStringer(t *testing.T) {
|
||||
// Create a couple of fake nonces to use in testing the lru nonce
|
||||
// stringer code.
|
||||
nonce1 := uint64(10)
|
||||
nonce2 := uint64(20)
|
||||
|
||||
// Create new mru nonce map and add the nonces.
|
||||
mruNonceMap := newMruNonceMap(uint(2))
|
||||
mruNonceMap.Add(nonce1)
|
||||
mruNonceMap.Add(nonce2)
|
||||
// Create new lru nonce map and add the nonces.
|
||||
lruNonceMap := newLruNonceMap(uint(2))
|
||||
lruNonceMap.Add(nonce1)
|
||||
lruNonceMap.Add(nonce2)
|
||||
|
||||
// Ensure the stringer gives the expected result. Since map iteration
|
||||
// is not ordered, either entry could be first, so account for both
|
||||
// cases.
|
||||
wantStr1 := fmt.Sprintf("<%d>[%d, %d]", 2, nonce1, nonce2)
|
||||
wantStr2 := fmt.Sprintf("<%d>[%d, %d]", 2, nonce2, nonce1)
|
||||
gotStr := mruNonceMap.String()
|
||||
gotStr := lruNonceMap.String()
|
||||
if gotStr != wantStr1 && gotStr != wantStr2 {
|
||||
t.Fatalf("unexpected string representation - got %q, want %q "+
|
||||
"or %q", gotStr, wantStr1, wantStr2)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkMruNonceList performs basic benchmarks on the most recently used
|
||||
// BenchmarkLruNonceList performs basic benchmarks on the most recently used
|
||||
// nonce handling.
|
||||
func BenchmarkMruNonceList(b *testing.B) {
|
||||
// Create a bunch of fake nonces to use in benchmarking the mru nonce
|
||||
func BenchmarkLruNonceList(b *testing.B) {
|
||||
// Create a bunch of fake nonces to use in benchmarking the lru nonce
|
||||
// code.
|
||||
b.StopTimer()
|
||||
numNonces := 100000
|
||||
@ -146,8 +146,8 @@ func BenchmarkMruNonceList(b *testing.B) {
|
||||
|
||||
// Benchmark the add plus evicition code.
|
||||
limit := 20000
|
||||
mruNonceMap := newMruNonceMap(uint(limit))
|
||||
lruNonceMap := newLruNonceMap(uint(limit))
|
||||
for i := 0; i < b.N; i++ {
|
||||
mruNonceMap.Add(nonces[i%numNonces])
|
||||
lruNonceMap.Add(nonces[i%numNonces])
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ var (
|
||||
|
||||
// sentNonces houses the unique nonces that are generated when pushing
|
||||
// version messages that are used to detect self connections.
|
||||
sentNonces = newMruNonceMap(50)
|
||||
sentNonces = newLruNonceMap(50)
|
||||
|
||||
// allowSelfConns is only used to allow the tests to bypass the self
|
||||
// connection detecting and disconnect logic since they intentionally
|
||||
@ -414,7 +414,7 @@ type Peer struct {
|
||||
versionSent bool
|
||||
verAckReceived bool
|
||||
|
||||
knownInventory *mruInventoryMap
|
||||
knownInventory *lruInventoryMap
|
||||
prevGetBlocksMtx sync.Mutex
|
||||
prevGetBlocksBegin *chainhash.Hash
|
||||
prevGetBlocksStop *chainhash.Hash
|
||||
@ -2035,7 +2035,7 @@ func newPeerBase(cfg *Config, inbound bool) *Peer {
|
||||
|
||||
p := Peer{
|
||||
inbound: inbound,
|
||||
knownInventory: newMruInventoryMap(maxKnownInventory),
|
||||
knownInventory: newLruInventoryMap(maxKnownInventory),
|
||||
stallControl: make(chan stallControlMsg, 1), // nonblocking sync
|
||||
outputQueue: make(chan outMsg, outputBufferSize),
|
||||
sendQueue: make(chan outMsg, 1), // nonblocking sync
|
||||
|
||||
Loading…
Reference in New Issue
Block a user