package clientv3

import (
    "sync"
    "time"

    "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
    pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

type (
    LeaseRevokeResponse pb.LeaseRevokeResponse
    LeaseID             int64
)

// LeaseGrantResponse is used to convert the protobuf grant response.
type LeaseGrantResponse struct {
    *pb.ResponseHeader
    ID    LeaseID
    TTL   int64
    Error string
}

// LeaseKeepAliveResponse is used to convert the protobuf keepalive response.
type LeaseKeepAliveResponse struct {
    *pb.ResponseHeader
    ID  LeaseID
    TTL int64
}

// LeaseTimeToLiveResponse is used to convert the protobuf lease timetolive response.
type LeaseTimeToLiveResponse struct {
    *pb.ResponseHeader
    ID LeaseID `json:"id"`

    // TTL is the remaining TTL in seconds for the lease; the lease will expire in under TTL+1 seconds.
    TTL int64 `json:"ttl"`

    // GrantedTTL is the initial granted time in seconds upon lease creation/renewal.
    GrantedTTL int64 `json:"granted-ttl"`

    // Keys is the list of keys attached to this lease.
    Keys [][]byte `json:"keys"`
}

const (
    // defaultTTL is the assumed lease TTL used for the first keepalive
    // deadline before the actual TTL is known to the client.
    defaultTTL = 5 * time.Second
    // a small buffer to store unsent lease responses.
    leaseResponseChSize = 16
    // NoLease is a lease ID for the absence of a lease.
    NoLease LeaseID = 0
)

type Lease interface {
    // Grant creates a new lease.
    Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error)

    // Revoke revokes the given lease.
    Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error)

    // TimeToLive retrieves the lease information of the given lease ID.
    TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error)

    // KeepAlive keeps the given lease alive forever.
    KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error)

    // KeepAliveOnce renews the lease once. In most of the cases, Keepalive
    // should be used instead of KeepAliveOnce.
    KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error)

    // Close releases all resources Lease keeps for efficient communication
    // with the etcd server.
    Close() error
}

type lessor struct {
    mu sync.Mutex // guards all fields

    // donec is closed when recvKeepAliveLoop stops
    donec chan struct{}

    remote pb.LeaseClient

    stream       pb.Lease_LeaseKeepAliveClient
    streamCancel context.CancelFunc

    stopCtx    context.Context
    stopCancel context.CancelFunc

    keepAlives map[LeaseID]*keepAlive

    // firstKeepAliveTimeout is the timeout for the first keepalive request
    // before the actual TTL is known to the lease client
    firstKeepAliveTimeout time.Duration
}

// keepAlive multiplexes a keepalive for a lease over multiple channels
type keepAlive struct {
    chs  []chan<- *LeaseKeepAliveResponse
    ctxs []context.Context
    // deadline is the time the keep alive channels close if no response
    deadline time.Time
    // nextKeepAlive is when to send the next keep alive message
    nextKeepAlive time.Time
    // donec is closed on lease revoke, expiration, or cancel.
    donec chan struct{}
}

func NewLease(c *Client) Lease {
    l := &lessor{
        donec:                 make(chan struct{}),
        keepAlives:            make(map[LeaseID]*keepAlive),
        remote:                RetryLeaseClient(c),
        firstKeepAliveTimeout: c.cfg.DialTimeout + time.Second,
    }
    if l.firstKeepAliveTimeout == time.Second {
        l.firstKeepAliveTimeout = defaultTTL
    }

    l.stopCtx, l.stopCancel = context.WithCancel(context.Background())
    go l.recvKeepAliveLoop()
    go l.deadlineLoop()
    return l
}

func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error) {
    cctx, cancel := context.WithCancel(ctx)
    done := cancelWhenStop(cancel, l.stopCtx.Done())
    defer close(done)

    for {
        r := &pb.LeaseGrantRequest{TTL: ttl}
        resp, err := l.remote.LeaseGrant(cctx, r)
        if err == nil {
            gresp := &LeaseGrantResponse{
                ResponseHeader: resp.GetHeader(),
                ID:             LeaseID(resp.ID),
                TTL:            resp.TTL,
                Error:          resp.Error,
            }
            return gresp, nil
        }
        if isHaltErr(cctx, err) {
            return nil, toErr(cctx, err)
        }
        if nerr := l.newStream(); nerr != nil {
            return nil, nerr
        }
    }
}

