github.com/annwntech/go-micro/v2@v2.9.5/sync/etcd/etcd.go (about)

     1  // Package etcd is an etcd implementation of lock
     2  package etcd
     3  
     4  import (
     5  	"context"
     6  	"errors"
     7  	"log"
     8  	"path"
     9  	"strings"
    10  	gosync "sync"
    11  
    12  	"github.com/annwntech/go-micro/v2/sync"
    13  	client "github.com/coreos/etcd/clientv3"
    14  	cc "github.com/coreos/etcd/clientv3/concurrency"
    15  )
    16  
    17  type etcdSync struct {
    18  	options sync.Options
    19  	path    string
    20  	client  *client.Client
    21  
    22  	mtx   gosync.Mutex
    23  	locks map[string]*etcdLock
    24  }
    25  
    26  type etcdLock struct {
    27  	s *cc.Session
    28  	m *cc.Mutex
    29  }
    30  
    31  type etcdLeader struct {
    32  	opts sync.LeaderOptions
    33  	s    *cc.Session
    34  	e    *cc.Election
    35  	id   string
    36  }
    37  
    38  func (e *etcdSync) Leader(id string, opts ...sync.LeaderOption) (sync.Leader, error) {
    39  	var options sync.LeaderOptions
    40  	for _, o := range opts {
    41  		o(&options)
    42  	}
    43  
    44  	// make path
    45  	path := path.Join(e.path, strings.ReplaceAll(e.options.Prefix+id, "/", "-"))
    46  
    47  	s, err := cc.NewSession(e.client)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	l := cc.NewElection(s, path)
    53  
    54  	if err := l.Campaign(context.TODO(), id); err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	return &etcdLeader{
    59  		opts: options,
    60  		e:    l,
    61  		id:   id,
    62  	}, nil
    63  }
    64  
    65  func (e *etcdLeader) Status() chan bool {
    66  	ch := make(chan bool, 1)
    67  	ech := e.e.Observe(context.Background())
    68  
    69  	go func() {
    70  		for r := range ech {
    71  			if string(r.Kvs[0].Value) != e.id {
    72  				ch <- true
    73  				close(ch)
    74  				return
    75  			}
    76  		}
    77  	}()
    78  
    79  	return ch
    80  }
    81  
    82  func (e *etcdLeader) Resign() error {
    83  	return e.e.Resign(context.Background())
    84  }
    85  
    86  func (e *etcdSync) Init(opts ...sync.Option) error {
    87  	for _, o := range opts {
    88  		o(&e.options)
    89  	}
    90  	return nil
    91  }
    92  
    93  func (e *etcdSync) Options() sync.Options {
    94  	return e.options
    95  }
    96  
    97  func (e *etcdSync) Lock(id string, opts ...sync.LockOption) error {
    98  	var options sync.LockOptions
    99  	for _, o := range opts {
   100  		o(&options)
   101  	}
   102  
   103  	// make path
   104  	path := path.Join(e.path, strings.ReplaceAll(e.options.Prefix+id, "/", "-"))
   105  
   106  	var sopts []cc.SessionOption
   107  	if options.TTL > 0 {
   108  		sopts = append(sopts, cc.WithTTL(int(options.TTL.Seconds())))
   109  	}
   110  
   111  	s, err := cc.NewSession(e.client, sopts...)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	m := cc.NewMutex(s, path)
   117  
   118  	if err := m.Lock(context.TODO()); err != nil {
   119  		return err
   120  	}
   121  
   122  	e.mtx.Lock()
   123  	e.locks[id] = &etcdLock{
   124  		s: s,
   125  		m: m,
   126  	}
   127  	e.mtx.Unlock()
   128  	return nil
   129  }
   130  
   131  func (e *etcdSync) Unlock(id string) error {
   132  	e.mtx.Lock()
   133  	defer e.mtx.Unlock()
   134  	v, ok := e.locks[id]
   135  	if !ok {
   136  		return errors.New("lock not found")
   137  	}
   138  	err := v.m.Unlock(context.Background())
   139  	delete(e.locks, id)
   140  	return err
   141  }
   142  
   143  func (e *etcdSync) String() string {
   144  	return "etcd"
   145  }
   146  
   147  func NewSync(opts ...sync.Option) sync.Sync {
   148  	var options sync.Options
   149  	for _, o := range opts {
   150  		o(&options)
   151  	}
   152  
   153  	var endpoints []string
   154  
   155  	for _, addr := range options.Nodes {
   156  		if len(addr) > 0 {
   157  			endpoints = append(endpoints, addr)
   158  		}
   159  	}
   160  
   161  	if len(endpoints) == 0 {
   162  		endpoints = []string{"http://127.0.0.1:2379"}
   163  	}
   164  
   165  	// TODO: parse addresses
   166  	c, err := client.New(client.Config{
   167  		Endpoints: endpoints,
   168  	})
   169  	if err != nil {
   170  		log.Fatal(err)
   171  	}
   172  
   173  	return &etcdSync{
   174  		path:    "/micro/sync",
   175  		client:  c,
   176  		options: options,
   177  		locks:   make(map[string]*etcdLock),
   178  	}
   179  }