Skip to content

Commit ab37da8

Browse files
committed
impr(locks): don't return cancelation errors
1 parent 9a7362a commit ab37da8

2 files changed

Lines changed: 71 additions & 26 deletions

File tree

pkg/lock/concurrency.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ import (
1111
// Locks should be short-lived and recover from previous states without the need
1212
// to persist them in memory (for fault-tolerancy and scalability).
1313
// This imply the context should be passed to the constructor rather than methods.
14+
//
15+
// Context errors (i.e. canceled or deadline reached) are not returned, only errors
16+
// from the downstream service.
17+
// Correct use of this module is to check yourself ctx.Err() whenever needing to.
1418
type RWLock interface {
1519
Key() string
1620

pkg/lock/etcd.go

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -70,24 +70,36 @@ func (lock *EtcdRWLock) RLock(ctx context.Context) error {
7070
ctxNc := context.WithoutCancel(ctx)
7171

7272
if err := lock.m3.Lock(ctx); err != nil {
73-
return err // could be context.Canceled
73+
if err == context.Canceled {
74+
return nil // Never went out of the equilibrium state
75+
}
76+
return err
7477
}
7578
defer unlock(ctxNc, lock.m3)
7679

7780
if err := lock.r.Lock(ctx); err != nil {
78-
return err // could be context.Canceled
81+
if err == context.Canceled {
82+
return nil // Equilibrium state is reached by previous defered operations
83+
}
84+
return err
7985
}
8086
defer unlock(ctxNc, lock.r)
8187

8288
if err := lock.m1.Lock(ctx); err != nil {
83-
return err // could be context.Canceled
89+
if err == context.Canceled {
90+
return nil // Equilibrium state is reached by previous defered operations
91+
}
92+
return err
8493
}
8594
defer unlock(ctxNc, lock.m1)
8695

8796
k := fmt.Sprintf("/chall-manager/%s/readCounter", lock.key)
8897
res, err := etcdCli.Get(ctx, k)
8998
if err != nil {
90-
return err // could be context.Canceled
99+
if err == context.Canceled {
100+
return nil // Equilibrium state is reached by previous defered operations
101+
}
102+
return err
91103
}
92104
var readCounter int
93105
switch len(res.Kvs) {
@@ -105,14 +117,20 @@ func (lock *EtcdRWLock) RLock(ctx context.Context) error {
105117
readCounter++
106118
_, err = etcdCli.Put(ctx, k, strconv.Itoa(readCounter))
107119
if err != nil {
108-
// Commited no value to etcd so it's fine.
109-
// Defered functions will reach the equilibrium state
120+
// Committed no value to etcd so it's fine.
121+
if err == context.Canceled {
122+
return nil // Equilibrium state is reached by previous defered operations
123+
}
110124
return err
111125
}
112126

127+
// From now on, we cannot go back and need to finish the job, else way deadlock
128+
113129
if readCounter == 1 {
114-
// Now that we wrote the readcounter, we can't skip the lock else deadlock
115130
if err := lock.w.Lock(ctxNc); err != nil {
131+
if err == context.Canceled {
132+
return nil
133+
}
116134
return err
117135
}
118136
}
@@ -125,38 +143,49 @@ func (lock *EtcdRWLock) RUnlock(ctx context.Context) error {
125143
ctxNc := context.WithoutCancel(ctx)
126144

127145
if err := lock.m1.Lock(ctx); err != nil {
128-
return err // could be context.Canceled
146+
if err == context.Canceled {
147+
return nil // Never went out of the equilibrium state
148+
}
149+
return err
129150
}
130151
defer unlock(ctxNc, lock.m1)
131152

132153
k := fmt.Sprintf("/chall-manager/%s/readCounter", lock.key)
133154
res, err := etcdCli.Get(ctx, k)
134155
if err != nil {
135-
return err // could be context.Canceled
156+
if err == context.Canceled {
157+
return nil // Equilibrium state is reached by previous defered operations
158+
}
159+
return err
136160
}
137161
var readCounter int
138162
switch len(res.Kvs) {
139163
case 1:
140164
str := string(res.Kvs[0].Value)
141165
readCounter, err = strconv.Atoi(str)
142166
if err != nil {
167+
// Equilibrium state is natievly reached by previous defered operations
143168
return errors.New("invalid format for " + k + ", got " + str)
144169
}
145170
default:
171+
// Equilibrium state is natievly reached by previous defered operations
146172
return errors.New("invalid etcd filter for " + k)
147173
}
148174
readCounter--
149175
_, err = etcdCli.Put(ctx, k, strconv.Itoa(readCounter))
150176
if err != nil {
151-
// Commited no value to etcd so it's fine.
177+
// Committed no value to etcd so it's fine.
152178
// Defered functions will reach the equilibrium state
179+
if err == context.Canceled {
180+
return nil
181+
}
153182
return err
154183
}
155184

156185
if readCounter == 0 {
157186
// Now that we wrote the readcounter, we can't skip the unlock else deadlock
158187
if err := lock.w.Unlock(ctxNc); err != nil {
159-
return err
188+
return err // can't be a context.Canceled as context is uncancelable
160189
}
161190
}
162191

@@ -168,14 +197,18 @@ func (lock *EtcdRWLock) RWLock(ctx context.Context) error {
168197
ctxNc := context.WithoutCancel(ctx)
169198

170199
if err := lock.m2.Lock(ctx); err != nil {
171-
return err // could be context.Canceled
200+
if err == context.Canceled {
201+
return nil // Never went out of the equilibrium state
202+
}
203+
return err
172204
}
173205

174206
k := fmt.Sprintf("/chall-manager/%s/writeCounter", lock.key)
175207
res, err := etcdCli.Get(ctx, k)
176208
if err != nil {
209+
// Manually reach equilibrium state
177210
if err == context.Canceled {
178-
return lock.m2.Unlock(ctxNc) // stop there, request simply don't need to go further
211+
return lock.m2.Unlock(ctxNc)
179212
}
180213
return multierr.Combine(err, lock.m2.Unlock(ctxNc))
181214
}
@@ -189,23 +222,23 @@ func (lock *EtcdRWLock) RWLock(ctx context.Context) error {
189222
if err != nil {
190223
return multierr.Combine(
191224
errors.New("invalid format for "+k+", got "+str),
192-
lock.m2.Unlock(ctxNc),
225+
lock.m2.Unlock(ctxNc), // Manually reach equilibrium state
193226
)
194227
}
195228
default:
196229
return multierr.Combine(
197230
errors.New("invalid etcd filter for "+k),
198-
lock.m2.Unlock(ctxNc),
231+
lock.m2.Unlock(ctxNc), // Manually reach equilibrium state
199232
)
200233
}
201234
writeCounter++
202235
_, perr := etcdCli.Put(ctx, k, strconv.Itoa(writeCounter))
203236
if perr != nil {
204-
// Commited no value to etcd so it's fine.
237+
// Committed no value to etcd so it's fine.
205238
// Defered functions will reach the equilibrium state
206239
return multierr.Combine(
207240
err,
208-
lock.m2.Unlock(ctxNc),
241+
lock.m2.Unlock(ctxNc), // Manually reach equilibrium state
209242
)
210243
}
211244

@@ -214,8 +247,8 @@ func (lock *EtcdRWLock) RWLock(ctx context.Context) error {
214247
if err := lock.r.Lock(ctxNc); err != nil {
215248
return multierr.Combine(
216249
err,
217-
lock.m2.Unlock(ctxNc),
218-
lock.w.Lock(ctxNc), // don't forget we need to lock W to avoid deadlock and keep the equilibrium state
250+
lock.m2.Unlock(ctxNc), // Manually reach equilibrium state
251+
lock.w.Lock(ctxNc), // And don't forget we need to lock W to avoid deadlock
219252
)
220253
}
221254
}
@@ -242,16 +275,20 @@ func (lock *EtcdRWLock) RWUnlock(ctx context.Context) error {
242275
// time to profit recoverability.
243276

244277
if err := lock.m2.Lock(ctx); err != nil {
245-
return err // could be context.Canceled
278+
if err == context.Canceled {
279+
return nil // Never went out of the equilibrium state
280+
}
281+
return err
246282
}
247283

248284
k := fmt.Sprintf("/chall-manager/%s/writeCounter", lock.key)
249285
res, err := etcdCli.Get(ctx, k)
250286
if err != nil {
251-
return multierr.Combine(
252-
err, // Could be context.Canceled
253-
lock.m2.Unlock(ctxNc),
254-
)
287+
// Manually reach equilibrium state
288+
if err == context.Canceled {
289+
return lock.m2.Unlock(ctxNc)
290+
}
291+
return multierr.Combine(err, lock.m2.Unlock(ctxNc))
255292
}
256293
var writeCounter int
257294
switch len(res.Kvs) {
@@ -261,19 +298,23 @@ func (lock *EtcdRWLock) RWUnlock(ctx context.Context) error {
261298
if err != nil {
262299
return multierr.Combine(
263300
errors.New("invalid format for "+k+", got "+str),
264-
lock.m2.Unlock(ctxNc),
301+
lock.m2.Unlock(ctxNc), // Manually reach equilibrium state
265302
)
266303
}
267304
default:
268305
return multierr.Combine(
269306
errors.New("invalid etcd filter for "+k),
270-
lock.m2.Unlock(ctxNc),
307+
lock.m2.Unlock(ctxNc), // Manually reach equilibrium state
271308
)
272309
}
273310
writeCounter--
274311
_, err = etcdCli.Put(ctx, k, strconv.Itoa(writeCounter))
275312
if err != nil {
276313
// Commited no value to etcd so it's fine.
314+
// Manually reach equilibrium state
315+
if err == context.Canceled {
316+
return lock.m2.Unlock(ctxNc)
317+
}
277318
return multierr.Combine(
278319
err,
279320
lock.m2.Unlock(ctxNc),

0 commit comments

Comments
 (0)