Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new Codec that uses mem.BufferSlice instead of []byte #7356

Merged
merged 100 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
12bf91d
Implement new Codec that uses [][]byte instead of []byte
PapaCharlie Apr 10, 2024
5575dbc
Disable full materialization if the BufferSlice only has one buffer i…
PapaCharlie Apr 10, 2024
3807e4b
Fix tests
PapaCharlie Apr 11, 2024
d9f1aa7
Add copyright
PapaCharlie Apr 11, 2024
abb4993
Complete migration by reading off wire in *Buffers
PapaCharlie Apr 15, 2024
eb442ed
More fixes
PapaCharlie Apr 15, 2024
3578382
transport package mostly fixed!
PapaCharlie Apr 16, 2024
cf49cd1
Nuke `BufferProvider` interface
PapaCharlie Apr 16, 2024
28f5651
Move to new `mem` package, wire in the BufferPool all the way down to…
PapaCharlie Apr 17, 2024
842d3fc
Complete!
PapaCharlie Apr 18, 2024
d3863ec
Address vet
PapaCharlie Apr 18, 2024
1ba7894
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Apr 18, 2024
b4b467f
Always fully materialize the buffers for the binlog
PapaCharlie Apr 18, 2024
466a0ce
First pass at attempting getting the leakcheck package at checking bu…
PapaCharlie Apr 18, 2024
afcd86a
Implement buffer leak check
PapaCharlie Apr 18, 2024
710ee3d
More frees
PapaCharlie Apr 18, 2024
80eab7c
Make referencing a buffer actually create a copy
PapaCharlie Apr 29, 2024
af0a194
Clear unread buffers on stream close
PapaCharlie May 2, 2024
e9deef0
Add ref in SendMsg
PapaCharlie May 2, 2024
74ffdf4
Fix retries
PapaCharlie May 3, 2024
527b644
Fix the retries?
PapaCharlie May 3, 2024
5a1e22e
Drain stream to pass test
PapaCharlie May 3, 2024
13efcff
Cleanups
PapaCharlie May 9, 2024
3e7f3d4
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie May 9, 2024
ab24c18
Remove dial
PapaCharlie May 14, 2024
af4644b
Disable goroutine leak check, but never disable buffer leak check
PapaCharlie May 28, 2024
df282ce
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie May 29, 2024
3ef511e
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie May 30, 2024
017b72f
Add docs and address comments
PapaCharlie Jun 20, 2024
978d448
Getting closer
PapaCharlie Jun 20, 2024
9be25eb
Disable leakcheck
PapaCharlie Jun 26, 2024
23975fc
Add TODO on leakcheck
PapaCharlie Jun 26, 2024
25adf8a
Address comments
PapaCharlie Jun 26, 2024
9962795
Address more comments
PapaCharlie Jun 26, 2024
5ee8565
Clean up buffers when initial call succeeds
PapaCharlie Jul 1, 2024
c5ed951
More touchups
PapaCharlie Jul 1, 2024
a2a6add
Address comments
PapaCharlie Jul 20, 2024
b6894dd
Allow specific tests to skip leakcheck
PapaCharlie Jul 22, 2024
37f1c30
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Jul 22, 2024
9a877dd
Add ForceCodecV2 option
PapaCharlie Jul 22, 2024
be4d4ab
Introduce `mem` package
PapaCharlie Jul 22, 2024
5624e37
Address comments
PapaCharlie Jul 23, 2024
169406f
add a few unit tests for Buffer; fix docstring for NopBufferPool
easwars Jul 23, 2024
c029ea2
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Jul 23, 2024
0889be4
Make BufferSlice.Reader take reference
PapaCharlie Jul 23, 2024
dace88f
Minor tweaks to BufferSlice.Reader
PapaCharlie Jul 23, 2024
14c288c
Improve test
PapaCharlie Jul 24, 2024
ae308d4
more tests and minor touchups
easwars Jul 25, 2024
01ac2c6
fix a bug in BufferSlice.Reader
easwars Jul 25, 2024
1261d3e
add prefix to test names to match type
easwars Jul 25, 2024
50bda24
handle review comments
easwars Jul 26, 2024
4d0eaa5
handle more review comments from dfawley
easwars Jul 26, 2024
f6f8b48
Merge remote-tracking branch 'upstream/master' into pc/mem
PapaCharlie Aug 1, 2024
5aa0784
Address comments
PapaCharlie Aug 1, 2024
284c19c
Merge branch 'pc/mem' into pc/newcodec
PapaCharlie Aug 1, 2024
0d79099
Update after rebase on pc/mem
PapaCharlie Aug 1, 2024
2ee31ca
Rename test
PapaCharlie Aug 1, 2024
c2d9987
Merge branch 'pc/mem' into pc/newcodec
PapaCharlie Aug 1, 2024
3a8cac5
Rename to onFree
PapaCharlie Aug 1, 2024
462e031
Merge branch 'pc/mem' into pc/newcodec
PapaCharlie Aug 1, 2024
30f191b
Fix mem test
PapaCharlie Aug 1, 2024
aaca195
Change to MaterializeToBuffer
PapaCharlie Aug 1, 2024
89ca8a9
Merge branch 'pc/mem' into pc/newcodec
PapaCharlie Aug 1, 2024
84c5817
Fix vet failure
PapaCharlie Aug 1, 2024
87df011
Unbreak uint cast
PapaCharlie Aug 1, 2024
e6c6731
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Aug 5, 2024
5cbb1d7
Reuse `Buffer` instances
PapaCharlie Aug 6, 2024
6e2146e
Fix `prepareMsg`
PapaCharlie Aug 6, 2024
152db3d
Make `BufferPool.Put` take a `*[]byte`
PapaCharlie Aug 6, 2024
981a5dc
Hack hack hack!
PapaCharlie Aug 6, 2024
8c40132
Implement flate.Reader to prevent wrapping
PapaCharlie Aug 6, 2024
5074715
Refactor splitting
PapaCharlie Aug 6, 2024
aa33829
Run it with tags instead
PapaCharlie Aug 9, 2024
19cc6b3
More touchups, tests are passing
PapaCharlie Aug 9, 2024
2efbe0d
Make leaks informational instead of failures
PapaCharlie Aug 9, 2024
35f35e4
Enable test failures with tag
PapaCharlie Aug 10, 2024
606b9f2
Split more stuff
PapaCharlie Aug 10, 2024
5672329
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Aug 10, 2024
8898f13
First pass at object reduction
PapaCharlie Aug 12, 2024
4949fc4
Getting closer
PapaCharlie Aug 12, 2024
d4b487a
Pool ref counters
PapaCharlie Aug 12, 2024
4b0c81e
More fixes
PapaCharlie Aug 13, 2024
44376be
No-copy BufferSlice
PapaCharlie Aug 13, 2024
ebffde3
Finish up the job!
PapaCharlie Aug 13, 2024
b82bf1f
Fix compile error on default tag
PapaCharlie Aug 13, 2024
c87426e
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Aug 13, 2024
83d0322
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Aug 14, 2024
87a408c
Remove tags
PapaCharlie Aug 14, 2024
aaa3f16
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Aug 14, 2024
6c38c43
Fix leakcheck
PapaCharlie Aug 14, 2024
189e620
Fix grpchttp2
PapaCharlie Aug 14, 2024
d557400
Fix vet
PapaCharlie Aug 14, 2024
a7fb1d6
Fix tests
PapaCharlie Aug 14, 2024
299260c
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Aug 20, 2024
6ad4793
Address Doug's comments
PapaCharlie Aug 20, 2024
dab5e1e
rerun tests
PapaCharlie Aug 20, 2024
7ff751b
Add coments
PapaCharlie Aug 20, 2024
bcb1b41
Fix grammar
PapaCharlie Aug 20, 2024
04691be
Fix test
PapaCharlie Aug 21, 2024
27f2187
Merge remote-tracking branch 'upstream/master' into pc/newcodec
PapaCharlie Aug 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ jobs:
goversion: '1.22'
testflags: -race

