Skip to content

Commit

Permalink
Makes the timeout behavior more intuitive.
Browse files Browse the repository at this point in the history
Previously, it would try once "up to" the timeout, but in practice it would
just fall through. This modifies the behavior to block until the timeout has
been reached.
  • Loading branch information
James Phillips committed Jan 6, 2016
1 parent 1d733f4 commit 49342dc
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 21 deletions.
12 changes: 9 additions & 3 deletions api/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ func (l *Lock) Lock(stopCh <-chan struct{}) (<-chan struct{}, error) {
WaitTime: l.opts.LockWaitTime,
}

start := time.Now()
attempts := 0
WAIT:
// Check if we should quit
Expand All @@ -176,9 +177,14 @@ WAIT:
default:
}

// See if we completed a one-shot.
if attempts > 0 && l.opts.LockTryOnce {
return nil, nil
// Handle the one-shot mode.
if l.opts.LockTryOnce && attempts > 0 {
elapsed := time.Now().Sub(start)
if elapsed > qOpts.WaitTime {
return nil, nil
}

qOpts.WaitTime -= elapsed
}
attempts++

Expand Down
5 changes: 3 additions & 2 deletions api/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,9 @@ func TestLock_OneShot(t *testing.T) {
if ch != nil {
t.Fatalf("should not be leader")
}
if diff := time.Now().Sub(start); diff > 2*contender.opts.LockWaitTime {
t.Fatalf("took too long: %9.6f", diff.Seconds())
diff := time.Now().Sub(start)
if diff < contender.opts.LockWaitTime || diff > 2*contender.opts.LockWaitTime {
t.Fatalf("time out of bounds: %9.6f", diff.Seconds())
}

// Unlock and then make sure the contender can get it.
Expand Down
12 changes: 9 additions & 3 deletions api/semaphore.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ func (s *Semaphore) Acquire(stopCh <-chan struct{}) (<-chan struct{}, error) {
WaitTime: s.opts.SemaphoreWaitTime,
}

start := time.Now()
attempts := 0
WAIT:
// Check if we should quit
Expand All @@ -195,9 +196,14 @@ WAIT:
default:
}

// See if we completed a one-shot.
if attempts > 0 && s.opts.SemaphoreTryOnce {
return nil, nil
// Handle the one-shot mode.
if s.opts.SemaphoreTryOnce && attempts > 0 {
elapsed := time.Now().Sub(start)
if elapsed > qOpts.WaitTime {
return nil, nil
}

qOpts.WaitTime -= elapsed
}
attempts++

Expand Down
5 changes: 3 additions & 2 deletions api/semaphore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -499,8 +499,9 @@ func TestSemaphore_OneShot(t *testing.T) {
if ch != nil {
t.Fatalf("should not have acquired the semaphore")
}
if diff := time.Now().Sub(start); diff > 2*contender.opts.SemaphoreWaitTime {
t.Fatalf("took too long: %9.6f", diff.Seconds())
diff := time.Now().Sub(start)
if diff < contender.opts.SemaphoreWaitTime || diff > 2*contender.opts.SemaphoreWaitTime {
t.Fatalf("time out of bounds: %9.6f", diff.Seconds())
}

// Give up a slot and make sure the third one can get it.
Expand Down
10 changes: 5 additions & 5 deletions command/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ Options:
-name="" Optional name to associate with lock session.
-token="" ACL token to use. Defaults to that of agent.
-pass-stdin Pass stdin to child process.
-try=duration Make a single attempt to acquire the lock, waiting
up to the given duration (eg. "15s").
-try=timeout Attempt to acquire the lock up to the given
timeout (eg. "15s").
-monitor-retry=n Retry up to n times if Consul returns a 500 error
while monitoring the lock. This allows riding out brief
periods of unavailability without causing leader
Expand Down Expand Up @@ -145,12 +145,12 @@ func (c *LockCommand) run(args []string, lu **LockUnlock) int {
var err error
wait, err = time.ParseDuration(try)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing duration for 'try' option: %s", err))
c.Ui.Error(fmt.Sprintf("Error parsing try timeout: %s", err))
return 1
}

if wait < 0 {
c.Ui.Error("Duration for 'try' option must be positive")
if wait <= 0 {
c.Ui.Error("Try timeout must be positive")
return 1
}

Expand Down
5 changes: 3 additions & 2 deletions command/lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ func argFail(t *testing.T, args []string, expected string) {
}

func TestLockCommand_BadArgs(t *testing.T) {
argFail(t, []string{"-try=blah", "test/prefix", "date"}, "parsing duration")
argFail(t, []string{"-try=-10s", "test/prefix", "date"}, "must be positive")
argFail(t, []string{"-try=blah", "test/prefix", "date"}, "parsing try timeout")
argFail(t, []string{"-try=0s", "test/prefix", "date"}, "timeout must be positive")
argFail(t, []string{"-try=-10s", "test/prefix", "date"}, "timeout must be positive")
argFail(t, []string{"-monitor-retry=-5", "test/prefix", "date"}, "must be >= 0")
}

Expand Down
7 changes: 3 additions & 4 deletions website/source/docs/commands/lock.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,9 @@ The list of available flags are:

* `-pass-stdin` - Pass stdin to child process.

* `-try` - Make a single attempt to acquire the lock, waiting up to the given
duration supplied as the argument. The duration is a decimal number, with
unit suffix, such as "500ms". Valid time units are "ns", "us" (or "µs"), "ms",
"s", "m", "h".
* `-try` - Attempt to acquire the lock up to the given timeout. The timeout is a
positive decimal number, with unit suffix, such as "500ms". Valid time units
are "ns", "us" (or "µs"), "ms", "s", "m", "h".

* `-monitor-retry` - Retry up to this number of times if Consul returns a 500 error
while monitoring the lock. This allows riding out brief periods of unavailability
Expand Down

0 comments on commit 49342dc

Please sign in to comment.