Skip to content

Commit 0075794

Browse files
committed
webdav: implement memLS.Confirm.
LockSystem.Confirm now takes two names (a src and a dst), and returns a func() instead of a 1-method Releaser. We now pass 25 of 30 "locks" litmus tests: - 4 failures are due to PROPFIND / PROPPATCH not being implemented. - 1 failure is due to shared locks being unsupported. - 3 warnings are also due to PROPFIND / PROPPATCH. - 1 failure (cond_put_corrupt_token) is due to returning 412 (Precondition Failed) instead of 423 (Locked), but IIUC the spec says to return 412. - 11 tests were skipped, presumably due to earlier failures. Change-Id: I3f4c178cdc4b99c6acb7f59783b4fd9b94f606ec Reviewed-on: https://go-review.googlesource.com/3860 Reviewed-by: Dave Cheney <[email protected]>
1 parent 5b4754d commit 0075794

File tree

3 files changed

+357
-55
lines changed

3 files changed

+357
-55
lines changed

webdav/lock.go

+87-17
Original file line numberDiff line numberDiff line change
@@ -32,31 +32,26 @@ type Condition struct {
3232
ETag string
3333
}
3434

35-
// Releaser releases previously confirmed lock claims.
36-
//
37-
// Calling Release does not unlock the lock, in the WebDAV UNLOCK sense, but
38-
// once LockSystem.Confirm has confirmed that a lock claim is valid, that lock
39-
// cannot be Confirmed again until it has been Released.
40-
type Releaser interface {
41-
Release()
42-
}
43-
4435
// LockSystem manages access to a collection of named resources. The elements
4536
// in a lock name are separated by slash ('/', U+002F) characters, regardless
4637
// of host operating system convention.
4738
type LockSystem interface {
4839
// Confirm confirms that the caller can claim all of the locks specified by
4940
// the given conditions, and that holding the union of all of those locks
50-
// gives exclusive access to the named resource.
41+
// gives exclusive access to all of the named resources. Up to two resources
42+
// can be named. Empty names are ignored.
5143
//
52-
// Exactly one of r and err will be non-nil. If r is non-nil, all of the
53-
// requested locks are held until r.Release is called.
44+
// Exactly one of release and err will be non-nil. If release is non-nil,
45+
// all of the requested locks are held until release is called. Calling
46+
// release does not unlock the lock, in the WebDAV UNLOCK sense, but once
47+
// Confirm has confirmed that a lock claim is valid, that lock cannot be
48+
// Confirmed again until it has been released.
5449
//
5550
// If Confirm returns ErrConfirmationFailed then the Handler will continue
5651
// to try any other set of locks presented (a WebDAV HTTP request can
5752
// present more than one set of locks). If it returns any other non-nil
5853
// error, the Handler will write a "500 Internal Server Error" HTTP status.
59-
Confirm(now time.Time, name string, conditions ...Condition) (r Releaser, err error)
54+
Confirm(now time.Time, name0, name1 string, conditions ...Condition) (release func(), err error)
6055

6156
// Create creates a lock with the given depth, duration, owner and root
6257
// (name). The depth will either be negative (meaning infinite) or zero.
@@ -149,14 +144,89 @@ func (m *memLS) collectExpiredNodes(now time.Time) {
149144
}
150145
}
151146