- type: tests
goversion: '1.22'
testflags: '-race -tags=buffer_pooling'

- type: tests
goversion: '1.22'
goarch: 386
Expand Down
34 changes: 30 additions & 4 deletions benchmark/benchmain/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,11 @@ import (
"google.golang.org/grpc/benchmark/stats"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/encoding/gzip"
"google.golang.org/grpc/experimental"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/internal"
"google.golang.org/grpc/internal/channelz"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/mem"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/test/bufconn"

Expand Down Expand Up @@ -153,6 +153,33 @@ const (
warmuptime = time.Second
)

var useNopBufferPool atomic.Bool

type swappableBufferPool struct {
mem.BufferPool
}

func (p swappableBufferPool) Get(length int) *[]byte {
var pool mem.BufferPool
if useNopBufferPool.Load() {
pool = mem.NopBufferPool{}
} else {
pool = p.BufferPool
}
return pool.Get(length)
}

func (p swappableBufferPool) Put(i *[]byte) {
if useNopBufferPool.Load() {
return
}
p.BufferPool.Put(i)
}

func init() {
internal.SetDefaultBufferPoolForTesting.(func(mem.BufferPool))(swappableBufferPool{mem.DefaultBufferPool()})
}

var (
allWorkloads = []string{workloadsUnary, workloadsStreaming, workloadsUnconstrained, workloadsAll}
allCompModes = []string{compModeOff, compModeGzip, compModeNop, compModeAll}
Expand Down Expand Up @@ -343,10 +370,9 @@ func makeClients(bf stats.Features) ([]testgrpc.BenchmarkServiceClient, func())
}
switch bf.RecvBufferPool {
case recvBufferPoolNil:
// Do nothing.
useNopBufferPool.Store(true)
case recvBufferPoolSimple:
opts = append(opts, experimental.WithRecvBufferPool(grpc.NewSharedBufferPool()))
sopts = append(sopts, experimental.RecvBufferPool(grpc.NewSharedBufferPool()))
// Do nothing as buffering is enabled by default.
default:
logger.Fatalf("Unknown shared recv buffer pool type: %v", bf.RecvBufferPool)
}
Expand Down
75 changes: 68 additions & 7 deletions codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,79 @@
import (
"google.golang.org/grpc/encoding"
_ "google.golang.org/grpc/encoding/proto" // to register the Codec for "proto"
"google.golang.org/grpc/mem"
)

// baseCodec contains the functionality of both Codec and encoding.Codec, but
// omits the name/string, which vary between the two and are not needed for
// anything besides the registry in the encoding package.
// baseCodec captures the new encoding.CodecV2 interface without the Name
// function, allowing it to be implemented by older Codec and encoding.Codec
// implementations. The omitted Name function is only needed for the register in
// the encoding package and is not part of the core functionality.
type baseCodec interface {
Marshal(v any) ([]byte, error)
Unmarshal(data []byte, v any) error
Marshal(v any) (mem.BufferSlice, error)
Unmarshal(data mem.BufferSlice, v any) error
}

// getCodec returns an encoding.CodecV2 for the codec of the given name (if
// registered). Initially checks the V2 registry with encoding.GetCodecV2 and
// returns the V2 codec if it is registered. Otherwise, it checks the V1 registry
// with encoding.GetCodec and if it is registered wraps it with newCodecV1Bridge
// to turn it into an encoding.CodecV2. Returns nil otherwise.
func getCodec(name string) encoding.CodecV2 {
codecV2 := encoding.GetCodecV2(name)
if codecV2 != nil {
return codecV2
}

codecV1 := encoding.GetCodec(name)
if codecV1 != nil {
return newCodecV1Bridge(codecV1)

Check warning on line 49 in codec.go

View check run for this annotation

Codecov / codecov/patch

codec.go#L49

Added line #L49 was not covered by tests
}

return nil
}

var _ baseCodec = Codec(nil)
var _ baseCodec = encoding.Codec(nil)
func newCodecV0Bridge(c Codec) baseCodec {
return codecV0Bridge{codec: c}

Check warning on line 56 in codec.go

View check run for this annotation

Codecov / codecov/patch

codec.go#L55-L56

Added lines #L55 - L56 were not covered by tests
}

func newCodecV1Bridge(c encoding.Codec) encoding.CodecV2 {
return codecV1Bridge{
codecV0Bridge: codecV0Bridge{codec: c},
name: c.Name(),
}
}

var _ baseCodec = codecV0Bridge{}

type codecV0Bridge struct {
codec interface {
Marshal(v any) ([]byte, error)
Unmarshal(data []byte, v any) error
}
}

func (c codecV0Bridge) Marshal(v any) (mem.BufferSlice, error) {
data, err := c.codec.Marshal(v)
if err != nil {
return nil, err
}
return mem.BufferSlice{mem.NewBuffer(&data, nil)}, nil
}

func (c codecV0Bridge) Unmarshal(data mem.BufferSlice, v any) (err error) {
return c.codec.Unmarshal(data.Materialize(), v)
}

var _ encoding.CodecV2 = codecV1Bridge{}

type codecV1Bridge struct {
codecV0Bridge
name string
}

func (c codecV1Bridge) Name() string {
return c.name
}

// Codec defines the interface gRPC uses to encode and decode messages.
// Note that implementations of this interface must be thread safe;
Expand Down
27 changes: 5 additions & 22 deletions dialoptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"google.golang.org/grpc/internal/binarylog"
"google.golang.org/grpc/internal/transport"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/mem"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/stats"
)
Expand Down Expand Up @@ -60,7 +61,7 @@
internal.WithBinaryLogger = withBinaryLogger
internal.JoinDialOptions = newJoinDialOption
internal.DisableGlobalDialOptions = newDisableGlobalDialOptions
internal.WithRecvBufferPool = withRecvBufferPool
internal.WithBufferPool = withBufferPool
}