func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error) {
    cctx, cancel := context.WithCancel(ctx)
    done := cancelWhenStop(cancel, l.stopCtx.Done())
    defer close(done)

    for {
        r := &pb.LeaseRevokeRequest{ID: int64(id)}
        resp, err := l.remote.LeaseRevoke(cctx, r)

        if err == nil {
            return (*LeaseRevokeResponse)(resp), nil
        }
        if isHaltErr(ctx, err) {
            return nil, toErr(ctx, err)
        }
        if nerr := l.newStream(); nerr != nil {
            return nil, nerr
        }
    }
}

func (l *lessor) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) {
    cctx, cancel := context.WithCancel(ctx)
    done := cancelWhenStop(cancel, l.stopCtx.Done())
    defer close(done)

    for {
        r := toLeaseTimeToLiveRequest(id, opts...)
        resp, err := l.remote.LeaseTimeToLive(cctx, r, grpc.FailFast(false))
        if err == nil {
            gresp := &LeaseTimeToLiveResponse{
                ResponseHeader: resp.GetHeader(),
                ID:             LeaseID(resp.ID),
                TTL:            resp.TTL,
                GrantedTTL:     resp.GrantedTTL,
                Keys:           resp.Keys,
            }
            return gresp, nil
        }
        if isHaltErr(cctx, err) {
            return nil, toErr(cctx, err)
        }
    }
}

func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) {
    ch := make(chan *LeaseKeepAliveResponse, leaseResponseChSize)

    l.mu.Lock()
    ka, ok := l.keepAlives[id]
    if !ok {
        // create fresh keep alive
        ka = &keepAlive{
            chs:           []chan<- *LeaseKeepAliveResponse{ch},
            ctxs:          []context.Context{ctx},
            deadline:      time.Now().Add(l.firstKeepAliveTimeout),
            nextKeepAlive: time.Now(),
            donec:         make(chan struct{}),
        }
        l.keepAlives[id] = ka
    } else {
        // add channel and context to existing keep alive
        ka.ctxs = append(ka.ctxs, ctx)
        ka.chs = append(ka.chs, ch)
    }
    l.mu.Unlock()

    go l.keepAliveCtxCloser(id, ctx, ka.donec)

    return ch, nil
}

func (l *lessor) KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) {
    cctx, cancel := context.WithCancel(ctx)
    done := cancelWhenStop(cancel, l.stopCtx.Done())
    defer close(done)

    for {
        resp, err := l.keepAliveOnce(cctx, id)
        if err == nil {
            if resp.TTL == 0 {
                err = rpctypes.ErrLeaseNotFound
            }
            return resp, err
        }
        if isHaltErr(ctx, err) {
            return nil, toErr(ctx, err)
        }

        if nerr := l.newStream(); nerr != nil {
            return nil, nerr
        }
    }
}

func (l *lessor) Close() error {
    l.stopCancel()
    <-l.donec
    return nil
}

func (l *lessor) keepAliveCtxCloser(id LeaseID, ctx context.Context, donec <-chan struct{}) {
    select {
    case <-donec:
        return
    case <-l.donec:
        return
    case <-ctx.Done():
    }

    l.mu.Lock()
    defer l.mu.Unlock()

    ka, ok := l.keepAlives[id]
    if !ok {
        return
    }

    // close channel and remove context if still associated with keep alive
    for i, c := range ka.ctxs {
        if c == ctx {
            close(ka.chs[i])
            ka.ctxs = append(ka.ctxs[:i], ka.ctxs[i+1:]...)
            ka.chs = append(ka.chs[:i], ka.chs[i+1:]...)
            break
        }
    }
    // remove if no one more listeners
    if len(ka.chs) == 0 {
        delete(l.keepAlives, id)
    }
}

func (l *lessor) keepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) {
    cctx, cancel := context.WithCancel(ctx)
    defer cancel()

    stream, err := l.remote.LeaseKeepAlive(cctx, grpc.FailFast(false))
    if err != nil {
        return nil, toErr(ctx, err)
    }

    err = stream.Send(&pb.LeaseKeepAliveRequest{ID: int64(id)})
    if err != nil {
        return nil, toErr(ctx, err)
    }

    resp, rerr := stream.Recv()
    if rerr != nil {
        return nil, toErr(ctx, rerr)
    }

    karesp := &LeaseKeepAliveResponse{
        ResponseHeader: resp.GetHeader(),
        ID:             LeaseID(resp.ID),
        TTL:            resp.TTL,
    }
    return karesp, nil
}

func (l *lessor) recvKeepAliveLoop() {
    defer func() {
        l.mu.Lock()
        close(l.donec)
        for _, ka := range l.keepAlives {
            ka.Close()
        }
        l.keepAlives = make(map[LeaseID]*keepAlive)
        l.mu.Unlock()
    }()

    stream, serr := l.resetRecv()
    for serr == nil {
        resp, err := stream.Recv()
        if err != nil {
            if isHaltErr(l.stopCtx, err) {
                return
            }
            stream, serr = l.resetRecv()
            continue
        }
        l.recvKeepAlive(resp)
    }
}

