package concurrency

import (
    "errors"
    "fmt"

    v3 "github.com/coreos/etcd/clientv3"
    "github.com/coreos/etcd/mvcc/mvccpb"
    "golang.org/x/net/context"
)

var (
    ErrElectionNotLeader = errors.New("election: not leader")
    ErrElectionNoLeader  = errors.New("election: no leader")
)

type Election struct {
    session *Session

    keyPrefix string

    leaderKey     string
    leaderRev     int64
    leaderSession *Session
}

// NewElection returns a new election on a given key prefix.
func NewElection(s *Session, pfx string) *Election {
    return &Election{session: s, keyPrefix: pfx + "/"}
}

// Campaign puts a value as eligible for the election. It blocks until
// it is elected, an error occurs, or the context is cancelled.
func (e *Election) Campaign(ctx context.Context, val string) error {
    s := e.session
    client := e.session.Client()

    k := fmt.Sprintf("%s%x", e.keyPrefix, s.Lease())
    txn := client.Txn(ctx).If(v3.Compare(v3.CreateRevision(k), "=", 0))
    txn = txn.Then(v3.OpPut(k, val, v3.WithLease(s.Lease())))
    txn = txn.Else(v3.OpGet(k))
    resp, err := txn.Commit()
    if err != nil {
        return err
    }
    e.leaderKey, e.leaderRev, e.leaderSession = k, resp.Header.Revision, s
    if !resp.Succeeded {
        kv := resp.Responses[0].GetResponseRange().Kvs[0]
        e.leaderRev = kv.CreateRevision
        if string(kv.Value) != val {
            if err = e.Proclaim(ctx, val); err != nil {
                e.Resign(ctx)
                return err
            }
        }
    }

    err = waitDeletes(ctx, client, e.keyPrefix, e.leaderRev-1)
    if err != nil {
        // clean up in case of context cancel
        select {
        case <-ctx.Done():
            e.Resign(client.Ctx())
        default:
            e.leaderSession = nil
        }
        return err
    }

    return nil
}

// Proclaim lets the leader announce a new value without another election.
func (e *Election) Proclaim(ctx context.Context, val string) error {
    if e.leaderSession == nil {
        return ErrElectionNotLeader
    }
    client := e.session.Client()
    cmp := v3.Compare(v3.CreateRevision(e.leaderKey), "=", e.leaderRev)
    txn := client.Txn(ctx).If(cmp)
    txn = txn.Then(v3.OpPut(e.leaderKey, val, v3.WithLease(e.leaderSession.Lease())))
    tresp, terr := txn.Commit()
    if terr != nil {
        return terr
    }
    if !tresp.Succeeded {
        e.leaderKey = ""
        return ErrElectionNotLeader
    }
    return nil
}

// Resign lets a leader start a new election.
func (e *Election) Resign(ctx context.Context) (err error) {
    if e.leaderSession == nil {
        return nil
    }
    client := e.session.Client()
    _, err = client.Delete(ctx, e.leaderKey)
    e.leaderKey = ""
    e.leaderSession = nil
    return err
}

// Leader returns the leader value for the current election.
func (e *Election) Leader(ctx context.Context) (string, error) {
    client := e.session.Client()
    resp, err := client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...)
    if err != nil {
        return "", err
    } else if len(resp.Kvs) == 0 {
        // no leader currently elected
        return "", ErrElectionNoLeader
    }
    return string(resp.Kvs[0].Value), nil
}

// Observe returns a channel that observes all leader proposal values as
// GetResponse values on the current leader key. The channel closes when
// the context is cancelled or the underlying watcher is otherwise disrupted.
func (e *Election) Observe(ctx context.Context) <-chan v3.GetResponse {
    retc := make(chan v3.GetResponse)
    go e.observe(ctx, retc)
    return retc
}

func (e *Election) observe(ctx context.Context, ch chan<- v3.GetResponse) {
    client := e.session.Client()

    defer close(ch)
    for {
        resp, err := client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...)
        if err != nil {
            return
        }

        var kv *mvccpb.KeyValue

        cctx, cancel := context.WithCancel(ctx)
        if len(resp.Kvs) == 0 {
            // wait for first key put on prefix
            opts := []v3.OpOption{v3.WithRev(resp.Header.Revision), v3.WithPrefix()}
            wch := client.Watch(cctx, e.keyPrefix, opts...)

            for kv == nil {
                wr, ok := <-wch
                if !ok || wr.Err() != nil {
                    cancel()
                    return
                }
                // only accept PUTs; a DELETE will make observe() spin
                for _, ev := range wr.Events {
                    if ev.Type == mvccpb.PUT {
                        kv = ev.Kv
                        break
                    }
                }
            }
        } else {
            kv = resp.Kvs[0]
        }

        wch := client.Watch(cctx, string(kv.Key), v3.WithRev(kv.ModRevision))
        keyDeleted := false
        for !keyDeleted {
            wr, ok := <-wch
            if !ok {
                return
            }
            for _, ev := range wr.Events {
                if ev.Type == mvccpb.DELETE {
                    keyDeleted = true
                    break
                }
                resp.Header = &wr.Header
                resp.Kvs = []*mvccpb.KeyValue{ev.Kv}
                select {
                case ch <- *resp:
                case <-cctx.Done():
                    return
                }
            }
        }
        cancel()
    }
}