// dialOptions configure a Dial call. dialOptions are set by the DialOption
Expand Down Expand Up @@ -92,7 +93,6 @@
defaultServiceConfigRawJSON *string
resolvers []resolver.Builder
idleTimeout time.Duration
recvBufferPool SharedBufferPool
defaultScheme string
maxCallAttempts int
}
Expand Down Expand Up @@ -679,11 +679,11 @@
WriteBufferSize: defaultWriteBufSize,
UseProxy: true,
UserAgent: grpcUA,
BufferPool: mem.DefaultBufferPool(),
},
bs: internalbackoff.DefaultExponential,
healthCheckFunc: internal.HealthCheckFunc,
idleTimeout: 30 * time.Minute,
recvBufferPool: nopBufferPool{},
defaultScheme: "dns",
maxCallAttempts: defaultMaxCallAttempts,
}
Expand Down Expand Up @@ -760,25 +760,8 @@
})
}

// WithRecvBufferPool returns a DialOption that configures the ClientConn
// to use the provided shared buffer pool for parsing incoming messages. Depending
// on the application's workload, this could result in reduced memory allocation.
//
// If you are unsure about how to implement a memory pool but want to utilize one,
// begin with grpc.NewSharedBufferPool.
//
// Note: The shared buffer pool feature will not be active if any of the following
// options are used: WithStatsHandler, EnableTracing, or binary logging. In such
// cases, the shared buffer pool will be ignored.
//
// Deprecated: use experimental.WithRecvBufferPool instead. Will be deleted in
// v1.60.0 or later.
func WithRecvBufferPool(bufferPool SharedBufferPool) DialOption {
return withRecvBufferPool(bufferPool)
}

