Skip to content

Commit a02f8d0

Browse files
authored
fix race condition when TTL=0 (#458)
1 parent 5967d58 commit a02f8d0

File tree

2 files changed

+34
-15
lines changed

2 files changed

+34
-15
lines changed

store/etcdv3/meta/etcd.go

+31-14
Original file line numberDiff line numberDiff line change
@@ -241,17 +241,21 @@ func (e *ETCD) BatchUpdate(ctx context.Context, data map[string]string, opts ...
241241

242242
// BindStatus keeps on a lease alive.
243243
func (e *ETCD) BindStatus(ctx context.Context, entityKey, statusKey, statusValue string, ttl int64) error {
244-
var leaseID clientv3.LeaseID
245-
updateStatus := []clientv3.Op{clientv3.OpPut(statusKey, statusValue)}
246-
if ttl != 0 {
247-
lease, err := e.Grant(ctx, ttl)
248-
if err != nil {
249-
return err
250-
}
251-
leaseID = lease.ID
252-
updateStatus = []clientv3.Op{clientv3.OpPut(statusKey, statusValue, clientv3.WithLease(lease.ID))}
244+
if ttl == 0 {
245+
return e.bindStatusWithoutTTL(ctx, statusKey, statusValue)
246+
}
247+
return e.bindStatusWithTTL(ctx, entityKey, statusKey, statusValue, ttl)
248+
}
249+
250+
func (e *ETCD) bindStatusWithTTL(ctx context.Context, entityKey, statusKey, statusValue string, ttl int64) error {
251+
lease, err := e.Grant(ctx, ttl)
252+
if err != nil {
253+
return err
253254
}
254255

256+
leaseID := lease.ID
257+
updateStatus := []clientv3.Op{clientv3.OpPut(statusKey, statusValue, clientv3.WithLease(lease.ID))}
258+
255259
entityTxn, err := e.cliv3.Txn(ctx).
256260
If(clientv3.Compare(clientv3.Version(entityKey), "!=", 0)).
257261
Then( // making sure there's an exists entity kv-pair.
@@ -282,11 +286,6 @@ func (e *ETCD) BindStatus(ctx context.Context, entityKey, statusKey, statusValue
282286
return nil
283287
}
284288

285-
// A zero TTL means it doesn't affect anything
286-
if ttl == 0 {
287-
return nil
288-
}
289-
290289
// There is a status bound to the entity yet but its value isn't same as the expected one.
291290
valueTxn := statusTxn.Responses[0].GetResponseTxn()
292291
if !valueTxn.Succeeded {
@@ -304,6 +303,24 @@ func (e *ETCD) BindStatus(ctx context.Context, entityKey, statusKey, statusValue
304303
return err
305304
}
306305

306+
// bindStatusWithoutTTL sets status without TTL.
307+
// When dealing with status of 0 TTL, we don't use lease,
308+
// also we don't check the existence of the entity key since
309+
// agent may report status earlier when core has not recorded the entity.
310+
func (e *ETCD) bindStatusWithoutTTL(ctx context.Context, statusKey, statusValue string) error {
311+
updateStatus := []clientv3.Op{clientv3.OpPut(statusKey, statusValue)}
312+
_, err := e.cliv3.Txn(ctx).
313+
If(clientv3.Compare(clientv3.Version(statusKey), "!=", 0)). // if there's an existing status key
314+
Then(clientv3.OpTxn( // deal with existing status key
315+
[]clientv3.Cmp{clientv3.Compare(clientv3.Value(statusKey), "!=", statusValue)}, // if the new value != the old value
316+
updateStatus, // then the status has been changed.
317+
[]clientv3.Op{}, // otherwise do nothing.
318+
)).
319+
Else(updateStatus...). // otherwise deal with non-existing status key
320+
Commit()
321+
return err
322+
}
323+
307324
func (e *ETCD) revokeLease(ctx context.Context, leaseID clientv3.LeaseID) {
308325
if leaseID == 0 {
309326
return

store/etcdv3/meta/etcd_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ func TestBindStatusWithZeroTTL(t *testing.T) {
150150
defer txn.AssertExpectations(t)
151151
txn.On("If", mock.Anything).Return(txn).Once()
152152
txn.On("Then", mock.Anything).Return(txn).Once()
153+
txn.On("Else", mock.Anything).Return(txn).Once()
153154
txn.On("Commit").Return(entityTxn, nil)
154155

155156
etcd.On("Txn", mock.Anything).Return(txn).Once()
@@ -189,7 +190,8 @@ func TestBindStatusButValueTxnUnsuccessful(t *testing.T) {
189190
txn.On("Commit").Return(entityTxn, nil)
190191

191192
etcd.On("Txn", mock.Anything).Return(txn).Once()
192-
require.Equal(t, nil, e.BindStatus(context.Background(), "/entity", "/status", "status", 0))
193+
etcd.On("Grant", mock.Anything, mock.Anything).Return(&clientv3.LeaseGrantResponse{}, nil).Once()
194+
require.Equal(t, nil, e.BindStatus(context.Background(), "/entity", "/status", "status", 1))
193195
}
194196

195197
func TestBindStatus(t *testing.T) {

0 commit comments

Comments
 (0)