152-
func (m *memLS) Confirm(now time.Time, name string, conditions ...Condition) (Releaser, error) {
147+
func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) {
153148
m.mu.Lock()
154149
defer m.mu.Unlock()
155150
m.collectExpiredNodes(now)
156-
name = slashClean(name)
157151

158-
// TODO: touch n.held.
159-
panic("TODO")
152+
var n0, n1 *memLSNode
153+
if name0 != "" {
154+
if n0 = m.lookup(slashClean(name0), conditions...); n0 == nil {
155+
return nil, ErrConfirmationFailed
156+
}
157+
}
158+
if name1 != "" {
159+
if n1 = m.lookup(slashClean(name1), conditions...); n1 == nil {
160+
return nil, ErrConfirmationFailed
161+
}
162+
}
163+
164+
// Don't hold the same node twice.
165+
if n1 == n0 {
166+
n1 = nil
167+
}
168+
169+
if n0 != nil {
170+
m.hold(n0)
171+
}
172+
if n1 != nil {
173+
m.hold(n1)
174+
}
175+
return func() {
176+
m.mu.Lock()
177+
defer m.mu.Unlock()
178+
if n1 != nil {
179+
m.unhold(n1)
180+
}
181+
if n0 != nil {
182+
m.unhold(n0)
183+
}
184+
}, nil
185+
}
186+
187+
// lookup returns the node n that locks the named resource, provided that n
188+
// matches at least one of the given conditions and that lock isn't held by
189+
// another party. Otherwise, it returns nil.
190+
//
191+
// n may be a parent of the named resource, if n is an infinite depth lock.
192+
func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) {
193+
// TODO: support Condition.Not and Condition.ETag.
194+
for _, c := range conditions {
195+
n = m.byToken[c.Token]
196+
if n == nil || n.held {
197+
continue
198+
}
199+
if name == n.details.Root {
200+
return n
201+
}
202+
if n.details.ZeroDepth {
203+
continue
204+
}
205+
if n.details.Root == "/" || strings.HasPrefix(name, n.details.Root+"/") {
206+
return n
207+
}
208+
}
209+
return nil
210+
}
211+
212+
func (m *memLS) hold(n *memLSNode) {
213+
if n.held {
214+
panic("webdav: memLS inconsistent held state")
215+
}
216+
n.held = true
217+
if n.details.Duration >= 0 && n.byExpiryIndex >= 0 {
218+
heap.Remove(&m.byExpiry, n.byExpiryIndex)
219+
}
220+
}
221+
222+
func (m *memLS) unhold(n *memLSNode) {
223+
if !n.held {
224+
panic("webdav: memLS inconsistent held state")
225+
}
226+
n.held = false
227+
if n.details.Duration >= 0 {
228+
heap.Push(&m.byExpiry, n)
229+
}
160230
}
161231