func withRecvBufferPool(bufferPool SharedBufferPool) DialOption {
func withBufferPool(bufferPool mem.BufferPool) DialOption {

Check warning on line 763 in dialoptions.go

View check run for this annotation

Codecov / codecov/patch

dialoptions.go#L763

Added line #L763 was not covered by tests
return newFuncDialOption(func(o *dialOptions) {
o.recvBufferPool = bufferPool
o.copts.BufferPool = bufferPool

Check warning on line 765 in dialoptions.go

View check run for this annotation

Codecov / codecov/patch

dialoptions.go#L765

Added line #L765 was not covered by tests
})
}
82 changes: 82 additions & 0 deletions encoding/encoding_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
*
* Copyright 2024 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package encoding

import (
"strings"

"google.golang.org/grpc/mem"
)

// CodecV2 defines the interface gRPC uses to encode and decode messages. Note
// that implementations of this interface must be thread safe; a CodecV2's
// methods can be called from concurrent goroutines.
type CodecV2 interface {
// Marshal returns the wire format of v. The buffers in the returned
// [mem.BufferSlice] must have at least one reference each, which will be freed
// by gRPC when they are no longer needed.
Marshal(v any) (out mem.BufferSlice, err error)
// Unmarshal parses the wire format into v. Note that data will be freed as soon
// as this function returns. If the codec wishes to guarantee access to the data
// after this function, it must take its own reference that it frees when it is
// no longer needed.
Unmarshal(data mem.BufferSlice, v any) error
// Name returns the name of the Codec implementation. The returned string
// will be used as part of content type in transmission. The result must be
// static; the result cannot change between calls.
Name() string
}

var registeredV2Codecs = make(map[string]CodecV2)
dfawley marked this conversation as resolved.
Show resolved Hide resolved

// RegisterCodecV2 registers the provided CodecV2 for use with all gRPC clients and
// servers.
//
// The CodecV2 will be stored and looked up by result of its Name() method, which
// should match the content-subtype of the encoding handled by the CodecV2. This
// is case-insensitive, and is stored and looked up as lowercase. If the
// result of calling Name() is an empty string, RegisterCodecV2 will panic. See
// Content-Type on
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
// more details.
//
// If both a Codec and CodecV2 are registered with the same name, the CodecV2
// will be used.
//
// NOTE: this function must only be called during initialization time (i.e. in
// an init() function), and is not thread-safe. If multiple Codecs are
// registered with the same name, the one registered last will take effect.
func RegisterCodecV2(codec CodecV2) {
if codec == nil {
panic("cannot register a nil CodecV2")

Check warning on line 67 in encoding/encoding_v2.go

View check run for this annotation

Codecov / codecov/patch

encoding/encoding_v2.go#L67

Added line #L67 was not covered by tests
}
if codec.Name() == "" {
panic("cannot register CodecV2 with empty string result for Name()")

Check warning on line 70 in encoding/encoding_v2.go

View check run for this annotation

Codecov / codecov/patch

encoding/encoding_v2.go#L70

Added line #L70 was not covered by tests
}
contentSubtype := strings.ToLower(codec.Name())
registeredV2Codecs[contentSubtype] = codec
}

// GetCodecV2 gets a registered CodecV2 by content-subtype, or nil if no CodecV2 is
// registered for the content-subtype.
//
// The content-subtype is expected to be lowercase.
func GetCodecV2(contentSubtype string) CodecV2 {
return registeredV2Codecs[contentSubtype]
}
81 changes: 81 additions & 0 deletions encoding/proto/proto_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
*
* Copyright 2024 gRPC authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package proto

import (
"fmt"

"google.golang.org/grpc/encoding"
"google.golang.org/grpc/mem"
"google.golang.org/protobuf/proto"
)

func init() {
encoding.RegisterCodecV2(&codecV2{})
}

// codec is a CodecV2 implementation with protobuf. It is the default codec for
// gRPC.
type codecV2 struct{}

var _ encoding.CodecV2 = (*codecV2)(nil)

func (c *codecV2) Marshal(v any) (data mem.BufferSlice, err error) {
vv := messageV2Of(v)
if vv == nil {
return nil, fmt.Errorf("proto: failed to marshal, message is %T, want proto.Message", v)

Check warning on line 42 in encoding/proto/proto_v2.go

View check run for this annotation

Codecov / codecov/patch

encoding/proto/proto_v2.go#L42

Added line #L42 was not covered by tests
}

size := proto.Size(vv)
if mem.IsBelowBufferPoolingThreshold(size) {
buf, err := proto.Marshal(vv)
if err != nil {
return nil, err

Check warning on line 49 in encoding/proto/proto_v2.go

View check run for this annotation

Codecov / codecov/patch

encoding/proto/proto_v2.go#L49

Added line #L49 was not covered by tests
}
data = append(data, mem.SliceBuffer(buf))
} else {
pool := mem.DefaultBufferPool()
buf := pool.Get(size)
if _, err := (proto.MarshalOptions{}).MarshalAppend((*buf)[:0], vv); err != nil {
pool.Put(buf)
return nil, err

Check warning on line 57 in encoding/proto/proto_v2.go

View check run for this annotation

Codecov / codecov/patch

encoding/proto/proto_v2.go#L56-L57

Added lines #L56 - L57 were not covered by tests
}
data = append(data, mem.NewBuffer(buf, pool))
}

return data, nil
}

func (c *codecV2) Unmarshal(data mem.BufferSlice, v any) (err error) {
vv := messageV2Of(v)
if vv == nil {
return fmt.Errorf("failed to unmarshal, message is %T, want proto.Message", v)

Check warning on line 68 in encoding/proto/proto_v2.go

View check run for this annotation

Codecov / codecov/patch

encoding/proto/proto_v2.go#L68

Added line #L68 was not covered by tests
}

buf := data.MaterializeToBuffer(mem.DefaultBufferPool())
defer buf.Free()
// TODO: Upgrade proto.Unmarshal to support mem.BufferSlice. Right now, it's not
// really possible without a major overhaul of the proto package, but the
// vtprotobuf library may be able to support this.
return proto.Unmarshal(buf.ReadOnlyData(), vv)
}

func (c *codecV2) Name() string {
return Name
}
Loading
Loading