// resetRecv opens a new lease stream and starts sending LeaseKeepAliveRequests
func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) {
    if err := l.newStream(); err != nil {
        return nil, err
    }
    stream := l.getKeepAliveStream()
    go l.sendKeepAliveLoop(stream)
    return stream, nil
}

// recvKeepAlive updates a lease based on its LeaseKeepAliveResponse
func (l *lessor) recvKeepAlive(resp *pb.LeaseKeepAliveResponse) {
    karesp := &LeaseKeepAliveResponse{
        ResponseHeader: resp.GetHeader(),
        ID:             LeaseID(resp.ID),
        TTL:            resp.TTL,
    }

    l.mu.Lock()
    defer l.mu.Unlock()

    ka, ok := l.keepAlives[karesp.ID]
    if !ok {
        return
    }

    if karesp.TTL <= 0 {
        // lease expired; close all keep alive channels
        delete(l.keepAlives, karesp.ID)
        ka.Close()
        return
    }

    // send update to all channels
    nextKeepAlive := time.Now().Add(1 + time.Duration(karesp.TTL/3)*time.Second)
    ka.deadline = time.Now().Add(time.Duration(karesp.TTL) * time.Second)
    for _, ch := range ka.chs {
        select {
        case ch <- karesp:
            ka.nextKeepAlive = nextKeepAlive
        default:
        }
    }
}

// deadlineLoop reaps any keep alive channels that have not received a response
// within the lease TTL
func (l *lessor) deadlineLoop() {
    for {
        select {
        case <-time.After(time.Second):
        case <-l.donec:
            return
        }
        now := time.Now()
        l.mu.Lock()
        for id, ka := range l.keepAlives {
            if ka.deadline.Before(now) {
                // waited too long for response; lease may be expired
                ka.Close()
                delete(l.keepAlives, id)
            }
        }
        l.mu.Unlock()
    }
}

// sendKeepAliveLoop sends LeaseKeepAliveRequests for the lifetime of a lease stream
func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {
    for {
        select {
        case <-time.After(500 * time.Millisecond):
        case <-stream.Context().Done():
            return
        case <-l.donec:
            return
        case <-l.stopCtx.Done():
            return
        }

        var tosend []LeaseID

        now := time.Now()
        l.mu.Lock()
        for id, ka := range l.keepAlives {
            if ka.nextKeepAlive.Before(now) {
                tosend = append(tosend, id)
            }
        }
        l.mu.Unlock()

        for _, id := range tosend {
            r := &pb.LeaseKeepAliveRequest{ID: int64(id)}
            if err := stream.Send(r); err != nil {
                // TODO do something with this error?
                return
            }
        }
    }
}

func (l *lessor) getKeepAliveStream() pb.Lease_LeaseKeepAliveClient {
    l.mu.Lock()
    defer l.mu.Unlock()
    return l.stream
}

func (l *lessor) newStream() error {
    sctx, cancel := context.WithCancel(l.stopCtx)
    stream, err := l.remote.LeaseKeepAlive(sctx, grpc.FailFast(false))
    if err != nil {
        cancel()
        return toErr(sctx, err)
    }

    l.mu.Lock()
    defer l.mu.Unlock()
    if l.stream != nil && l.streamCancel != nil {
        l.stream.CloseSend()
        l.streamCancel()
    }

    l.streamCancel = cancel
    l.stream = stream
    return nil
}

func (ka *keepAlive) Close() {
    close(ka.donec)
    for _, ch := range ka.chs {
        close(ch)
    }
}

// cancelWhenStop calls cancel when the given stopc fires. It returns a done chan. done
// should be closed when the work is finished. When done fires, cancelWhenStop will release
// its internal resource.
func cancelWhenStop(cancel context.CancelFunc, stopc <-chan struct{}) chan<- struct{} {
    done := make(chan struct{}, 1)

    go func() {
        select {
        case <-stopc:
        case <-done:
        }
        cancel()
    }()

    return done
}