162232
func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {

webdav/lock_test.go

+180-15
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func TestWalkToRoot(t *testing.T) {
6161
}
6262

6363
var lockTestDurations = []time.Duration{
64-
-1, // A negative duration means to never expire.
64+
infiniteTimeout, // infiniteTimeout means to never expire.
6565
0, // A zero duration means to expire immediately.
6666
100 * time.Hour, // A very large duration will not expire in these tests.
6767
}
@@ -102,7 +102,7 @@ func TestMemLSCanCreate(t *testing.T) {
102102
for _, name := range lockTestNames {
103103
_, err := m.Create(now, LockDetails{
104104
Root: name,
105-
Duration: -1,
105+
Duration: infiniteTimeout,
106106
ZeroDepth: lockTestZeroDepth(name),
107107
})
108108
if err != nil {
@@ -155,6 +155,147 @@ func TestMemLSCanCreate(t *testing.T) {
155155
check(0, "/")
156156
}
157157

158+
func TestMemLSLookup(t *testing.T) {
159+
now := time.Unix(0, 0)
160+
m := NewMemLS().(*memLS)
161+
162+
badToken := m.nextToken()
163+
t.Logf("badToken=%q", badToken)
164+
165+
for _, name := range lockTestNames {
166+
token, err := m.Create(now, LockDetails{
167+
Root: name,
168+
Duration: infiniteTimeout,
169+
ZeroDepth: lockTestZeroDepth(name),
170+
})
171+
if err != nil {
172+
t.Fatalf("creating lock for %q: %v", name, err)
173+
}
174+
t.Logf("%-15q -> node=%p token=%q", name, m.byName[name], token)
175+
}
176+
177+
baseNames := append([]string{"/a", "/b/c"}, lockTestNames...)
178+
for _, baseName := range baseNames {
179+
for _, suffix := range []string{"", "/0", "/1/2/3"} {
180+
name := baseName + suffix
181+
182+
goodToken := ""
183+
base := m.byName[baseName]
184+
if base != nil && (suffix == "" || !lockTestZeroDepth(baseName)) {
185+
goodToken = base.token
186+
}
187+
188+
for _, token := range []string{badToken, goodToken} {
189+
if token == "" {
190+
continue
191+
}
192+
193+
got := m.lookup(name, Condition{Token: token})
194+
want := base
195+
if token == badToken {
196+
want = nil
197+
}
198+
if got != want {
199+
t.Errorf("name=%-20qtoken=%q (bad=%t): got %p, want %p",
200+
name, token, token == badToken, got, want)
201+
}
202+
}
203+
}
204+
}
205+
}
206+
207+
func TestMemLSConfirm(t *testing.T) {
208+
now := time.Unix(0, 0)
209+
m := NewMemLS().(*memLS)
210+
alice, err := m.Create(now, LockDetails{
211+
Root: "/alice",
212+
Duration: infiniteTimeout,
213+
ZeroDepth: false,
214+
})
215+
tweedle, err := m.Create(now, LockDetails{
216+
Root: "/tweedle",
217+
Duration: infiniteTimeout,
218+
ZeroDepth: false,
219+
})
220+
if err != nil {
221+
t.Fatalf("Create: %v", err)
222+
}
223+
if err := m.consistent(); err != nil {
224+
t.Fatalf("Create: inconsistent state: %v", err)
225+
}
226+
227+
// Test a mismatch between name and condition.
228+
_, err = m.Confirm(now, "/tweedle/dee", "", Condition{Token: alice})
229+
if err != ErrConfirmationFailed {
230+
t.Fatalf("Confirm (mismatch): got %v, want ErrConfirmationFailed", err)
231+
}
232+
if err := m.consistent(); err != nil {
233+
t.Fatalf("Confirm (mismatch): inconsistent state: %v", err)
234+
}
235+
236+
// Test two names (that fall under the same lock) in the one Confirm call.
237+
release, err := m.Confirm(now, "/tweedle/dee", "/tweedle/dum", Condition{Token: tweedle})
238+
if err != nil {
239+
t.Fatalf("Confirm (twins): %v", err)
240+
}
241+
if err := m.consistent(); err != nil {
242+
t.Fatalf("Confirm (twins): inconsistent state: %v", err)
243+
}
244+
release()
245+
if err := m.consistent(); err != nil {
246+
t.Fatalf("release (twins): inconsistent state: %v", err)
247+
}
248+
249+
// Test the same two names in overlapping Confirm / release calls.
250+
releaseDee, err := m.Confirm(now, "/tweedle/dee", "", Condition{Token: tweedle})
251+
if err != nil {
252+
t.Fatalf("Confirm (sequence #0): %v", err)
253+
}
254+
if err := m.consistent(); err != nil {
255+
t.Fatalf("Confirm (sequence #0): inconsistent state: %v", err)
256+
}
257+
258+
_, err = m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle})
259+
if err != ErrConfirmationFailed {
260+
t.Fatalf("Confirm (sequence #1): got %v, want ErrConfirmationFailed", err)
261+
}
262+
if err := m.consistent(); err != nil {
263+
t.Fatalf("Confirm (sequence #1): inconsistent state: %v", err)
264+
}
265+
266+
releaseDee()
267+
if err := m.consistent(); err != nil {
268+
t.Fatalf("release (sequence #2): inconsistent state: %v", err)
269+
}
270+
271+
releaseDum, err := m.Confirm(now, "/tweedle/dum", "", Condition{Token: tweedle})
272+
if err != nil {
273+
t.Fatalf("Confirm (sequence #3): %v", err)
274+
}
275+
if err := m.consistent(); err != nil {
276+
t.Fatalf("Confirm (sequence #3): inconsistent state: %v", err)
277+
}
278+
279+
// Test that you can't unlock a held lock.
280+
err = m.Unlock(now, tweedle)
281+
if err != ErrLocked {
282+
t.Fatalf("Unlock (sequence #4): got %v, want ErrLocked", err)
283+
}
284+
285+
releaseDum()
286+
if err := m.consistent(); err != nil {
287+
t.Fatalf("release (sequence #5): inconsistent state: %v", err)
288+
}
289+
290+
err = m.Unlock(now, tweedle)
291+
if err != nil {
292+
t.Fatalf("Unlock (sequence #6): %v", err)
293+
}
294+
if err := m.consistent(); err != nil {
295+
t.Fatalf("Unlock (sequence #6): inconsistent state: %v", err)
296+
}
297+
}
298+
158299
func TestMemLSNonCanonicalRoot(t *testing.T) {
159300
now := time.Unix(0, 0)
160301
m := NewMemLS().(*memLS)
@@ -304,29 +445,43 @@ func TestMemLSExpiry(t *testing.T) {
304445
}
305446
}
306447

307-
func TestMemLSCreateRefreshUnlock(t *testing.T) {
448+
func TestMemLS(t *testing.T) {
308449
now := time.Unix(0, 0)
309450
m := NewMemLS().(*memLS)
310451
rng := rand.New(rand.NewSource(0))
311452
tokens := map[string]string{}
312-
nCreate, nRefresh, nUnlock := 0, 0, 0
453+
nConfirm, nCreate, nRefresh, nUnlock := 0, 0, 0, 0
313454
const N = 2000
314455

315456
for i := 0; i < N; i++ {
316457
name := lockTestNames[rng.Intn(len(lockTestNames))]
317458
duration := lockTestDurations[rng.Intn(len(lockTestDurations))]
318-
unlocked := false
459+
confirmed, unlocked := false, false
319460

320-
// If the name was already locked, there's a 50-50 chance that
321-
// we refresh or unlock it. Otherwise, we create a lock.
461+
// If the name was already locked, we randomly confirm/release, refresh
462+
// or unlock it. Otherwise, we create a lock.
322463
token := tokens[name]
323464
if token != "" {
324-
if rng.Intn(2) == 0 {
465+
switch rng.Intn(3) {
466+
case 0:
467+
confirmed = true
468+
nConfirm++
469+
release, err := m.Confirm(now, name, "", Condition{Token: token})
470+
if err != nil {
471+
t.Fatalf("iteration #%d: Confirm %q: %v", i, name, err)
472+
}
473+
if err := m.consistent(); err != nil {
474+
t.Fatalf("iteration #%d: inconsistent state: %v", i, err)
475+
}
476+
release()
477+
478+
case 1:
325479
nRefresh++
326480
if _, err := m.Refresh(now, token, duration); err != nil {
327481
t.Fatalf("iteration #%d: Refresh %q: %v", i, name, err)
328482
}
329-
} else {
483+
484+
case 2:
330485
unlocked = true
331486
nUnlock++
332487
if err := m.Unlock(now, token); err != nil {
@@ -347,19 +502,24 @@ func TestMemLSCreateRefreshUnlock(t *testing.T) {
347502
}
348503
}
349504

350-
if duration == 0 || unlocked {
351-
// A zero-duration lock should expire immediately and is
352-
// effectively equivalent to being unlocked.
353-
tokens[name] = ""
354-
} else {
355-
tokens[name] = token
505+
if !confirmed {
506+
if duration == 0 || unlocked {
507+
// A zero-duration lock should expire immediately and is
508+
// effectively equivalent to being unlocked.
509+
tokens[name] = ""
510+
} else {
511+
tokens[name] = token
512+
}
356513
}
357514

358515
if err := m.consistent(); err != nil {
359516
t.Fatalf("iteration #%d: inconsistent state: %v", i, err)
360517
}
361518
}
362519

520+
if nConfirm < N/10 {
521+
t.Fatalf("too few Confirm calls: got %d, want >= %d", nConfirm, N/10)
522+
}
363523
if nCreate < N/10 {
364524
t.Fatalf("too few Create calls: got %d, want >= %d", nCreate, N/10)
365525
}
@@ -464,6 +624,11 @@ func (m *memLS) consistent() error {
464624
if _, ok := m.byName[n.details.Root]; !ok {
465625
return fmt.Errorf("node at name %q in m.byExpiry but not in m.byName", n.details.Root)
466626
}
627+
628+
// No node in m.byExpiry should be held.
629+
if n.held {
630+
return fmt.Errorf("node at name %q in m.byExpiry is held", n.details.Root)
631+
}
467632
}
468633
return nil
469634
}

0 commit comments

Comments
 (0)