2020-09-05 02:11:29 +03:00
|
|
|
package server
|
|
|
|
|
2020-10-16 09:38:57 +03:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"sync"
|
|
|
|
"time"
|
2020-09-05 02:11:29 +03:00
|
|
|
|
2020-11-23 16:18:47 +03:00
|
|
|
// cjson "github.com/unistack-org/micro-codec-json"
|
|
|
|
// cjsonrpc "github.com/unistack-org/micro-codec-jsonrpc"
|
|
|
|
// cproto "github.com/unistack-org/micro-codec-proto"
|
|
|
|
// cprotorpc "github.com/unistack-org/micro-codec-protorpc"
|
2020-10-16 09:38:57 +03:00
|
|
|
"github.com/unistack-org/micro/v3/broker"
|
|
|
|
"github.com/unistack-org/micro/v3/codec"
|
|
|
|
"github.com/unistack-org/micro/v3/logger"
|
2021-01-29 13:17:32 +03:00
|
|
|
"github.com/unistack-org/micro/v3/register"
|
2020-10-16 09:38:57 +03:00
|
|
|
)
|
2020-09-05 02:11:29 +03:00
|
|
|
|
2020-10-16 09:38:57 +03:00
|
|
|
var (
|
2020-12-08 00:38:37 +03:00
|
|
|
// DefaultCodecs will be used to encode/decode
|
2020-11-23 16:18:47 +03:00
|
|
|
DefaultCodecs = map[string]codec.Codec{
|
|
|
|
//"application/json": cjson.NewCodec,
|
|
|
|
//"application/json-rpc": cjsonrpc.NewCodec,
|
|
|
|
//"application/protobuf": cproto.NewCodec,
|
|
|
|
//"application/proto-rpc": cprotorpc.NewCodec,
|
|
|
|
"application/octet-stream": codec.NewCodec(),
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultContentType = "application/json"
|
|
|
|
)
|
2020-09-05 02:11:29 +03:00
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
type noopServer struct {
|
2020-10-16 09:38:57 +03:00
|
|
|
h Handler
|
|
|
|
opts Options
|
2021-01-29 13:17:32 +03:00
|
|
|
rsvc *register.Service
|
2020-10-16 09:38:57 +03:00
|
|
|
handlers map[string]Handler
|
|
|
|
subscribers map[*subscriber][]broker.Subscriber
|
|
|
|
registered bool
|
|
|
|
started bool
|
|
|
|
exit chan chan error
|
|
|
|
wg *sync.WaitGroup
|
|
|
|
sync.RWMutex
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
// NewServer returns new noop server
|
|
|
|
func NewServer(opts ...Option) Server {
|
|
|
|
return &noopServer{opts: NewOptions(opts...)}
|
|
|
|
}
|
|
|
|
|
2020-11-23 16:18:47 +03:00
|
|
|
func (n *noopServer) newCodec(contentType string) (codec.Codec, error) {
|
2020-10-16 09:38:57 +03:00
|
|
|
if cf, ok := n.opts.Codecs[contentType]; ok {
|
|
|
|
return cf, nil
|
|
|
|
}
|
|
|
|
if cf, ok := DefaultCodecs[contentType]; ok {
|
|
|
|
return cf, nil
|
|
|
|
}
|
2020-11-23 16:18:47 +03:00
|
|
|
return nil, codec.ErrUnknownContentType
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) Handle(handler Handler) error {
|
2020-10-16 09:38:57 +03:00
|
|
|
n.h = handler
|
|
|
|
return nil
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|
|
|
|
|
2021-01-29 13:17:32 +03:00
|
|
|
func (n *noopServer) Name() string {
|
|
|
|
return n.opts.Name
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) Subscribe(sb Subscriber) error {
|
2020-10-16 09:38:57 +03:00
|
|
|
sub, ok := sb.(*subscriber)
|
|
|
|
if !ok {
|
|
|
|
return fmt.Errorf("invalid subscriber: expected *subscriber")
|
|
|
|
}
|
|
|
|
if len(sub.handlers) == 0 {
|
|
|
|
return fmt.Errorf("invalid subscriber: no handler functions")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := ValidateSubscriber(sb); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
n.Lock()
|
|
|
|
if _, ok = n.subscribers[sub]; ok {
|
|
|
|
n.Unlock()
|
|
|
|
return fmt.Errorf("subscriber %v already exists", sub)
|
|
|
|
}
|
|
|
|
|
|
|
|
n.subscribers[sub] = nil
|
|
|
|
n.Unlock()
|
2020-09-05 02:11:29 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) NewHandler(h interface{}, opts ...HandlerOption) Handler {
|
2020-10-16 09:38:57 +03:00
|
|
|
return newRpcHandler(h, opts...)
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) NewSubscriber(topic string, sb interface{}, opts ...SubscriberOption) Subscriber {
|
2020-10-16 09:38:57 +03:00
|
|
|
return newSubscriber(topic, sb, opts...)
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) Init(opts ...Option) error {
|
2020-10-16 09:38:57 +03:00
|
|
|
for _, o := range opts {
|
|
|
|
o(&n.opts)
|
|
|
|
}
|
|
|
|
|
|
|
|
if n.handlers == nil {
|
|
|
|
n.handlers = make(map[string]Handler)
|
|
|
|
}
|
|
|
|
if n.subscribers == nil {
|
|
|
|
n.subscribers = make(map[*subscriber][]broker.Subscriber)
|
|
|
|
}
|
|
|
|
if n.exit == nil {
|
|
|
|
n.exit = make(chan chan error)
|
|
|
|
}
|
|
|
|
|
2020-09-05 02:11:29 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) Options() Options {
|
2020-09-05 02:11:29 +03:00
|
|
|
return n.opts
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) String() string {
|
2020-09-05 02:11:29 +03:00
|
|
|
return "noop"
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) Register() error {
|
2020-10-16 09:38:57 +03:00
|
|
|
n.RLock()
|
|
|
|
rsvc := n.rsvc
|
|
|
|
config := n.opts
|
|
|
|
n.RUnlock()
|
2020-09-05 02:11:29 +03:00
|
|
|
|
2020-10-16 09:38:57 +03:00
|
|
|
// if service already filled, reuse it and return early
|
|
|
|
if rsvc != nil {
|
|
|
|
if err := DefaultRegisterFunc(rsvc, config); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-09-05 02:11:29 +03:00
|
|
|
|
2020-10-16 09:38:57 +03:00
|
|
|
var err error
|
2021-01-29 13:17:32 +03:00
|
|
|
var service *register.Service
|
2020-10-16 09:38:57 +03:00
|
|
|
var cacheService bool
|
|
|
|
|
2021-01-29 13:17:32 +03:00
|
|
|
service, err = NewRegisterService(n)
|
2020-10-16 09:38:57 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|
|
|
|
|
2020-10-16 09:38:57 +03:00
|
|
|
n.RLock()
|
|
|
|
// Maps are ordered randomly, sort the keys for consistency
|
|
|
|
var handlerList []string
|
|
|
|
for n, e := range n.handlers {
|
|
|
|
// Only advertise non internal handlers
|
|
|
|
if !e.Options().Internal {
|
|
|
|
handlerList = append(handlerList, n)
|
|
|
|
}
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|
|
|
|
|
2020-10-16 09:38:57 +03:00
|
|
|
sort.Strings(handlerList)
|
|
|
|
|
|
|
|
var subscriberList []*subscriber
|
|
|
|
for e := range n.subscribers {
|
|
|
|
// Only advertise non internal subscribers
|
|
|
|
if !e.Options().Internal {
|
|
|
|
subscriberList = append(subscriberList, e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Slice(subscriberList, func(i, j int) bool {
|
|
|
|
return subscriberList[i].topic > subscriberList[j].topic
|
|
|
|
})
|
|
|
|
|
2021-01-29 13:17:32 +03:00
|
|
|
endpoints := make([]*register.Endpoint, 0, len(handlerList)+len(subscriberList))
|
2020-10-16 09:38:57 +03:00
|
|
|
for _, h := range handlerList {
|
|
|
|
endpoints = append(endpoints, n.handlers[h].Endpoints()...)
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|
2020-10-16 09:38:57 +03:00
|
|
|
for _, e := range subscriberList {
|
|
|
|
endpoints = append(endpoints, e.Endpoints()...)
|
|
|
|
}
|
|
|
|
n.RUnlock()
|
|
|
|
|
|
|
|
service.Nodes[0].Metadata["protocol"] = "noop"
|
|
|
|
service.Nodes[0].Metadata["transport"] = "noop"
|
|
|
|
service.Endpoints = endpoints
|
|
|
|
|
|
|
|
n.RLock()
|
|
|
|
registered := n.registered
|
|
|
|
n.RUnlock()
|
|
|
|
|
|
|
|
if !registered {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.InfoLevel) {
|
2021-01-29 13:17:32 +03:00
|
|
|
config.Logger.Infof(n.opts.Context, "register [%s] Registering node: %s", config.Register.String(), service.Nodes[0].Id)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// register the service
|
|
|
|
if err := DefaultRegisterFunc(service, config); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// already registered? don't need to register subscribers
|
|
|
|
if registered {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
n.Lock()
|
|
|
|
defer n.Unlock()
|
|
|
|
|
|
|
|
cx := config.Context
|
|
|
|
|
|
|
|
for sb := range n.subscribers {
|
|
|
|
handler := n.createSubHandler(sb, config)
|
|
|
|
var opts []broker.SubscribeOption
|
|
|
|
if queue := sb.Options().Queue; len(queue) > 0 {
|
|
|
|
opts = append(opts, broker.SubscribeGroup(queue))
|
|
|
|
}
|
|
|
|
|
|
|
|
if sb.Options().Context != nil {
|
|
|
|
cx = sb.Options().Context
|
|
|
|
}
|
|
|
|
|
|
|
|
opts = append(opts, broker.SubscribeContext(cx), broker.SubscribeAutoAck(sb.Options().AutoAck))
|
|
|
|
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.InfoLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Infof(n.opts.Context, "subscribing to topic: %s", sb.Topic())
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
sub, err := config.Broker.Subscribe(cx, sb.Topic(), handler, opts...)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
n.subscribers[sb] = []broker.Subscriber{sub}
|
|
|
|
}
|
|
|
|
|
|
|
|
n.registered = true
|
|
|
|
if cacheService {
|
|
|
|
n.rsvc = service
|
|
|
|
}
|
|
|
|
|
2020-09-05 02:11:29 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) Deregister() error {
|
2020-10-16 09:38:57 +03:00
|
|
|
var err error
|
|
|
|
|
|
|
|
n.RLock()
|
|
|
|
config := n.opts
|
|
|
|
n.RUnlock()
|
|
|
|
|
2021-01-29 13:17:32 +03:00
|
|
|
service, err := NewRegisterService(n)
|
2020-10-16 09:38:57 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.InfoLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Infof(n.opts.Context, "deregistering node: %s", service.Nodes[0].Id)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := DefaultDeregisterFunc(service, config); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
n.Lock()
|
|
|
|
n.rsvc = nil
|
|
|
|
|
|
|
|
if !n.registered {
|
|
|
|
n.Unlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
n.registered = false
|
|
|
|
|
|
|
|
cx := config.Context
|
|
|
|
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
for sb, subs := range n.subscribers {
|
|
|
|
for _, sub := range subs {
|
|
|
|
if sb.Options().Context != nil {
|
|
|
|
cx = sb.Options().Context
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Add(1)
|
|
|
|
go func(s broker.Subscriber) {
|
|
|
|
defer wg.Done()
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.InfoLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Infof(n.opts.Context, "unsubscribing from topic: %s", s.Topic())
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
if err := s.Unsubscribe(cx); err != nil {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "unsubscribing from topic: %s err: %v", s.Topic(), err)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}(sub)
|
|
|
|
}
|
|
|
|
n.subscribers[sb] = nil
|
|
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
n.Unlock()
|
2020-09-05 02:11:29 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) Start() error {
|
2020-10-16 09:38:57 +03:00
|
|
|
n.RLock()
|
|
|
|
if n.started {
|
|
|
|
n.RUnlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
config := n.Options()
|
|
|
|
n.RUnlock()
|
|
|
|
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.InfoLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Infof(n.opts.Context, "server [noop] Listening on %s", config.Address)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
n.Lock()
|
|
|
|
if len(config.Advertise) == 0 {
|
|
|
|
config.Advertise = config.Address
|
|
|
|
}
|
|
|
|
n.Unlock()
|
|
|
|
|
|
|
|
// only connect if we're subscribed
|
|
|
|
if len(n.subscribers) > 0 {
|
|
|
|
// connect to the broker
|
|
|
|
if err := config.Broker.Connect(config.Context); err != nil {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "broker [%s] connect error: %v", config.Broker.String(), err)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.InfoLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Infof(n.opts.Context, "broker [%s] Connected to %s", config.Broker.String(), config.Broker.Address())
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// use RegisterCheck func before register
|
|
|
|
if err := config.RegisterCheck(config.Context); err != nil {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.Id, err)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// announce self to the world
|
|
|
|
if err := n.Register(); err != nil {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "server register error: %v", err)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
t := new(time.Ticker)
|
|
|
|
|
|
|
|
// only process if it exists
|
|
|
|
if config.RegisterInterval > time.Duration(0) {
|
|
|
|
// new ticker
|
|
|
|
t = time.NewTicker(config.RegisterInterval)
|
|
|
|
}
|
|
|
|
|
|
|
|
// return error chan
|
|
|
|
var ch chan error
|
|
|
|
|
|
|
|
Loop:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
// register self on interval
|
|
|
|
case <-t.C:
|
|
|
|
n.RLock()
|
|
|
|
registered := n.registered
|
|
|
|
n.RUnlock()
|
|
|
|
rerr := config.RegisterCheck(config.Context)
|
|
|
|
if rerr != nil && registered {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s, deregister it", config.Name, config.Id, rerr)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
// deregister self in case of error
|
|
|
|
if err := n.Deregister(); err != nil {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "server %s-%s deregister error: %s", config.Name, config.Id, err)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if rerr != nil && !registered {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "server %s-%s register check error: %s", config.Name, config.Id, rerr)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if err := n.Register(); err != nil {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "server %s-%s register error: %s", config.Name, config.Id, err)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// wait for exit
|
|
|
|
case ch = <-n.exit:
|
|
|
|
break Loop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// deregister self
|
|
|
|
if err := n.Deregister(); err != nil {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "server deregister error: ", err)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// wait for waitgroup
|
|
|
|
if n.wg != nil {
|
|
|
|
n.wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// close transport
|
|
|
|
ch <- nil
|
|
|
|
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.InfoLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Infof(n.opts.Context, "broker [%s] Disconnected from %s", config.Broker.String(), config.Broker.Address())
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
// disconnect broker
|
|
|
|
if err := config.Broker.Disconnect(config.Context); err != nil {
|
2020-11-04 00:38:12 +03:00
|
|
|
if config.Logger.V(logger.ErrorLevel) {
|
2021-01-10 19:24:03 +03:00
|
|
|
config.Logger.Errorf(n.opts.Context, "broker [%s] disconnect error: %v", config.Broker.String(), err)
|
2020-10-16 09:38:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// mark the server as started
|
|
|
|
n.Lock()
|
|
|
|
n.started = true
|
|
|
|
n.Unlock()
|
|
|
|
|
2020-09-05 02:11:29 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-03 01:08:23 +03:00
|
|
|
func (n *noopServer) Stop() error {
|
2020-10-16 09:38:57 +03:00
|
|
|
n.RLock()
|
|
|
|
if !n.started {
|
|
|
|
n.RUnlock()
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
n.RUnlock()
|
|
|
|
|
|
|
|
ch := make(chan error)
|
|
|
|
n.exit <- ch
|
|
|
|
|
|
|
|
err := <-ch
|
|
|
|
n.Lock()
|
|
|
|
n.rsvc = nil
|
|
|
|
n.started = false
|
|
|
|
n.Unlock()
|
|
|
|
|
|
|
|
return err
|
2020-09-05 02:11:29 +03:00
|
|
|
}
|