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. 恶补web之五:dhtml学习

    dhtml是一种使html页面具有动态特性的艺术.对于多数人来说dhtml意味着html(html DOM),样式表和javascript的组合. dhtml不是w3c标准.dhtml指动态html, ...

  2. 关于iOS9 HTTP不能正常使用的解决方法

    在工程的info.plist文件中添加NSAPPTransportSecurity类型为Dictionary,在NSAPPTransportSecurity下添加NSAllowsArbitraryLo ...

  3. webservice入门简介

    为了梦想,努力奋斗! 追求卓越,成功就会在不经意间追上你 webservice入门简介 1.什么是webservice? webservice是一种跨编程语言和跨操作系统平台的远程调用技术. 所谓的远 ...

  4. 浅谈这个时代的SEO与网络营销

    1.大网站对分享内容的审核越来越严,高质量借道外链越来越难做. 2.大搜索引擎入口的权威性将会不断受各种方面的的削弱:比如自媒体.垂直服务网站等 3.旧路还没有短,但是新路要积极挖掘. 这也说的太少了 ...

  5. erlang的脚本执行---escript

    1.概述: 作为程序员对于脚本语言应该很熟悉了,脚本语言的优点很多,如快速开发.容易编写.实时开发和执行, 我们常用的脚本有Javascript.shell.python等,我们的erlang语言也有 ...

  6. 【数据可视化之Flask】快速设计和部署Flask网站

    Flask是Python应用于WEB开发的第三方开源框架,以设计简单高效著称.我也尝试过Django,相对于Flask显得更加全面同样也更加笨重,并且我也不需要它的后台管理功能,因此选择了Flask作 ...

  7. Oracle解锁表笔记

    1.查询被锁的对象: select object_name,machine,s.sid,s.serial# from v$locked_object l,dba_objects o ,v$sessio ...

  8. 万网主机使用wordpress发送邮件的方法

    今天弄了一下午总算明白了,这里写一下具体过程. 首先是邮箱,万网主机是不支持mail()函数的,所以默认的不可用,如果你想发送邮件的话,只能使用fsockopen()函数.首先进入万网主机管理平台,启 ...

  9. 学生管理系统_排序后通过name删除列表里的字典

    l = [{'name': 'wangfan', 'age': 18, 'sex': 'nan'}, {'name': 'wangerfan', 'age': 10, 'sex': 'nan'}, { ...

  10. python的约束库constraint解决《2018刑侦科题目》

    Github地址:https://github.com/ZJW9633/hello-word/blob/master/Xingzhenke 题目分析: 10道题互相关联,耦合性强,暴力求解需4^10种 ...