Compare commits

..

7 Commits

Author SHA1 Message Date
bf4143cde5 replace default go resolver with caching resolver
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-03 01:11:08 +03:00
36b7b9f5fb add Live/Ready/Health methods
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-02 13:20:13 +03:00
ae97023092 store: updates for Watcher
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-12-01 19:54:38 +03:00
115ca6a018 logger: add WithAddFields option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-11-29 15:34:02 +03:00
89cf4ef8af store: add missin LazyConnect option
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-11-26 17:48:09 +03:00
2a6ce6d4da add using lazy connect (#361)
#357

Co-authored-by: Василий Толстов <v.tolstov@unistack.org>
Reviewed-on: #361
Reviewed-by: Василий Толстов <v.tolstov@unistack.org>
Co-authored-by: Evstigneev Denis <danteevstigneev@yandex.ru>
Co-committed-by: Evstigneev Denis <danteevstigneev@yandex.ru>
2024-11-26 12:18:17 +03:00
ad19fe2b90 logger/slog: fix race condigtion with Enabled and Level
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
2024-11-24 23:40:54 +03:00
32 changed files with 1064 additions and 443 deletions

View File

@@ -46,6 +46,12 @@ type Broker interface {
BatchSubscribe(ctx context.Context, topic string, h BatchHandler, opts ...SubscribeOption) (Subscriber, error)
// String type of broker
String() string
// Live returns broker liveness
Live() bool
// Ready returns broker readiness
Ready() bool
// Health returns broker health
Health() bool
}
type (

View File

@@ -339,6 +339,18 @@ func (m *memoryBroker) Name() string {
return m.opts.Name
}
func (m *memoryBroker) Live() bool {
return true
}
func (m *memoryBroker) Ready() bool {
return true
}
func (m *memoryBroker) Health() bool {
return true
}
func (m *memoryEvent) Topic() string {
return m.topic
}

View File

@@ -25,6 +25,18 @@ func NewBroker(opts ...Option) *NoopBroker {
return b
}
func (b *NoopBroker) Health() bool {
return true
}
func (b *NoopBroker) Live() bool {
return true
}
func (b *NoopBroker) Ready() bool {
return true
}
func (b *NoopBroker) Name() string {
return b.opts.Name
}

View File

@@ -11,7 +11,6 @@ import (
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register"
"go.unistack.org/micro/v3/router"
@@ -22,8 +21,6 @@ import (
// Options holds client options
type Options struct {
// Transport used for transfer messages
Transport transport.Transport
// Selector used to select needed address
Selector selector.Selector
// Logger used to log messages
@@ -194,18 +191,16 @@ func NewOptions(opts ...Option) Options {
Retry: DefaultRetry,
Retries: DefaultRetries,
RequestTimeout: DefaultRequestTimeout,
DialTimeout: transport.DefaultDialTimeout,
},
Lookup: LookupRoute,
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Router: router.DefaultRouter,
Transport: transport.DefaultTransport,
Lookup: LookupRoute,
PoolSize: DefaultPoolSize,
PoolTTL: DefaultPoolTTL,
Selector: random.NewSelector(),
Logger: logger.DefaultLogger,
Broker: broker.DefaultBroker,
Meter: meter.DefaultMeter,
Tracer: tracer.DefaultTracer,
Router: router.DefaultRouter,
}
for _, o := range opts {
@@ -278,13 +273,6 @@ func PoolTTL(d time.Duration) Option {
}
}
// Transport to use for communication e.g http, rabbitmq, etc
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// Register sets the routers register
func Register(r register.Register) Option {
return func(o *Options) {
@@ -334,14 +322,6 @@ func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
// set the internal tls
o.TLSConfig = t
// set the default transport if one is not
// already set. Required for Init call below.
// set the transport tls
_ = o.Transport.Init(
transport.TLSConfig(t),
)
}
}
@@ -507,13 +487,6 @@ func WithAuthToken(t string) CallOption {
}
}
// WithNetwork is a CallOption which sets the network attribute
func WithNetwork(n string) CallOption {
return func(o *CallOptions) {
o.Network = n
}
}
// WithRouter sets the router to use for this call
func WithRouter(r router.Router) CallOption {
return func(o *CallOptions) {

View File

@@ -38,4 +38,10 @@ type Cluster interface {
Broadcast(ctx context.Context, msg Message, filter ...string) error
// Unicast send message to single member in cluster
Unicast(ctx context.Context, node Node, msg Message) error
// Live returns cluster liveness
Live() bool
// Ready returns cluster readiness
Ready() bool
// Health returns cluster health
Health() bool
}

View File

@@ -4,7 +4,6 @@ package config
import (
"context"
"errors"
"fmt"
"reflect"
"time"
)
@@ -139,7 +138,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeLoad error", c.String()), err)
c.Options().Logger.Error(ctx, c.String()+" BeforeLoad error", err)
if !c.Options().AllowFail {
return err
}
@@ -154,7 +153,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterLoad error", c.String()), err)
c.Options().Logger.Error(ctx, c.String()+" AfterLoad error", err)
if !c.Options().AllowFail {
return err
}
@@ -169,7 +168,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeSave error", c.String()), err)
c.Options().Logger.Error(ctx, c.String()+" BeforeSave error", err)
if !c.Options().AllowFail {
return err
}
@@ -184,7 +183,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterSave error", c.String()), err)
c.Options().Logger.Error(ctx, c.String()+" AfterSave error", err)
if !c.Options().AllowFail {
return err
}
@@ -199,7 +198,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s BeforeInit error", c.String()), err)
c.Options().Logger.Error(ctx, c.String()+" BeforeInit error", err)
if !c.Options().AllowFail {
return err
}
@@ -214,7 +213,7 @@ var (
return nil
}
if err := fn(ctx, c); err != nil {
c.Options().Logger.Error(ctx, fmt.Sprintf("%s AfterInit error", c.String(), err), err)
c.Options().Logger.Error(ctx, c.String()+" AfterInit error", err)
if !c.Options().AllowFail {
return err
}

View File

@@ -80,6 +80,13 @@ func WithContextAttrFuncs(fncs ...ContextAttrFunc) Option {
}
}
// WithAddFields add fields for the logger
func WithAddFields(fields ...interface{}) Option {
return func(o *Options) {
o.Fields = append(o.Fields, fields...)
}
}
// WithFields set default fields for the logger
func WithFields(fields ...interface{}) Option {
return func(o *Options) {

View File

@@ -8,6 +8,7 @@ import (
"runtime"
"strconv"
"sync"
"sync/atomic"
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/semconv"
@@ -33,12 +34,11 @@ var (
type wrapper struct {
h slog.Handler
level logger.Level
level atomic.Int64
}
func (h *wrapper) Enabled(ctx context.Context, level slog.Level) bool {
lvl := slogToLoggerLevel(level)
return h.level.Enabled(lvl)
return level >= slog.Level(int(h.level.Load()))
}
func (h *wrapper) Handle(ctx context.Context, rec slog.Record) error {
@@ -110,20 +110,25 @@ func (s *slogLogger) Clone(opts ...logger.Option) logger.Logger {
attrs, _ := s.argsAttrs(options.Fields)
l := &slogLogger{
handler: &wrapper{level: options.Level, h: s.handler.h.WithAttrs(attrs)},
handler: &wrapper{h: s.handler.h.WithAttrs(attrs)},
opts: options,
}
l.handler.level.Store(int64(loggerToSlogLevel(options.Level)))
return l
}
func (s *slogLogger) V(level logger.Level) bool {
return s.opts.Level.Enabled(level)
s.mu.Lock()
v := s.opts.Level.Enabled(level)
s.mu.Unlock()
return v
}
func (s *slogLogger) Level(level logger.Level) {
s.mu.Lock()
s.opts.Level = level
s.handler.level.Store(int64(loggerToSlogLevel(level)))
s.mu.Unlock()
}
@@ -143,7 +148,8 @@ func (s *slogLogger) Fields(fields ...interface{}) logger.Logger {
}
attrs, _ := s.argsAttrs(fields)
l.handler = &wrapper{level: s.opts.Level, h: s.handler.h.WithAttrs(attrs)}
l.handler = &wrapper{h: s.handler.h.WithAttrs(attrs)}
l.handler.level.Store(int64(loggerToSlogLevel(l.opts.Level)))
return l
}
@@ -166,7 +172,8 @@ func (s *slogLogger) Init(opts ...logger.Option) error {
}
attrs, _ := s.argsAttrs(s.opts.Fields)
s.handler = &wrapper{level: s.opts.Level, h: slog.NewJSONHandler(s.opts.Out, handleOpt).WithAttrs(attrs)}
s.handler = &wrapper{h: slog.NewJSONHandler(s.opts.Out, handleOpt).WithAttrs(attrs)}
s.handler.level.Store(int64(loggerToSlogLevel(s.opts.Level)))
s.mu.Unlock()
return nil

View File

@@ -15,6 +15,34 @@ import (
"go.unistack.org/micro/v3/logger"
)
func TestWithAddFields(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)
l := NewLogger(logger.WithLevel(logger.InfoLevel), logger.WithOutput(buf))
if err := l.Init(); err != nil {
t.Fatal(err)
}
l.Info(ctx, "msg1")
if err := l.Init(logger.WithAddFields("key1", "val1")); err != nil {
t.Fatal(err)
}
l.Info(ctx, "msg2")
if err := l.Init(logger.WithAddFields("key2", "val2")); err != nil {
t.Fatal(err)
}
l.Info(ctx, "msg3")
if !bytes.Contains(buf.Bytes(), []byte(`"key1"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
if !bytes.Contains(buf.Bytes(), []byte(`"key2"`)) {
t.Fatalf("logger error not works, buf contains: %s", buf.Bytes())
}
}
func TestMultipleFieldsWithLevel(t *testing.T) {
ctx := context.TODO()
buf := bytes.NewBuffer(nil)

View File

@@ -5,6 +5,28 @@ import (
"testing"
)
func TestMultipleUsage(t *testing.T) {
ctx := context.TODO()
md := New(0)
md.Set("key1_1", "val1_1", "key1_2", "val1_2", "key1_3", "val1_3")
ctx = NewIncomingContext(ctx, Copy(md))
ctx = NewOutgoingContext(ctx, Copy(md))
imd, _ := FromIncomingContext(ctx)
omd, _ := FromOutgoingContext(ctx)
_ = func(x context.Context) context.Context {
m, _ := FromIncomingContext(x)
m.Del("key1_2")
return ctx
}(ctx)
_ = func(x context.Context) context.Context {
m, _ := FromIncomingContext(x)
m.Del("key1_3")
return ctx
}(ctx)
t.Logf("imd %#+v", imd)
t.Logf("omd %#+v", omd)
}
func TestMetadataSetMultiple(t *testing.T) {
md := New(4)
md.Set("key1", "val1", "key2", "val2", "key3")

View File

@@ -66,6 +66,12 @@ type bro struct {
func (p *bro) Name() string { return p.name }
func (p *bro) Live() bool { return true }
func (p *bro) Ready() bool { return true }
func (p *bro) Health() bool { return true }
func (p *bro) Init(opts ...broker.Option) error { return nil }
// Options returns broker options

View File

@@ -45,6 +45,18 @@ type (
tunnelAddr struct{}
)
func (t *tunBroker) Live() bool {
return true
}
func (t *tunBroker) Ready() bool {
return true
}
func (t *tunBroker) Health() bool {
return true
}
func (t *tunBroker) Init(opts ...broker.Option) error {
for _, o := range opts {
o(&t.opts)

View File

@@ -29,17 +29,32 @@ var (
// and an abstraction over varying implementations
// {consul, etcd, zookeeper, ...}
type Register interface {
// Name returns register name
Name() string
// Init initialize register
Init(...Option) error
// Options returns options for register
Options() Options
// Connect initialize connect to register
Connect(context.Context) error
// Disconnect initialize discconection from register
Disconnect(context.Context) error
// Register service in registry
Register(context.Context, *Service, ...RegisterOption) error
// Deregister service from registry
Deregister(context.Context, *Service, ...DeregisterOption) error
// LookupService in registry
LookupService(context.Context, string, ...LookupOption) ([]*Service, error)
// ListServices in registry
ListServices(context.Context, ...ListOption) ([]*Service, error)
// Watch registry events
Watch(context.Context, ...WatchOption) (Watcher, error)
// String returns registry string representation
String() string
// Live returns register liveness
// Live() bool
// Ready returns register readiness
// Ready() bool
}
// Service holds service register info

View File

@@ -12,9 +12,9 @@ import (
// Resolver is a DNS network resolve
type Resolver struct {
sync.RWMutex
goresolver *net.Resolver
Address string
mu sync.RWMutex
}
// Resolve tries to resolve endpoint address
@@ -39,12 +39,12 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
return []*resolver.Record{rec}, nil
}
r.RLock()
r.mu.RLock()
goresolver := r.goresolver
r.RUnlock()
r.mu.RUnlock()
if goresolver == nil {
r.Lock()
r.mu.Lock()
r.goresolver = &net.Resolver{
Dial: func(ctx context.Context, _ string, _ string) (net.Conn, error) {
d := net.Dialer{
@@ -53,7 +53,7 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) {
return d.DialContext(ctx, "udp", r.Address)
},
}
r.Unlock()
r.mu.Unlock()
}
addrs, err := goresolver.LookupIP(context.TODO(), "ip", host)

View File

@@ -121,6 +121,18 @@ func (n *noopServer) newCodec(contentType string) (codec.Codec, error) {
return nil, codec.ErrUnknownContentType
}
func (n *noopServer) Live() bool {
return true
}
func (n *noopServer) Ready() bool {
return true
}
func (n *noopServer) Health() bool {
return true
}
func (n *noopServer) Handle(handler Handler) error {
n.h = handler
return nil

View File

@@ -12,7 +12,6 @@ import (
"go.unistack.org/micro/v3/logger"
"go.unistack.org/micro/v3/metadata"
"go.unistack.org/micro/v3/meter"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/register"
msync "go.unistack.org/micro/v3/sync"
@@ -37,8 +36,6 @@ type Options struct {
Logger logger.Logger
// Meter holds the meter
Meter meter.Meter
// Transport holds the transport
Transport transport.Transport
/*
// Router for requests
@@ -100,7 +97,6 @@ func NewOptions(opts ...Option) Options {
Tracer: tracer.DefaultTracer,
Broker: broker.DefaultBroker,
Register: register.DefaultRegister,
Transport: transport.DefaultTransport,
Address: DefaultAddress,
Name: DefaultName,
Version: DefaultVersion,
@@ -209,13 +205,6 @@ func Tracer(t tracer.Tracer) Option {
}
}
// Transport mechanism for communication e.g http, rabbitmq, etc
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// Metadata associated with the server
func Metadata(md metadata.Metadata) Option {
return func(o *Options) {
@@ -249,14 +238,6 @@ func TLSConfig(t *tls.Config) Option {
return func(o *Options) {
// set the internal tls
o.TLSConfig = t
// set the default transport if one is not
// already set. Required for Init call below.
// set the transport tls
_ = o.Transport.Init(
transport.TLSConfig(t),
)
}
}

View File

@@ -62,6 +62,12 @@ type Server interface {
Stop() error
// Server implementation
String() string
// Live returns server liveness
Live() bool
// Ready returns server readiness
Ready() bool
// Health returns server health
Health() bool
}
type (

View File

@@ -1,9 +1,11 @@
// Package micro is a pluggable framework for microservices
package micro // import "go.unistack.org/micro/v3"
package micro
import (
"fmt"
"net"
"sync"
"time"
"github.com/KimMachineGun/automemlimit/memlimit"
"go.uber.org/automaxprocs/maxprocs"
@@ -17,6 +19,7 @@ import (
"go.unistack.org/micro/v3/server"
"go.unistack.org/micro/v3/store"
"go.unistack.org/micro/v3/tracer"
utildns "go.unistack.org/micro/v3/util/dns"
)
func init() {
@@ -30,6 +33,8 @@ func init() {
),
),
)
net.DefaultResolver = utildns.NewNetResolver(utildns.Timeout(1 * time.Second))
}
// Service is an interface that wraps the lower level components.
@@ -72,8 +77,14 @@ type Service interface {
Start() error
// Stop the service
Stop() error
// The service implementation
// String service representation
String() string
// Live returns service liveness
Live() bool
// Ready returns service readiness
Ready() bool
// Health returns service health
Health() bool
}
// RegisterHandler is syntactic sugar for registering a handler
@@ -101,9 +112,7 @@ func (s *service) Name() string {
return s.opts.Name
}
// Init initialises options. Additionally it calls cmd.Init
// which parses command line flags. cmd.Init is only called
// on first Init.
// Init initialises options.
//
//nolint:gocyclo
func (s *service) Init(opts ...Option) error {
@@ -252,6 +261,63 @@ func (s *service) String() string {
return s.opts.Name
}
func (s *service) Live() bool {
for _, v := range s.opts.Brokers {
if !v.Live() {
return false
}
}
for _, v := range s.opts.Servers {
if !v.Live() {
return false
}
}
for _, v := range s.opts.Stores {
if !v.Live() {
return false
}
}
return true
}
func (s *service) Ready() bool {
for _, v := range s.opts.Brokers {
if !v.Ready() {
return false
}
}
for _, v := range s.opts.Servers {
if !v.Ready() {
return false
}
}
for _, v := range s.opts.Stores {
if !v.Ready() {
return false
}
}
return true
}
func (s *service) Health() bool {
for _, v := range s.opts.Brokers {
if !v.Health() {
return false
}
}
for _, v := range s.opts.Servers {
if !v.Health() {
return false
}
}
for _, v := range s.opts.Stores {
if !v.Health() {
return false
}
}
return true
}
//nolint:gocyclo
func (s *service) Start() error {
var err error
@@ -281,10 +347,6 @@ func (s *service) Start() error {
config.Loggers[0].Info(s.opts.Context, fmt.Sprintf("starting [service] %s version %s", s.Options().Name, s.Options().Version))
}
if len(s.opts.Servers) == 0 {
return fmt.Errorf("cant start nil server")
}
for _, reg := range s.opts.Registers {
if err = reg.Connect(s.opts.Context); err != nil {
return err

View File

@@ -4,6 +4,7 @@ import (
"context"
"sort"
"strings"
"sync/atomic"
"time"
cache "github.com/patrickmn/go-cache"
@@ -20,7 +21,10 @@ func NewStore(opts ...store.Option) store.Store {
}
func (m *memoryStore) Connect(ctx context.Context) error {
return nil
if m.opts.LazyConnect {
return nil
}
return m.connect(ctx)
}
func (m *memoryStore) Disconnect(ctx context.Context) error {
@@ -29,13 +33,14 @@ func (m *memoryStore) Disconnect(ctx context.Context) error {
}
type memoryStore struct {
funcRead store.FuncRead
funcWrite store.FuncWrite
funcExists store.FuncExists
funcList store.FuncList
funcDelete store.FuncDelete
store *cache.Cache
opts store.Options
funcRead store.FuncRead
funcWrite store.FuncWrite
funcExists store.FuncExists
funcList store.FuncList
funcDelete store.FuncDelete
store *cache.Cache
opts store.Options
isConnected atomic.Int32
}
func (m *memoryStore) key(prefix, key string) string {
@@ -144,7 +149,24 @@ func (m *memoryStore) Name() string {
return m.opts.Name
}
func (m *memoryStore) Live() bool {
return true
}
func (m *memoryStore) Ready() bool {
return true
}
func (m *memoryStore) Health() bool {
return true
}
func (m *memoryStore) Exists(ctx context.Context, key string, opts ...store.ExistsOption) error {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return err
}
}
return m.funcExists(ctx, key, opts...)
}
@@ -157,6 +179,11 @@ func (m *memoryStore) fnExists(ctx context.Context, key string, opts ...store.Ex
}
func (m *memoryStore) Read(ctx context.Context, key string, val interface{}, opts ...store.ReadOption) error {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return err
}
}
return m.funcRead(ctx, key, val, opts...)
}
@@ -169,6 +196,11 @@ func (m *memoryStore) fnRead(ctx context.Context, key string, val interface{}, o
}
func (m *memoryStore) Write(ctx context.Context, key string, val interface{}, opts ...store.WriteOption) error {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return err
}
}
return m.funcWrite(ctx, key, val, opts...)
}
@@ -193,6 +225,11 @@ func (m *memoryStore) fnWrite(ctx context.Context, key string, val interface{},
}
func (m *memoryStore) Delete(ctx context.Context, key string, opts ...store.DeleteOption) error {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return err
}
}
return m.funcDelete(ctx, key, opts...)
}
@@ -211,6 +248,11 @@ func (m *memoryStore) Options() store.Options {
}
func (m *memoryStore) List(ctx context.Context, opts ...store.ListOption) ([]string, error) {
if m.opts.LazyConnect {
if err := m.connect(ctx); err != nil {
return nil, err
}
}
return m.funcList(ctx, opts...)
}
@@ -244,3 +286,21 @@ func (m *memoryStore) fnList(ctx context.Context, opts ...store.ListOption) ([]s
return keys, nil
}
func (m *memoryStore) connect(ctx context.Context) error {
m.isConnected.CompareAndSwap(0, 1)
return nil
}
func (m *memoryStore) Watch(ctx context.Context, opts ...store.WatchOption) (store.Watcher, error) {
return &watcher{}, nil
}
type watcher struct{}
func (w *watcher) Next() (store.Event, error) {
return nil, nil
}
func (w *watcher) Stop() {
}

View File

@@ -2,19 +2,37 @@ package store
import (
"context"
"sync"
"sync/atomic"
"go.unistack.org/micro/v3/options"
"go.unistack.org/micro/v3/util/id"
)
var _ Store = (*noopStore)(nil)
type noopStore struct {
funcRead FuncRead
funcWrite FuncWrite
funcExists FuncExists
funcList FuncList
funcDelete FuncDelete
opts Options
mu sync.Mutex
watchers map[string]Watcher
funcRead FuncRead
funcWrite FuncWrite
funcExists FuncExists
funcList FuncList
funcDelete FuncDelete
opts Options
isConnected atomic.Int32
}
func (n *noopStore) Live() bool {
return true
}
func (n *noopStore) Ready() bool {
return true
}
func (n *noopStore) Health() bool {
return true
}
func NewStore(opts ...Option) *noopStore {
@@ -52,12 +70,10 @@ func (n *noopStore) Init(opts ...Option) error {
}
func (n *noopStore) Connect(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
if n.opts.LazyConnect {
return nil
}
return nil
return n.connect(ctx)
}
func (n *noopStore) Disconnect(ctx context.Context) error {
@@ -70,6 +86,11 @@ func (n *noopStore) Disconnect(ctx context.Context) error {
}
func (n *noopStore) Read(ctx context.Context, key string, val interface{}, opts ...ReadOption) error {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return err
}
}
return n.funcRead(ctx, key, val, opts...)
}
@@ -83,6 +104,11 @@ func (n *noopStore) fnRead(ctx context.Context, key string, val interface{}, opt
}
func (n *noopStore) Delete(ctx context.Context, key string, opts ...DeleteOption) error {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return err
}
}
return n.funcDelete(ctx, key, opts...)
}
@@ -96,6 +122,11 @@ func (n *noopStore) fnDelete(ctx context.Context, key string, opts ...DeleteOpti
}
func (n *noopStore) Exists(ctx context.Context, key string, opts ...ExistsOption) error {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return err
}
}
return n.funcExists(ctx, key, opts...)
}
@@ -109,6 +140,11 @@ func (n *noopStore) fnExists(ctx context.Context, key string, opts ...ExistsOpti
}
func (n *noopStore) Write(ctx context.Context, key string, val interface{}, opts ...WriteOption) error {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return err
}
}
return n.funcWrite(ctx, key, val, opts...)
}
@@ -122,6 +158,11 @@ func (n *noopStore) fnWrite(ctx context.Context, key string, val interface{}, op
}
func (n *noopStore) List(ctx context.Context, opts ...ListOption) ([]string, error) {
if n.opts.LazyConnect {
if err := n.connect(ctx); err != nil {
return nil, err
}
}
return n.funcList(ctx, opts...)
}
@@ -145,3 +186,53 @@ func (n *noopStore) String() string {
func (n *noopStore) Options() Options {
return n.opts
}
func (n *noopStore) connect(ctx context.Context) error {
if n.isConnected.CompareAndSwap(0, 1) {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
}
return nil
}
type watcher struct {
exit chan bool
id string
ch chan Event
opts WatchOptions
}
func (m *noopStore) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
id, err := id.New()
if err != nil {
return nil, err
}
wo, err := NewWatchOptions(opts...)
if err != nil {
return nil, err
}
// construct the watcher
w := &watcher{
exit: make(chan bool),
ch: make(chan Event),
id: id,
opts: wo,
}
m.mu.Lock()
m.watchers[w.id] = w
m.mu.Unlock()
return w, nil
}
func (w *watcher) Next() (Event, error) {
return nil, nil
}
func (w *watcher) Stop() {
}

View File

@@ -41,6 +41,8 @@ type Options struct {
Timeout time.Duration
// Hooks can be run before/after store Read/List/Write/Exists/Delete
Hooks options.Hooks
// LazyConnect creates a connection when using store
LazyConnect bool
}
// NewOptions creates options struct
@@ -132,6 +134,13 @@ func Timeout(td time.Duration) Option {
}
}
// LazyConnect initialize connection only when needed
func LazyConnect(b bool) Option {
return func(o *Options) {
o.LazyConnect = b
}
}
// Addrs contains the addresses or other connection information of the backing storage.
// For example, an etcd implementation would contain the nodes of the cluster.
// A SQL implementation could contain one or more connection strings.

View File

@@ -4,9 +4,11 @@ package store
import (
"context"
"errors"
"time"
)
var (
ErrWatcherStopped = errors.New("watcher stopped")
// ErrNotConnected is returned when a store is not connected
ErrNotConnected = errors.New("not conected")
// ErrNotFound is returned when a key doesn't exist
@@ -43,6 +45,14 @@ type Store interface {
Disconnect(ctx context.Context) error
// String returns the name of the implementation.
String() string
// Watch returns events watcher
Watch(ctx context.Context, opts ...WatchOption) (Watcher, error)
// Live returns store liveness
Live() bool
// Ready returns store readiness
Ready() bool
// Health returns store health
Health() bool
}
type (
@@ -57,3 +67,41 @@ type (
FuncList func(ctx context.Context, opts ...ListOption) ([]string, error)
HookList func(next FuncList) FuncList
)
type EventType int
const (
EventTypeUnknown = iota
EventTypeConnect
EventTypeDisconnect
EventTypeOpError
)
type Event interface {
Timestamp() time.Time
Error() error
Type() EventType
}
type Watcher interface {
// Next is a blocking call
Next() (Event, error)
// Stop stops the watcher
Stop()
}
type WatchOption func(*WatchOptions) error
type WatchOptions struct{}
func NewWatchOptions(opts ...WatchOption) (WatchOptions, error) {
options := WatchOptions{}
var err error
for _, o := range opts {
if err = o(&options); err != nil {
break
}
}
return options, err
}

View File

@@ -67,16 +67,18 @@ func (w *NamespaceStore) String() string {
return w.s.String()
}
// type NamespaceWrapper struct{}
// func NewNamespaceWrapper() Wrapper {
// return &NamespaceWrapper{}
// }
/*
func (w *OmitWrapper) Logf(fn LogfFunc) LogfFunc {
return func(ctx context.Context, level Level, msg string, args ...interface{}) {
fn(ctx, level, msg, getArgs(args)...)
}
func (w *NamespaceStore) Watch(ctx context.Context, opts ...WatchOption) (Watcher, error) {
return w.s.Watch(ctx, opts...)
}
func (w *NamespaceStore) Live() bool {
return w.s.Live()
}
func (w *NamespaceStore) Ready() bool {
return w.s.Ready()
}
func (w *NamespaceStore) Health() bool {
return w.s.Health()
}
*/

377
util/dns/cache.go Normal file
View File

@@ -0,0 +1,377 @@
package dns
import (
"context"
"math"
"net"
"sync"
"time"
)
// DialFunc is a [net.Resolver.Dial] function.
type DialFunc func(ctx context.Context, network, address string) (net.Conn, error)
// NewNetResolver creates a caching [net.Resolver] that uses parent to resolve names.
func NewNetResolver(opts ...Option) *net.Resolver {
options := Options{Resolver: &net.Resolver{}}
for _, o := range opts {
o(&options)
}
return &net.Resolver{
PreferGo: true,
StrictErrors: options.Resolver.StrictErrors,
Dial: NewNetDialer(options.Resolver.Dial, append(opts, Resolver(options.Resolver))...),
}
}
// NewNetDialer adds caching to a [net.Resolver.Dial] function.
func NewNetDialer(parent DialFunc, opts ...Option) DialFunc {
cache := cache{dial: parent, opts: Options{}}
for _, o := range opts {
o(&cache.opts)
}
if cache.opts.MaxCacheEntries == 0 {
cache.opts.MaxCacheEntries = DefaultMaxCacheEntries
}
return func(ctx context.Context, network, address string) (net.Conn, error) {
conn := &dnsConn{}
conn.roundTrip = cachingRoundTrip(&cache, network, address)
return conn, nil
}
}
const DefaultMaxCacheEntries = 300
// A Option customizes the resolver cache.
type Option func(*Options)
type Options struct {
Resolver *net.Resolver
MaxCacheEntries int
MaxCacheTTL time.Duration
MinCacheTTL time.Duration
NegativeCache bool
PreferIPV4 bool
PreferIPV6 bool
Timeout time.Duration
}
// MaxCacheEntries sets the maximum number of entries to cache.
// If zero, [DefaultMaxCacheEntries] is used; negative means no limit.
func MaxCacheEntries(n int) Option {
return func(o *Options) {
o.MaxCacheEntries = n
}
}
// MaxCacheTTL sets the maximum time-to-live for entries in the cache.
func MaxCacheTTL(td time.Duration) Option {
return func(o *Options) {
o.MaxCacheTTL = td
}
}
// MinCacheTTL sets the minimum time-to-live for entries in the cache.
func MinCacheTTL(td time.Duration) Option {
return func(o *Options) {
o.MinCacheTTL = td
}
}
// NegativeCache sets whether to cache negative responses.
func NegativeCache(b bool) Option {
return func(o *Options) {
o.NegativeCache = b
}
}
// Timeout sets upstream *net.Resolver timeout
func Timeout(td time.Duration) Option {
return func(o *Options) {
o.Timeout = td
}
}
// Resolver sets upstream *net.Resolver.
func Resolver(r *net.Resolver) Option {
return func(o *Options) {
o.Resolver = r
}
}
// PreferIPV4 resolve ipv4 records.
func PreferIPV4(b bool) Option {
return func(o *Options) {
o.PreferIPV4 = b
}
}
// PreferIPV6 resolve ipv4 records.
func PreferIPV6(b bool) Option {
return func(o *Options) {
o.PreferIPV6 = b
}
}
type cache struct {
sync.RWMutex
dial DialFunc
entries map[string]cacheEntry
opts Options
}
type cacheEntry struct {
deadline time.Time
value string
}
func (c *cache) put(req string, res string) {
// ignore uncacheable/unparseable answers
if invalid(req, res) {
return
}
// ignore errors (if requested)
if nameError(res) && !c.opts.NegativeCache {
return
}
// ignore uncacheable/unparseable answers
ttl := getTTL(res)
if ttl <= 0 {
return
}
// adjust TTL
if ttl < c.opts.MinCacheTTL {
ttl = c.opts.MinCacheTTL
}
// maxTTL overrides minTTL
if ttl > c.opts.MaxCacheTTL && c.opts.MaxCacheTTL != 0 {
ttl = c.opts.MaxCacheTTL
}
c.Lock()
defer c.Unlock()
if c.entries == nil {
c.entries = make(map[string]cacheEntry)
}
// do some cache evition
var tested, evicted int
for k, e := range c.entries {
if time.Until(e.deadline) <= 0 {
// delete expired entry
delete(c.entries, k)
evicted++
}
tested++
if tested < 8 {
continue
}
if evicted == 0 && c.opts.MaxCacheEntries > 0 && len(c.entries) >= c.opts.MaxCacheEntries {
// delete at least one entry
delete(c.entries, k)
}
break
}
// remove message IDs
c.entries[req[2:]] = cacheEntry{
deadline: time.Now().Add(ttl),
value: res[2:],
}
}
func (c *cache) get(req string) (res string) {
// ignore invalid messages
if len(req) < 12 {
return ""
}
if req[2] >= 0x7f {
return ""
}
c.RLock()
defer c.RUnlock()
if c.entries == nil {
return ""
}
// remove message ID
entry, ok := c.entries[req[2:]]
if ok && time.Until(entry.deadline) > 0 {
// prepend correct ID
return req[:2] + entry.value
}
return ""
}
func invalid(req string, res string) bool {
if len(req) < 12 || len(res) < 12 { // header size
return true
}
if req[0] != res[0] || req[1] != res[1] { // IDs match
return true
}
if req[2] >= 0x7f || res[2] < 0x7f { // query, response
return true
}
if req[2]&0x7a != 0 || res[2]&0x7a != 0 { // standard query, not truncated
return true
}
if res[3]&0xf != 0 && res[3]&0xf != 3 { // no error, or name error
return true
}
return false
}
func nameError(res string) bool {
return res[3]&0xf == 3
}
func getTTL(msg string) time.Duration {
ttl := math.MaxInt32
qdcount := getUint16(msg[4:])
ancount := getUint16(msg[6:])
nscount := getUint16(msg[8:])
arcount := getUint16(msg[10:])
rdcount := ancount + nscount + arcount
msg = msg[12:] // skip header
// skip questions
for i := 0; i < qdcount; i++ {
name := getNameLen(msg)
if name < 0 || name+4 > len(msg) {
return -1
}
msg = msg[name+4:]
}
// parse records
for i := 0; i < rdcount; i++ {
name := getNameLen(msg)
if name < 0 || name+10 > len(msg) {
return -1
}
rtyp := getUint16(msg[name+0:])
rttl := getUint32(msg[name+4:])
rlen := getUint16(msg[name+8:])
if name+10+rlen > len(msg) {
return -1
}
// skip EDNS OPT since it doesn't have a TTL
if rtyp != 41 && rttl < ttl {
ttl = rttl
}
msg = msg[name+10+rlen:]
}
return time.Duration(ttl) * time.Second
}
func getNameLen(msg string) int {
i := 0
for i < len(msg) {
if msg[i] == 0 {
// end of name
i += 1
break
}
if msg[i] >= 0xc0 {
// compressed name
i += 2
break
}
if msg[i] >= 0x40 {
// reserved
return -1
}
i += int(msg[i] + 1)
}
return i
}
func getUint16(s string) int {
return int(s[1]) | int(s[0])<<8
}
func getUint32(s string) int {
return int(s[3]) | int(s[2])<<8 | int(s[1])<<16 | int(s[0])<<24
}
func cachingRoundTrip(cache *cache, network, address string) roundTripper {
return func(ctx context.Context, req string) (res string, err error) {
// check cache
if res := cache.get(req); res != "" {
return res, nil
}
switch {
case cache.opts.PreferIPV4 && cache.opts.PreferIPV6:
network = "udp"
case cache.opts.PreferIPV4:
network = "udp4"
case cache.opts.PreferIPV6:
network = "udp6"
default:
network = "udp"
}
if cache.opts.Timeout > 0 {
var cancel func()
ctx, cancel = context.WithTimeout(ctx, cache.opts.Timeout)
defer cancel()
}
// dial connection
var conn net.Conn
if cache.dial != nil {
conn, err = cache.dial(ctx, network, address)
} else {
var d net.Dialer
conn, err = d.DialContext(ctx, network, address)
}
if err != nil {
return "", err
}
ctx, cancel := context.WithCancel(ctx)
go func() {
<-ctx.Done()
conn.Close()
}()
defer cancel()
if t, ok := ctx.Deadline(); ok {
err = conn.SetDeadline(t)
if err != nil {
return "", err
}
}
// send request
err = writeMessage(conn, req)
if err != nil {
return "", err
}
// read response
res, err = readMessage(conn)
if err != nil {
return "", err
}
// cache response
cache.put(req, res)
return res, nil
}
}

16
util/dns/cache_test.go Normal file
View File

@@ -0,0 +1,16 @@
package dns
import (
"net"
"testing"
)
func TestCache(t *testing.T) {
net.DefaultResolver = NewNetResolver(PreferIPV4(true))
addrs, err := net.LookupHost("unistack.org")
if err != nil {
t.Fatal(err)
}
t.Logf("addrs %v", addrs)
}

178
util/dns/conn.go Normal file
View File

@@ -0,0 +1,178 @@
package dns
import (
"bytes"
"context"
"io"
"net"
"strings"
"sync"
"time"
)
type dnsConn struct {
sync.Mutex
ibuf bytes.Buffer
obuf bytes.Buffer
ctx context.Context
cancel context.CancelFunc
deadline time.Time
roundTrip roundTripper
}
type roundTripper func(ctx context.Context, req string) (res string, err error)
func (c *dnsConn) Read(b []byte) (n int, err error) {
imsg, n, err := c.drainBuffers(b)
if n != 0 || err != nil {
return n, err
}
ctx, cancel := c.childContext()
omsg, err := c.roundTrip(ctx, imsg)
cancel()
if err != nil {
return 0, err
}
return c.fillBuffer(b, omsg)
}
func (c *dnsConn) Write(b []byte) (n int, err error) {
c.Lock()
defer c.Unlock()
return c.ibuf.Write(b)
}
func (c *dnsConn) Close() error {
c.Lock()
cancel := c.cancel
c.Unlock()
if cancel != nil {
cancel()
}
return nil
}
func (c *dnsConn) LocalAddr() net.Addr {
return nil
}
func (c *dnsConn) RemoteAddr() net.Addr {
return nil
}
func (c *dnsConn) SetDeadline(t time.Time) error {
c.SetReadDeadline(t)
c.SetWriteDeadline(t)
return nil
}
func (c *dnsConn) SetReadDeadline(t time.Time) error {
c.Lock()
defer c.Unlock()
c.deadline = t
return nil
}
func (c *dnsConn) SetWriteDeadline(t time.Time) error {
// writes do not timeout
return nil
}
func (c *dnsConn) drainBuffers(b []byte) (string, int, error) {
c.Lock()
defer c.Unlock()
// drain the output buffer
if c.obuf.Len() > 0 {
n, err := c.obuf.Read(b)
return "", n, err
}
// otherwise, get the next message from the input buffer
sz := c.ibuf.Next(2)
if len(sz) < 2 {
return "", 0, io.ErrUnexpectedEOF
}
size := int64(sz[0])<<8 | int64(sz[1])
var str strings.Builder
_, err := io.CopyN(&str, &c.ibuf, size)
if err == io.EOF {
return "", 0, io.ErrUnexpectedEOF
}
if err != nil {
return "", 0, err
}
return str.String(), 0, nil
}
func (c *dnsConn) fillBuffer(b []byte, str string) (int, error) {
c.Lock()
defer c.Unlock()
c.obuf.WriteByte(byte(len(str) >> 8))
c.obuf.WriteByte(byte(len(str)))
c.obuf.WriteString(str)
return c.obuf.Read(b)
}
func (c *dnsConn) childContext() (context.Context, context.CancelFunc) {
c.Lock()
defer c.Unlock()
if c.ctx == nil {
c.ctx, c.cancel = context.WithCancel(context.Background())
}
return context.WithDeadline(c.ctx, c.deadline)
}
func writeMessage(conn net.Conn, msg string) error {
var buf []byte
if _, ok := conn.(net.PacketConn); ok {
buf = []byte(msg)
} else {
buf = make([]byte, len(msg)+2)
buf[0] = byte(len(msg) >> 8)
buf[1] = byte(len(msg))
copy(buf[2:], msg)
}
// SHOULD do a single write on TCP (RFC 7766, section 8).
// MUST do a single write on UDP.
_, err := conn.Write(buf)
return err
}
func readMessage(c net.Conn) (string, error) {
if _, ok := c.(net.PacketConn); ok {
// RFC 1035 specifies 512 as the maximum message size for DNS over UDP.
// RFC 6891 OTOH suggests 4096 as the maximum payload size for EDNS.
b := make([]byte, 4096)
n, err := c.Read(b)
if err != nil {
return "", err
}
return string(b[:n]), nil
} else {
var sz [2]byte
_, err := io.ReadFull(c, sz[:])
if err != nil {
return "", err
}
size := int64(sz[0])<<8 | int64(sz[1])
var str strings.Builder
_, err = io.CopyN(&str, c, size)
if err == io.EOF {
return "", io.ErrUnexpectedEOF
}
if err != nil {
return "", err
}
return str.String(), nil
}
}

View File

@@ -1,40 +0,0 @@
// Package io is for io management
package io
import (
"io"
"go.unistack.org/micro/v3/network/transport"
)
type rwc struct {
socket transport.Socket
}
func (r *rwc) Read(p []byte) (n int, err error) {
m := new(transport.Message)
if err := r.socket.Recv(m); err != nil {
return 0, err
}
copy(p, m.Body)
return len(m.Body), nil
}
func (r *rwc) Write(p []byte) (n int, err error) {
err = r.socket.Send(&transport.Message{
Body: p,
})
if err != nil {
return 0, err
}
return len(p), nil
}
func (r *rwc) Close() error {
return r.socket.Close()
}
// NewRWC returns a new ReadWriteCloser
func NewRWC(sock transport.Socket) io.ReadWriteCloser {
return &rwc{sock}
}

View File

@@ -1,118 +0,0 @@
package pool
import (
"context"
"sync"
"time"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/util/id"
)
type pool struct {
tr transport.Transport
conns map[string][]*poolConn
size int
ttl time.Duration
sync.Mutex
}
type poolConn struct {
created time.Time
transport.Client
id string
}
func newPool(options Options) *pool {
return &pool{
size: options.Size,
tr: options.Transport,
ttl: options.TTL,
conns: make(map[string][]*poolConn),
}
}
func (p *pool) Close() error {
p.Lock()
for k, c := range p.conns {
for _, conn := range c {
conn.Client.Close()
}
delete(p.conns, k)
}
p.Unlock()
return nil
}
// NoOp the Close since we manage it
func (p *poolConn) Close() error {
return nil
}
func (p *poolConn) ID() string {
return p.id
}
func (p *poolConn) Created() time.Time {
return p.created
}
func (p *pool) Get(ctx context.Context, addr string, opts ...transport.DialOption) (Conn, error) {
p.Lock()
conns := p.conns[addr]
// while we have conns check age and then return one
// otherwise we'll create a new conn
for len(conns) > 0 {
conn := conns[len(conns)-1]
conns = conns[:len(conns)-1]
p.conns[addr] = conns
// if conn is old kill it and move on
if d := time.Since(conn.Created()); d > p.ttl {
conn.Client.Close()
continue
}
// we got a good conn, lets unlock and return it
p.Unlock()
return conn, nil
}
p.Unlock()
// create new conn
c, err := p.tr.Dial(ctx, addr, opts...)
if err != nil {
return nil, err
}
id, err := id.New()
if err != nil {
return nil, err
}
return &poolConn{
Client: c,
id: id,
created: time.Now(),
}, nil
}
func (p *pool) Release(conn Conn, err error) error {
// don't store the conn if it has errored
if err != nil {
return conn.(*poolConn).Client.Close()
}
// otherwise put it back for reuse
p.Lock()
conns := p.conns[conn.Remote()]
if len(conns) >= p.size {
p.Unlock()
return conn.(*poolConn).Client.Close()
}
p.conns[conn.Remote()] = append(conns, conn.(*poolConn))
p.Unlock()
return nil
}

View File

@@ -1,92 +0,0 @@
//go:build ignore
// +build ignore
package pool
import (
"testing"
"time"
"go.unistack.org/micro/v3/network/transport"
"go.unistack.org/micro/v3/network/transport/memory"
)
func testPool(t *testing.T, size int, ttl time.Duration) {
// mock transport
tr := memory.NewTransport()
options := Options{
TTL: ttl,
Size: size,
Transport: tr,
}
// zero pool
p := newPool(options)
// listen
l, err := tr.Listen(":0")
if err != nil {
t.Fatal(err)
}
defer l.Close()
// accept loop
go func() {
for {
if err := l.Accept(func(s transport.Socket) {
for {
var msg transport.Message
if err := s.Recv(&msg); err != nil {
return
}
if err := s.Send(&msg); err != nil {
return
}
}
}); err != nil {
return
}
}
}()
for i := 0; i < 10; i++ {
// get a conn
c, err := p.Get(l.Addr())
if err != nil {
t.Fatal(err)
}
msg := &transport.Message{
Body: []byte(`hello world`),
}
if err := c.Send(msg); err != nil {
t.Fatal(err)
}
var rcv transport.Message
if err := c.Recv(&rcv); err != nil {
t.Fatal(err)
}
if string(rcv.Body) != string(msg.Body) {
t.Fatalf("got %v, expected %v", rcv.Body, msg.Body)
}
// release the conn
p.Release(c, nil)
p.Lock()
if i := len(p.conns[l.Addr()]); i > size {
p.Unlock()
t.Fatalf("pool size %d is greater than expected %d", i, size)
}
p.Unlock()
}
}
func TestClientPool(t *testing.T) {
testPool(t, 0, time.Minute)
testPool(t, 2, time.Minute)
}

View File

@@ -1,38 +0,0 @@
package pool
import (
"time"
"go.unistack.org/micro/v3/network/transport"
)
// Options struct
type Options struct {
Transport transport.Transport
TTL time.Duration
Size int
}
// Option func signature
type Option func(*Options)
// Size sets the size
func Size(i int) Option {
return func(o *Options) {
o.Size = i
}
}
// Transport sets the transport
func Transport(t transport.Transport) Option {
return func(o *Options) {
o.Transport = t
}
}
// TTL specifies ttl
func TTL(t time.Duration) Option {
return func(o *Options) {
o.TTL = t
}
}

View File

@@ -1,38 +0,0 @@
// Package pool is a connection pool
package pool
import (
"context"
"time"
"go.unistack.org/micro/v3/network/transport"
)
// Pool is an interface for connection pooling
type Pool interface {
// Close the pool
Close() error
// Get a connection
Get(ctx context.Context, addr string, opts ...transport.DialOption) (Conn, error)
// Release the connection
Release(c Conn, status error) error
}
// Conn conn pool interface
type Conn interface {
// unique id of connection
ID() string
// time it was created
Created() time.Time
// embedded connection
transport.Client
}
// NewPool creates new connection pool
func NewPool(opts ...Option) Pool {
options := Options{}
for _, o := range opts {
o(&options)
}
return newPool(options)
}

View File

@@ -23,7 +23,7 @@ func TestMarshalYAML(t *testing.T) {
func TestUnmarshalYAML(t *testing.T) {
type str struct {
TTL Duration `yaml:"ttl"`
TTL *Duration `yaml:"ttl"`
}
v := &str{}
var err error
@@ -31,14 +31,14 @@ func TestUnmarshalYAML(t *testing.T) {
err = yaml.Unmarshal([]byte(`{"ttl":"10ms"}`), v)
if err != nil {
t.Fatal(err)
} else if v.TTL != 10000000 {
} else if *(v.TTL) != 10000000 {
t.Fatalf("invalid duration %v != 10000000", v.TTL)
}
err = yaml.Unmarshal([]byte(`{"ttl":"1y"}`), v)
if err != nil {
t.Fatal(err)
} else if v.TTL != 31622400000000000 {
} else if *(v.TTL) != 31622400000000000 {
t.Fatalf("invalid duration %v != 31622400000000000", v.TTL)
}
}