move options to dedicated package
Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit is contained in:
63
util/ctx/ctx.go
Normal file
63
util/ctx/ctx.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package ctx
|
||||
|
||||
import "context"
|
||||
|
||||
// Mixin mixes a typically shorter-lived (service, etc.) context passed in
|
||||
// “shortctx” into a long-living context “longctx”, returning a derived “mixed”
|
||||
// context. The “mixed” context is derived from the long-living context and
|
||||
// additionally gets the deadline (if any) and cancellation of the mixed-in
|
||||
// “shortctx”.
|
||||
//
|
||||
// Please note that it is essential to always call the additionally returned
|
||||
// cancel function in order to not leak go routines. Calling this cancel
|
||||
// function won't cancel the long-living and short-lived contexts, just clean
|
||||
// up. This follows the established context pattern of [context.WithCancel],
|
||||
// [context.WithDeadline] and [context.WithTimeout].
|
||||
func Mixin(longctx, shortctx context.Context) (mixedctx context.Context, mixedcancel context.CancelFunc) {
|
||||
if longctx == nil {
|
||||
panic("wye.Mixin: cannot mix into nil context")
|
||||
}
|
||||
if shortctx == nil {
|
||||
panic("wye.Mixin: cannot mix-in nil context")
|
||||
}
|
||||
mixedctx = longctx
|
||||
shortDone := shortctx.Done()
|
||||
if shortDone == nil {
|
||||
// In case the shorter-living context isn't cancellable at all, then we
|
||||
// cannot cancel it and the cancel function returned must be a "no
|
||||
// operation". There's no need to mix in something, so we can pass on
|
||||
// the long-lived context.
|
||||
mixedcancel = func() {}
|
||||
return
|
||||
}
|
||||
// Nota bene: cancelled contexts "trickle down", so if a context higher up
|
||||
// the hierarchy was cancelled, this will automatically propagate down to
|
||||
// all child contexts, and so on.
|
||||
//
|
||||
// In case the shorter-lived context has a deadline, we need to carry it
|
||||
// over into the final mixed context.
|
||||
if deadline, ok := shortctx.Deadline(); ok {
|
||||
mixedctx, mixedcancel = context.WithDeadline(mixedctx, deadline)
|
||||
}
|
||||
// As the shorter-living context can be cancelled, we will need to supervise
|
||||
// it so we notice when it gets cancelled and then cancel the mixed context.
|
||||
mixedctx, mixedcancel = context.WithCancel(mixedctx)
|
||||
go func(ctx context.Context, cancel context.CancelFunc) {
|
||||
select {
|
||||
case <-shortDone:
|
||||
// In case the shorter-living context was cancelled (but it did
|
||||
// not pass a deadline) then we need to propagate this mixed
|
||||
// context. Please note this correctly won't cancel the original
|
||||
// longer context, because that's a long-living context we
|
||||
// shouldn't interfere with.
|
||||
if shortctx.Err() == context.Canceled {
|
||||
cancel()
|
||||
}
|
||||
case <-ctx.Done():
|
||||
// The final mixed context was either cancelled itself or its parent
|
||||
// deadline context met its fate; here, do not touch short-lived
|
||||
// context.
|
||||
}
|
||||
}(mixedctx, mixedcancel)
|
||||
return
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
// Package io is for io management
|
||||
package io // import "go.unistack.org/micro/v4/util/io"
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"go.unistack.org/micro/v4/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}
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v4/network/transport"
|
||||
"go.unistack.org/micro/v4/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
|
||||
}
|
@@ -1,92 +0,0 @@
|
||||
//go:build ignore
|
||||
// +build ignore
|
||||
|
||||
package pool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v4/network/transport"
|
||||
"go.unistack.org/micro/v4/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)
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
package pool
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v4/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
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
// Package pool is a connection pool
|
||||
package pool // import "go.unistack.org/micro/v4/util/pool"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.unistack.org/micro/v4/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)
|
||||
}
|
@@ -76,6 +76,9 @@ func ZeroFieldByPath(src interface{}, path string) error {
|
||||
val := reflect.ValueOf(src)
|
||||
|
||||
for _, p := range strings.Split(path, ".") {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
val, err = structValueByName(val, p)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -101,6 +104,9 @@ func SetFieldByPath(src interface{}, dst interface{}, path string) error {
|
||||
val := reflect.ValueOf(src)
|
||||
|
||||
for _, p := range strings.Split(path, ".") {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
val, err = structValueByName(val, p)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -111,7 +117,20 @@ func SetFieldByPath(src interface{}, dst interface{}, path string) error {
|
||||
return ErrInvalidStruct
|
||||
}
|
||||
|
||||
val.Set(reflect.ValueOf(dst))
|
||||
nv := reflect.ValueOf(dst)
|
||||
if val.Kind() != nv.Kind() {
|
||||
switch {
|
||||
default:
|
||||
val.Set(nv)
|
||||
case nv.Kind() == reflect.Slice:
|
||||
val.Set(nv.Index(0))
|
||||
case val.Kind() == reflect.Slice:
|
||||
val.Set(reflect.MakeSlice(val.Type(), 1, 1))
|
||||
val.Index(0).Set(nv)
|
||||
}
|
||||
} else {
|
||||
val.Set(nv)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -157,6 +176,9 @@ func structValueByName(sv reflect.Value, tkey string) (reflect.Value, error) {
|
||||
func StructFieldByPath(src interface{}, path string) (interface{}, error) {
|
||||
var err error
|
||||
for _, p := range strings.Split(path, ".") {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
src, err = StructFieldByName(src, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -62,6 +62,42 @@ func TestStructFieldsNested(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetFieldByPathMultiple(t *testing.T) {
|
||||
var err error
|
||||
tv := "test_val"
|
||||
type Str1 struct {
|
||||
Name []string `json:"name"`
|
||||
}
|
||||
val1 := &Str1{}
|
||||
|
||||
err = rutil.SetFieldByPath(val1, tv, ".Name")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(val1.Name) != 1 {
|
||||
t.Fatal("assign error")
|
||||
} else if val1.Name[0] != tv {
|
||||
t.Fatal("assign error")
|
||||
}
|
||||
|
||||
type Str2 struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
val2 := &Str2{}
|
||||
|
||||
err = rutil.SetFieldByPath(val2, []string{tv}, ".Name")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(val1.Name) != 1 {
|
||||
t.Fatal("assign error")
|
||||
} else if val1.Name[0] != tv {
|
||||
t.Fatal("assign error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetFieldByPath(t *testing.T) {
|
||||
type NestedStr struct {
|
||||
BBB string `json:"bbb"`
|
||||
|
@@ -1,65 +0,0 @@
|
||||
package socket
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Pool holds the socket pool
|
||||
type Pool struct {
|
||||
sync.RWMutex
|
||||
pool map[string]*Socket
|
||||
}
|
||||
|
||||
// Get socket from pool
|
||||
func (p *Pool) Get(id string) (*Socket, bool) {
|
||||
// attempt to get existing socket
|
||||
p.RLock()
|
||||
socket, ok := p.pool[id]
|
||||
if ok {
|
||||
p.RUnlock()
|
||||
return socket, ok
|
||||
}
|
||||
p.RUnlock()
|
||||
|
||||
// save socket
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
// double checked locking
|
||||
socket, ok = p.pool[id]
|
||||
if ok {
|
||||
return socket, ok
|
||||
}
|
||||
// create new socket
|
||||
socket = New(id)
|
||||
p.pool[id] = socket
|
||||
|
||||
// return socket
|
||||
return socket, false
|
||||
}
|
||||
|
||||
// Release close the socket and delete from pool
|
||||
func (p *Pool) Release(s *Socket) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
// close the socket
|
||||
s.Close()
|
||||
delete(p.pool, s.id)
|
||||
}
|
||||
|
||||
// Close the pool and delete all the sockets
|
||||
func (p *Pool) Close() {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
for id, sock := range p.pool {
|
||||
sock.Close()
|
||||
delete(p.pool, id)
|
||||
}
|
||||
}
|
||||
|
||||
// NewPool returns a new socket pool
|
||||
func NewPool() *Pool {
|
||||
return &Pool{
|
||||
pool: make(map[string]*Socket),
|
||||
}
|
||||
}
|
@@ -1,118 +0,0 @@
|
||||
// Package socket provides a pseudo socket
|
||||
package socket // import "go.unistack.org/micro/v4/util/socket"
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"go.unistack.org/micro/v4/network/transport"
|
||||
)
|
||||
|
||||
// Socket is our pseudo socket for transport.Socket
|
||||
type Socket struct {
|
||||
closed chan bool
|
||||
send chan *transport.Message
|
||||
recv chan *transport.Message
|
||||
id string
|
||||
remote string
|
||||
local string
|
||||
}
|
||||
|
||||
// SetLocal sets the local addr
|
||||
func (s *Socket) SetLocal(l string) {
|
||||
s.local = l
|
||||
}
|
||||
|
||||
// SetRemote sets the remote addr
|
||||
func (s *Socket) SetRemote(r string) {
|
||||
s.remote = r
|
||||
}
|
||||
|
||||
// Accept passes a message to the socket which will be processed by the call to Recv
|
||||
func (s *Socket) Accept(m *transport.Message) error {
|
||||
select {
|
||||
case s.recv <- m:
|
||||
return nil
|
||||
case <-s.closed:
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
// Process takes the next message off the send queue created by a call to Send
|
||||
func (s *Socket) Process(m *transport.Message) error {
|
||||
select {
|
||||
case msg := <-s.send:
|
||||
*m = *msg
|
||||
case <-s.closed:
|
||||
// see if we need to drain
|
||||
select {
|
||||
case msg := <-s.send:
|
||||
*m = *msg
|
||||
return nil
|
||||
default:
|
||||
return io.EOF
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remote returns remote addr
|
||||
func (s *Socket) Remote() string {
|
||||
return s.remote
|
||||
}
|
||||
|
||||
// Local returns local addr
|
||||
func (s *Socket) Local() string {
|
||||
return s.local
|
||||
}
|
||||
|
||||
// Send message by via transport
|
||||
func (s *Socket) Send(m *transport.Message) error {
|
||||
// send a message
|
||||
select {
|
||||
case s.send <- m:
|
||||
case <-s.closed:
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recv message from transport
|
||||
func (s *Socket) Recv(m *transport.Message) error {
|
||||
// receive a message
|
||||
select {
|
||||
case msg := <-s.recv:
|
||||
// set message
|
||||
*m = *msg
|
||||
case <-s.closed:
|
||||
return io.EOF
|
||||
}
|
||||
|
||||
// return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the socket
|
||||
func (s *Socket) Close() error {
|
||||
select {
|
||||
case <-s.closed:
|
||||
// no op
|
||||
default:
|
||||
close(s.closed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// New returns a new pseudo socket which can be used in the place of a transport socket.
|
||||
// Messages are sent to the socket via Accept and receives from the socket via Process.
|
||||
// SetLocal/SetRemote should be called before using the socket.
|
||||
func New(id string) *Socket {
|
||||
return &Socket{
|
||||
id: id,
|
||||
closed: make(chan bool),
|
||||
local: "local",
|
||||
remote: "remote",
|
||||
send: make(chan *transport.Message, 128),
|
||||
recv: make(chan *transport.Message, 128),
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user