Skip to content

Commit b4ed595

Browse files
Remove groupcache (#7126)
As discussed during the caching retro, this PR removes the experimental distributed cache based on Mailgun's fork of groupcache. Signed-off-by: Kaviraj <kavirajkanagaraj@gmail.com> Co-authored-by: Kaviraj <kavirajkanagaraj@gmail.com>
1 parent ae2a6f3 commit b4ed595

File tree

40 files changed

+14
-3952
lines changed

40 files changed

+14
-3952
lines changed

cmd/loki/loki-local-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ query_range:
2121
cache:
2222
embedded_cache:
2323
enabled: true
24-
distributed: false
2524
max_size_mb: 100
2625

2726
schema_config:

docs/sources/configuration/_index.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2896,17 +2896,6 @@ This way, one doesn't have to replicate configuration in multiple places.
28962896
# CLI flag: -common.compactor-address
28972897
[compactor_address: <string> | default = ""]
28982898

2899-
# Groupcache is an in-process, distributed cache that behaves similarly to memcached but is built-in to Loki
2900-
groupcache:
2901-
# Enable groupcache
2902-
# CLI flag: -common.groupcache.enabled
2903-
[enabled: <boolean>: default = false]
2904-
# Set the maximum available memory to use for each groupcache group
2905-
# NOTE: there are 3 caches (result, chunk, and index query), so the maximum used memory will be *triple* the value specified here.
2906-
# CLI flag: -common.groupcache.capacity-per-cache-mb
2907-
[capacity_per_cache_mb: <int>: default = 100]
2908-
```
2909-
29102899
## analytics
29112900

29122901
The `analytics` block configures the reporting of Loki analytics to grafana.com.

docs/sources/upgrading/_index.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,9 @@ Previously, we had two configurations to define a query timeout: `engine.timeout
4343
As they were conflicting and `engine.timeout` isn't as expressive as `querier.query-tiomeout`,
4444
we're deprecating it in favor of relying on `engine.query-timeout` only.
4545

46-
#### Fifocache is deprecated
46+
#### `fifocache` has been renamed
4747

48-
We introduced a new cache called `embedded-cache` which is an in-process cache system that make it possible to run Loki without the need for an external cache (like Memcached, Redis, etc). It can be run in two modes `distributed: false` (default, and same as old `fifocache`) and `distributed: true` which runs cache in distributed fashion sharding keys across peers if Loki is run in microservices or SSD mode.
49-
50-
Currently `embedded-cache` with `distributed: true` can be enabled only for results cache.
48+
The in-memory `fifocache` has been renamed to `embedded-cache`. This allows us to replace the implementation (currently a simple FIFO datastructure) with something else in the future without causing confusion
5149

5250
#### Evenly spread Memcached pods for chunks across kubernetes nodes
5351

@@ -98,7 +96,7 @@ The global `deletion_mode` option in the compactor configuration moved to runtim
9896
- The `deletion_mode` global override needs to be set to the desired mode: `disabled`, `filter-only`, or `filter-and-delete`. By default, `filter-and-delete` is enabled.
9997
- Any `allow_delete` per-tenant overrides need to be removed or changed to `deletion_mode` overrides with the desired mode.
10098

101-
#### Metric name for `loki_log_messages_total` changed
99+
#### Metric name for `loki_log_messages_total` changed
102100

103101
The name of this metric was changed to `loki_internal_log_messages_total` to reduce ambiguity. The previous name is still present but is deprecated.
104102

go.mod

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,7 @@ require (
113113
)
114114

115115
require (
116-
github.com/grafana/groupcache_exporter v0.0.0-20220629095919-59a8c6428a43
117116
github.com/heroku/x v0.0.50
118-
github.com/mailgun/groupcache/v2 v2.3.2
119117
github.com/prometheus/alertmanager v0.24.0
120118
github.com/prometheus/common/sigv4 v0.1.0
121119
github.com/thanos-io/objstore v0.0.0-20220715165016-ce338803bc1e

go.sum

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -713,8 +713,6 @@ github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQa
713713
github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90=
714714
github.com/grafana/gocql v0.0.0-20200605141915-ba5dc39ece85 h1:xLuzPoOzdfNb/RF/IENCw+oLVdZB4G21VPhkHBgwSHY=
715715
github.com/grafana/gocql v0.0.0-20200605141915-ba5dc39ece85/go.mod h1:crI9WX6p0IhrqB+DqIUHulRW853PaNFf7o4UprV//3I=
716-
github.com/grafana/groupcache_exporter v0.0.0-20220629095919-59a8c6428a43 h1:yOw0zAMMp/GXp6eQ2hc5c9w6RcZ44u+QeyNeeAVoV7w=
717-
github.com/grafana/groupcache_exporter v0.0.0-20220629095919-59a8c6428a43/go.mod h1:hgtvYpo9DY0PnOJHQTlV12Uecz0jbDK/o0L9Vz4WvLs=
718716
github.com/grafana/memberlist v0.3.1-0.20220714140823-09ffed8adbbe h1:yIXAAbLswn7VNWBIvM71O2QsgfgW9fRXZNR0DXe6pDU=
719717
github.com/grafana/memberlist v0.3.1-0.20220714140823-09ffed8adbbe/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
720718
github.com/grafana/regexp v0.0.0-20221005093135-b4c2bcb0a4b6 h1:A3dhViTeFDSQcGOXuUi6ukCQSMyDtDISBp2z6OOo2YM=
@@ -945,8 +943,6 @@ github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0U
945943
github.com/linode/linodego v1.9.1 h1:29UpEPpYcGFnbwiJW8mbk/bjBZpgd/pv68io2IKTo34=
946944
github.com/lstoll/grpce v1.7.0/go.mod h1:XiCWl3R+avNCT7KsTjv3qCblgsSqd0SC4ymySrH226g=
947945
github.com/lyft/protoc-gen-validate v0.0.0-20180911180927-64fcb82c878e/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
948-
github.com/mailgun/groupcache/v2 v2.3.2 h1:5dU4h13edj8lqMvdjmpmudv2l6iym5J7jqxf7KeE6Zg=
949-
github.com/mailgun/groupcache/v2 v2.3.2/go.mod h1:tH8aMaTRIjFMJsmJ9p7Y5HGBj9hV/J9rKQ+/3dIXzNU=
950946
github.com/mailru/easyjson v0.0.0-20180717111219-efc7eb8984d6/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
951947
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
952948
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=

pkg/loki/common/common.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"github.com/grafana/dskit/flagext"
77
"github.com/grafana/dskit/netutil"
88

9-
"github.com/grafana/loki/pkg/storage/chunk/cache"
109
"github.com/grafana/loki/pkg/storage/chunk/client/aws"
1110
"github.com/grafana/loki/pkg/storage/chunk/client/azure"
1211
"github.com/grafana/loki/pkg/storage/chunk/client/baidubce"
@@ -44,9 +43,6 @@ type Config struct {
4443

4544
// CompactorAddress is the http address of the compactor in the form http://host:port
4645
CompactorAddress string `yaml:"compactor_address"`
47-
48-
// Global embedded-cache config. Independent of what type of cache, we need some singleton configs like Ring configuration when running in distributed fashion.
49-
EmbeddedCacheConfig cache.EmbeddedCacheSingletonConfig `yaml:"embedded_cache"`
5046
}
5147

5248
func (c *Config) RegisterFlags(f *flag.FlagSet) {
@@ -61,8 +57,6 @@ func (c *Config) RegisterFlags(f *flag.FlagSet) {
6157
throwaway.Var((*flagext.StringSlice)(&c.InstanceInterfaceNames), "common.instance-interface-names", "List of network interfaces to read address from.")
6258

6359
f.StringVar(&c.CompactorAddress, "common.compactor-address", "", "the http address of the compactor in the form http://host:port")
64-
65-
c.EmbeddedCacheConfig.RegisterFlagsWithPrefix("common.embedded-cache", "", f)
6660
}
6761

6862
type Storage struct {

pkg/loki/config_wrapper.go

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,6 @@ func applyInstanceConfigs(r, defaults *ConfigWrapper) {
164164
}
165165
r.Frontend.FrontendV2.Addr = r.Common.InstanceAddr
166166
r.IndexGateway.Ring.InstanceAddr = r.Common.InstanceAddr
167-
if r.QueryRange.CacheConfig.EmbeddedCache.IsEnabledWithDistributed() {
168-
r.Common.EmbeddedCacheConfig.Ring.InstanceAddr = r.Common.InstanceAddr
169-
}
170167
}
171168

172169
if !reflect.DeepEqual(r.Common.InstanceInterfaceNames, defaults.Common.InstanceInterfaceNames) {
@@ -175,9 +172,6 @@ func applyInstanceConfigs(r, defaults *ConfigWrapper) {
175172
}
176173
r.Frontend.FrontendV2.InfNames = r.Common.InstanceInterfaceNames
177174
r.IndexGateway.Ring.InstanceInterfaceNames = r.Common.InstanceInterfaceNames
178-
if r.QueryRange.CacheConfig.EmbeddedCache.IsEnabledWithDistributed() {
179-
r.Common.EmbeddedCacheConfig.Ring.InstanceInterfaceNames = r.Common.InstanceInterfaceNames
180-
}
181175
}
182176
}
183177

@@ -305,20 +299,6 @@ func applyConfigToRings(r, defaults *ConfigWrapper, rc util.RingConfig, mergeWit
305299
r.IndexGateway.Ring.ZoneAwarenessEnabled = rc.ZoneAwarenessEnabled
306300
r.IndexGateway.Ring.KVStore = rc.KVStore
307301
}
308-
309-
// EmbeddedCache distributed ring.
310-
if r.QueryRange.CacheConfig.EmbeddedCache.IsEnabledWithDistributed() &&
311-
(mergeWithExisting || reflect.DeepEqual(r.Common.EmbeddedCacheConfig.Ring, defaults.Common.EmbeddedCacheConfig.Ring)) {
312-
r.Common.EmbeddedCacheConfig.Ring.HeartbeatTimeout = rc.HeartbeatTimeout
313-
r.Common.EmbeddedCacheConfig.Ring.HeartbeatPeriod = rc.HeartbeatPeriod
314-
r.Common.EmbeddedCacheConfig.Ring.InstancePort = rc.InstancePort
315-
r.Common.EmbeddedCacheConfig.Ring.InstanceAddr = rc.InstanceAddr
316-
r.Common.EmbeddedCacheConfig.Ring.InstanceID = rc.InstanceID
317-
r.Common.EmbeddedCacheConfig.Ring.InstanceInterfaceNames = rc.InstanceInterfaceNames
318-
r.Common.EmbeddedCacheConfig.Ring.InstanceZone = rc.InstanceZone
319-
r.Common.EmbeddedCacheConfig.Ring.ZoneAwarenessEnabled = rc.ZoneAwarenessEnabled
320-
r.Common.EmbeddedCacheConfig.Ring.KVStore = rc.KVStore
321-
}
322302
}
323303

324304
func applyTokensFilePath(cfg *ConfigWrapper) error {
@@ -349,11 +329,6 @@ func applyTokensFilePath(cfg *ConfigWrapper) error {
349329
}
350330
cfg.IndexGateway.Ring.TokensFilePath = f
351331

352-
f, err = tokensFile(cfg, "groupcache.tokens")
353-
if err != nil {
354-
return err
355-
}
356-
cfg.Common.EmbeddedCacheConfig.Ring.TokensFilePath = f
357332
return nil
358333
}
359334

@@ -431,10 +406,6 @@ func appendLoopbackInterface(cfg, defaults *ConfigWrapper) {
431406
if reflect.DeepEqual(cfg.IndexGateway.Ring.InstanceInterfaceNames, defaults.IndexGateway.Ring.InstanceInterfaceNames) {
432407
cfg.IndexGateway.Ring.InstanceInterfaceNames = append(cfg.IndexGateway.Ring.InstanceInterfaceNames, loopbackIface)
433408
}
434-
435-
if reflect.DeepEqual(cfg.Common.EmbeddedCacheConfig.Ring.InstanceInterfaceNames, defaults.Common.EmbeddedCacheConfig.Ring.InstanceInterfaceNames) {
436-
cfg.Common.EmbeddedCacheConfig.Ring.InstanceInterfaceNames = append(cfg.Common.EmbeddedCacheConfig.Ring.InstanceInterfaceNames, loopbackIface)
437-
}
438409
}
439410

440411
// applyMemberlistConfig will change the default ingester, distributor, ruler, and query scheduler ring configurations to use memberlist.
@@ -448,7 +419,6 @@ func applyMemberlistConfig(r *ConfigWrapper) {
448419
r.QueryScheduler.SchedulerRing.KVStore.Store = memberlistStr
449420
r.CompactorConfig.CompactorRing.KVStore.Store = memberlistStr
450421
r.IndexGateway.Ring.KVStore.Store = memberlistStr
451-
r.Common.EmbeddedCacheConfig.Ring.KVStore.Store = memberlistStr
452422
}
453423

454424
var ErrTooManyStorageConfigs = errors.New("too many storage configs provided in the common config, please only define one storage backend")

pkg/loki/config_wrapper_test.go

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -824,20 +824,17 @@ ingester:
824824
// ensure they are all false by default
825825
config, _, _ := configWrapperFromYAML(t, minimalConfig, nil)
826826
assert.False(t, config.QueryRange.ResultsCacheConfig.CacheConfig.EmbeddedCache.Enabled)
827-
assert.False(t, config.QueryRange.ResultsCacheConfig.CacheConfig.EmbeddedCache.Distributed)
828827

829828
configFileString := `---
830829
query_range:
831830
results_cache:
832831
cache:
833832
embedded_cache:
834-
enabled: true
835-
distributed: true`
833+
enabled: true`
836834

837835
config, _ = testContext(configFileString, nil)
838836

839837
assert.True(t, config.QueryRange.ResultsCacheConfig.CacheConfig.EmbeddedCache.Enabled)
840-
assert.True(t, config.QueryRange.ResultsCacheConfig.CacheConfig.EmbeddedCache.Distributed)
841838
})
842839
}
843840

@@ -867,20 +864,6 @@ chunk_store_config:
867864
assert.False(t, config.ChunkStoreConfig.ChunkCacheConfig.EnableFifoCache)
868865
})
869866

870-
t.Run("if distributed cache is set for results cache, FIFO cache should be disabled.", func(t *testing.T) {
871-
configFileString := `---
872-
query_range:
873-
results_cache:
874-
cache:
875-
embedded_cache:
876-
enabled: true
877-
distributed: true`
878-
879-
config, _, _ := configWrapperFromYAML(t, configFileString, nil)
880-
assert.True(t, config.QueryRange.CacheConfig.EmbeddedCache.IsEnabledWithDistributed())
881-
assert.False(t, config.QueryRange.CacheConfig.EnableFifoCache)
882-
})
883-
884867
t.Run("FIFO cache is enabled by default if no other cache is set", func(t *testing.T) {
885868
config, _, _ := configWrapperFromYAML(t, minimalConfig, nil)
886869
assert.True(t, config.ChunkStoreConfig.ChunkCacheConfig.EnableFifoCache)

pkg/loki/loki.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import (
4343
"github.com/grafana/loki/pkg/scheduler"
4444
internalserver "github.com/grafana/loki/pkg/server"
4545
"github.com/grafana/loki/pkg/storage"
46-
"github.com/grafana/loki/pkg/storage/chunk/cache"
4746
"github.com/grafana/loki/pkg/storage/config"
4847
"github.com/grafana/loki/pkg/storage/stores/indexshipper/compactor"
4948
"github.com/grafana/loki/pkg/storage/stores/indexshipper/compactor/deletion"
@@ -270,7 +269,6 @@ type Loki struct {
270269
queryScheduler *scheduler.Scheduler
271270
usageReport *usagestats.Reporter
272271
indexGatewayRingManager *indexgateway.RingManager
273-
embeddedcacheRingManager *cache.GroupcacheRingManager
274272

275273
clientMetrics storage.ClientMetrics
276274
deleteClientMetrics *deletion.DeleteRequestClientMetrics
@@ -503,7 +501,6 @@ func (t *Loki) setupModuleManager() error {
503501
mm.RegisterModule(RuntimeConfig, t.initRuntimeConfig, modules.UserInvisibleModule)
504502
mm.RegisterModule(MemberlistKV, t.initMemberlistKV, modules.UserInvisibleModule)
505503
mm.RegisterModule(Ring, t.initRing, modules.UserInvisibleModule)
506-
mm.RegisterModule(Embededcache, t.initEmbeddedCache, modules.UserInvisibleModule)
507504
mm.RegisterModule(Overrides, t.initOverrides, modules.UserInvisibleModule)
508505
mm.RegisterModule(OverridesExporter, t.initOverridesExporter)
509506
mm.RegisterModule(TenantConfigs, t.initTenantConfigs, modules.UserInvisibleModule)
@@ -531,16 +528,15 @@ func (t *Loki) setupModuleManager() error {
531528
// Add dependencies
532529
deps := map[string][]string{
533530
Ring: {RuntimeConfig, Server, MemberlistKV},
534-
Embededcache: {RuntimeConfig, Server, MemberlistKV},
535531
UsageReport: {},
536532
Overrides: {RuntimeConfig},
537533
OverridesExporter: {Overrides, Server},
538534
TenantConfigs: {RuntimeConfig},
539535
Distributor: {Ring, Server, Overrides, TenantConfigs, UsageReport},
540-
Store: {Overrides, Embededcache, IndexGatewayRing},
536+
Store: {Overrides, IndexGatewayRing},
541537
Ingester: {Store, Server, MemberlistKV, TenantConfigs, UsageReport},
542538
Querier: {Store, Ring, Server, IngesterQuerier, TenantConfigs, UsageReport, CacheGenerationLoader},
543-
QueryFrontendTripperware: {Server, Embededcache, Overrides, TenantConfigs},
539+
QueryFrontendTripperware: {Server, Overrides, TenantConfigs},
544540
QueryFrontend: {QueryFrontendTripperware, UsageReport, CacheGenerationLoader},
545541
QueryScheduler: {Server, Overrides, MemberlistKV, UsageReport},
546542
Ruler: {Ring, Server, Store, RulerStorage, IngesterQuerier, Overrides, TenantConfigs, UsageReport},

pkg/loki/modules.go

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ import (
3737
"github.com/grafana/loki/pkg/ingester"
3838
"github.com/grafana/loki/pkg/logproto"
3939
"github.com/grafana/loki/pkg/logql"
40-
"github.com/grafana/loki/pkg/logqlmodel/stats"
4140
"github.com/grafana/loki/pkg/lokifrontend/frontend"
4241
"github.com/grafana/loki/pkg/lokifrontend/frontend/transport"
4342
"github.com/grafana/loki/pkg/lokifrontend/frontend/v1/frontendv1pb"
@@ -74,7 +73,6 @@ const maxChunkAgeForTableManager = 12 * time.Hour
7473
// The various modules that make up Loki.
7574
const (
7675
Ring string = "ring"
77-
Embededcache string = "embedded-cache"
7876
RuntimeConfig string = "runtime-config"
7977
Overrides string = "overrides"
8078
OverridesExporter string = "overrides-exporter"
@@ -209,49 +207,6 @@ func (t *Loki) initRing() (_ services.Service, err error) {
209207
return t.ring, nil
210208
}
211209

212-
func (t *Loki) initEmbeddedCache() (_ services.Service, err error) {
213-
if !t.Cfg.QueryRange.CacheConfig.EmbeddedCache.IsEnabledWithDistributed() {
214-
return nil, nil
215-
}
216-
217-
groupCacheConfig := cache.GroupCacheConfig{
218-
Enabled: true,
219-
Ring: t.Cfg.Common.EmbeddedCacheConfig.Ring,
220-
MaxSizeMB: t.Cfg.Common.EmbeddedCacheConfig.MaxSizeMB,
221-
ListenPort: t.Cfg.Common.EmbeddedCacheConfig.ListenPort,
222-
HeartbeatInterval: t.Cfg.Common.EmbeddedCacheConfig.HeartbeatInterval,
223-
HeartbeatTimeout: t.Cfg.Common.EmbeddedCacheConfig.HeartbeatTimeout,
224-
WriteByteTimeout: t.Cfg.Common.EmbeddedCacheConfig.WriteByteTimeout,
225-
}
226-
227-
groupCacheConfig.Ring.ListenPort = groupCacheConfig.ListenPort
228-
229-
rm, err := cache.NewGroupcacheRingManager(groupCacheConfig, util_log.Logger, prometheus.DefaultRegisterer)
230-
if err != nil {
231-
return nil, gerrors.Wrap(err, "new embedded-cache ring manager")
232-
}
233-
234-
t.embeddedcacheRingManager = rm
235-
t.Server.HTTP.Path("/embedded-cache/ring").Methods("GET", "POST").Handler(t.embeddedcacheRingManager)
236-
237-
gc, err := cache.NewGroupCache(rm, groupCacheConfig, util_log.Logger, prometheus.DefaultRegisterer)
238-
if err != nil {
239-
return nil, err
240-
}
241-
242-
groupConfig := cache.GroupConfig{
243-
MaxSizeMB: t.Cfg.QueryRange.CacheConfig.EmbeddedCache.MaxSizeMB,
244-
}
245-
246-
t.Cfg.QueryRange.ResultsCacheConfig.CacheConfig.Cache = gc.NewGroup(
247-
t.Cfg.QueryRange.ResultsCacheConfig.CacheConfig.Prefix+"groupcache",
248-
&groupConfig,
249-
stats.ResultCache,
250-
)
251-
252-
return t.embeddedcacheRingManager, nil
253-
}
254-
255210
func (t *Loki) initRuntimeConfig() (services.Service, error) {
256211
if len(t.Cfg.RuntimeConfig.LoadPath) == 0 {
257212
if len(t.Cfg.LimitsConfig.PerTenantOverrideConfig) != 0 {
@@ -998,9 +953,6 @@ func (t *Loki) initMemberlistKV() (services.Service, error) {
998953
t.Cfg.Ingester.LifecyclerConfig.RingConfig.KVStore.MemberlistKV = t.MemberlistKV.GetMemberlistKV
999954
t.Cfg.QueryScheduler.SchedulerRing.KVStore.MemberlistKV = t.MemberlistKV.GetMemberlistKV
1000955
t.Cfg.Ruler.Ring.KVStore.MemberlistKV = t.MemberlistKV.GetMemberlistKV
1001-
if t.Cfg.QueryRange.CacheConfig.EmbeddedCache.IsEnabledWithDistributed() {
1002-
t.Cfg.Common.EmbeddedCacheConfig.Ring.KVStore.MemberlistKV = t.MemberlistKV.GetMemberlistKV
1003-
}
1004956

1005957
t.Server.HTTP.Handle("/memberlist", t.MemberlistKV)
1006958

0 commit comments

Comments
 (0)