lease.go的更多相关文章

  1. 基于Lease分布式系统重试服务选举

    /** * Copyright (c) 2015, www.cubbery.com. All rights reserved. */ package com.cubbery.event.retry; ...

  2. 分布式入门之1:Lease机制

      引子: 分布式系统中,如何确认一个节点是否工作正常?   如果有3副本A.B.C,并通过中心结点M来管理.其中A为主副本. 未接触过分布式的直观的处理方法是在每个副本与中心节点M中维护一个心跳,期 ...

  3. Azure 删除VHD时报错:There is currently a lease on the blob and no lease ID was specified in the request

    可下载:http://clumsyleaf.com/products/cloudxplorer 然后在Accounts中新建一个Account,账号与Key,可在相应的storage Manage A ...

  4. sudo -u hdfs hdfs balancer出现异常 No lease on /system/balancer.id

    16/06/02 20:34:05 INFO balancer.Balancer: namenodes = [hdfs://dlhtHadoop101:8022, hdfs://dlhtHadoop1 ...

  5. Hey,man,are you ok? -- 关于心跳、故障监测、lease机制

    电话之于短信.微信的一个很大的不同点在于,前者更加及时,有更快速直接的反馈:而后面两个虽然称之为instant message,但经常时发出去了就得等对方回复,等多久是不确定的.打电话能明确知道对方在 ...

  6. Lease问题

    经过查明原来是lease引发的问题.不过查问题的过程让我们耽误了很多修复故障的时间,很是不爽. 起因:datanode和regionserver以及master同时挂掉 现象:datanode重启后, ...

  7. 分析dhcp.lease文件,统计DHCP服务器IP自动分配

    #!/usr/bin/env python # coding=utf-8 import string import time,datetime class TIMEFORMAT: def __init ...

  8. 深入NAS协议系列: 召唤SMB2 OpLock/Lease

    这是从事存储行业十年以来我写的第一篇博客,希望借此开始把自己这些年所积累的一些干货借这个平台做分享. 虽然NAS协议众多,但核心的就那个几个:NFS,SMB/CIFS, FTP/SFTP, 其中SMB ...

  9. HDFS Lease Recovey 和 Block Recovery

    这篇分析一下Lease Recovery 和 Block Recovery hdfs支持hflush后,需要保证hflush的数据被读到,datanode重启不能简单的丢弃文件的最后一个block,而 ...

随机推荐

  1. rails应用ajax之三:进一步完善ajax动画特效果

    本猫已经对界面放低标准很久了,但是复习了ajax之后突然发现:哇!原来世界可以这么美,这么生动鲜活的!所以本篇主要讨论下如何用ajax在rails中做一些简单的动画效果. 其实最新版的的rails中使 ...

  2. C# /VB.NET 创建PDF项目符号列表和多级编号列表

    使用项目符号和编号,可以让文档的层次结构更清晰.更有条理,也更容易突出重点.在编辑文档的过程中,我个人也比较偏爱项目标号来标注文章重点信息.在之前的文章中,介绍了如何在Word中来创建项目标号和编号列 ...

  3. android + php 后台开发

    android+php 安卓与服务器的数据交互 在我们进行android开发的时候,避免不了的要进行登录注册,个人信息获取,数据交互等等这一系列的操作.这样就需要进行android端与服务器端进行数据 ...

  4. Flask-email 发送邮件的配置,发送附件的方法,以及os.environ.get('MAIL_USERNAME')为None的解决办法

    一.发送邮件的配置 在学习flask-mail来发送电子邮件的时候遇到了一些问题,其实都是些小问题,现在记录下来以便于以后查看. 1.首先flask-mail的安装 pip install flask ...

  5. JS基础速成(三)- DOM(文件对象模型)

    .t1 { background-color: #ff8080; width: 1100px; height: 40px } 一.DOM树的基本结构 DOM节点分为三大类:元素节点(标签节点),属性节 ...

  6. 《C++标准程序库》学习笔记(一)C++相关特性

    抱着本厚厚的<C++标准库>读了几天,想想也该写点关于用法的总结,一来怕今后容易忘记,二来将书上的事例重新敲一遍,巩固对程序库相关知识的了解.今天开第一篇,以后不固定更新.当然,笔者所读为 ...

  7. Unknown entity: org.jbpm.services.task.audit.TaskEventImpl

    1. use this persistence.xml - simply copy it into src/main/resources/META-INF please note the name o ...

  8. 【转载】Session的生命周期

    http://www.cnblogs.com/binger/archive/2013/03/19/2970171.html 以前在学习的时候没怎么注意,今天又回过头来仔细研究研究了一下Session的 ...

  9. Python-网站页面代码获取

    Python3.6 库:urllib3, bs4 主程序是抓取亚马逊图书销售排名数据,但是亚马逊应该是加了反爬虫,拒绝疑似机器人的请求,这部分暂时以百度代替. 其实简单的页面抓取,常用的urllib. ...

  10. How 5 Natural Language Processing APIs Stack Up

    https://www.programmableweb.com/news/how-5-natural-language-processing-apis-stack/analysis/2014/07/2 ...