projecteru2/core

Etcd lease leak

jschwinger233 opened this issue · 4 comments

func (c *Calcium) doLock(ctx context.Context, name string, timeout time.Duration) (lock.DistributedLock, context.Context, error) {
lock, err := c.store.CreateLock(name, timeout)
if err != nil {
return nil, nil, errors.WithStack(err)
}
ctx, err = lock.Lock(ctx)
return lock, ctx, errors.WithStack(err)
}

这个函数可能会造成锁泄露.

通过 etcd lock 的源码可以知道, 在 store.CreateLock() 成功之后, 就已经创建了 lease 并且有 goroutine 不断 keepalive lease:

https://github.com/etcd-io/etcd/blob/bd4f8e2b6c6a0bdcd52f4593f68d9f2415ab5293/client/v3/concurrency/session.go#L38-L71

func NewSession(client *v3.Client, opts ...SessionOption) (*Session, error) {
	ops := &sessionOptions{ttl: defaultSessionTTL, ctx: client.Ctx()}
	for _, opt := range opts {
		opt(ops)
	}

	id := ops.leaseID
	if id == v3.NoLease {
		resp, err := client.Grant(ops.ctx, int64(ops.ttl))
		if err != nil {
			return nil, err
		}
		id = resp.ID
	}

	ctx, cancel := context.WithCancel(ops.ctx)
	keepAlive, err := client.KeepAlive(ctx, id)
	if err != nil || keepAlive == nil {
		cancel()
		return nil, err
	}

	donec := make(chan struct{})
	s := &Session{client: client, opts: ops, id: id, cancel: cancel, donec: donec}

	// keep the lease alive until client error or cancelled context
	go func() {
		defer close(donec)
		for range keepAlive {
			// eat messages until keep alive channel closes
		}
	}()

	return s, nil

所以在 doLock 函数里, 假如 CreateLock() 成功而 lock.Lock() 失败, 已经创建的 lease, keepalive goroutine 都没有被销毁, 而 return 出去之后的上层也没有做处理(也不应该让上层处理, 毕竟 return err), 从而造成 lease + goroutine 泄露.

等下, 有地方有问题, 我改改

我倒是觉得这个问题在于 lock 被 keepalive 这点上 = =
似乎也没有什么方法控制这个, 记得有个 Orphan 好像避免创建之后的 keepalive goroutine

擦 这个如果改掉似乎不会就出现锁泄露了, 因为 revoke lease 会删除 attached keys, 而且我测了一下居然没有 race

绕来绕去没想到答案早就在手上了...伤心