// Key returns the leader key if elected, empty string otherwise.
func (e *Election) Key() string { return e.leaderKey }

election.go的更多相关文章

  1. [译]ZOOKEEPER RECIPES-Leader Election

    选主 使用ZooKeeper选主的一个简单方法是,在创建znode时使用Sequence和Ephemeral标志.主要思想是,使用一个znode,比如"/election",每个客 ...

  2. hihoCoder 1426 : What a Ridiculous Election(总统夶选)

    hihoCoder #1426 : What a Ridiculous Election(总统夶选) 时间限制:1000ms 单点时限:1000ms 内存限制:256MB Description - ...

  3. berkeley db replica机制 - election algorithm

    repmgr_method.c, __repmgr_start_int() 初始2个elect线程. repmgr_elect.c, __repmgr_init_election() __repmgr ...

  4. Election Time

    Election Time Time Limit: 1000MS Memory limit: 65536K 题目描述 The cows are having their first election ...

  5. zookeeper 集群 Cannot open channel to X at election address Error contacting service. It is probably not running.

    zookeeper集群   启动 1.问题现象. 启动每一个都提示  STARTED 但是查看 status时全部节点都报错 [root@ip-172-31-19-246 bin]# sh zkSer ...

  6. bzoj1675 [Usaco2005 Feb]Rigging the Bovine Election 竞选划区

    Description It's election time. The farm is partitioned into a 5x5 grid of cow locations, each of wh ...

  7. 1675: [Usaco2005 Feb]Rigging the Bovine Election 竞选划区(题解第二弹)

    1675: [Usaco2005 Feb]Rigging the Bovine Election 竞选划区 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit:  ...

  8. 1675: [Usaco2005 Feb]Rigging the Bovine Election 竞选划区(题解第一弹)

    1675: [Usaco2005 Feb]Rigging the Bovine Election 竞选划区 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit:  ...

  9. A Bayesian election prediction, implemented with R and Stan

    If the media coverage is anything to go by, people are desperate to know who will win the US electio ...

  10. bzoj:1675 [Usaco2005 Feb]Rigging the Bovine Election 竞选划区

    Description It's election time. The farm is partitioned into a 5x5 grid of cow locations, each of wh ...

随机推荐

  1. 百度站长平台MIP

    使用说明 MIP(Mobile Instant Pages - 移动网页加速器),是一套应用于移动网页的开放性技术标准.通过提供 MIP-HTML 规范.MIP-JS 运行环境以及 MIP-Cache ...

  2. Java + Selenium + TestNG + Maven

    环境准备: 1. Java: Install Java jdk: Version: java 1.8 or aboveConfigure Java Environment Variables:Add ...

  3. cookie的增删改查函数

    function setCookie(name,value,expires,path,domain){ //设置过期时间 var oDate = new Date(); oDate.setDate(o ...

  4. 如何写jquery插件

      首页    新文章  联系  管理  订阅  自己写一个 jQuery 插件 我知道这一天终将会到来,现在,它来了. 需求 开发 SharePoint 的 CSOM 应用时,经常需要在网页上输出一 ...

  5. python---数据类型---列表

    #列表: name = ["lc","pxm","pt"] print('------------',name[2],"----- ...

  6. use ECharts with Angular 2 and TypeScript

    https://stackoverflow.com/questions/38158318/is-it-possible-to-use-echarts-baidu-with-angular-2-and- ...

  7. Colossus: Successor to the Google File System (GFS)

    Colossus is the successor to the Google File System (GFS) as mentioned in the recent paper on Spanne ...

  8. GPU Accelerated Computing with Python

    https://developer.nvidia.com/how-to-cuda-Python python is one of the fastest growing and most popula ...

  9. 中国的UED们

    UED网址导航:http://www.ux265.com/ 天猫UED:http://ued.tmall.com/ 一淘UED:http://ux.etao.com/ 淘宝UED:http://ued ...

  10. DevOps之三 Git的安装与配置

    Centos7 安装Git 一.卸载Centos7 自带的git # git --version git version 1.8.3.1# whereis gitgit: /usr/bin/git / ...