Search code examples
golockingconsul

Two clients acquiring the same lock in Consul


I have the following code:

package publicservice

import (
    "time"
    "github.com/hashicorp/consul/api"
    "github.com/hashicorp/consul/testutil"
    "testing"
)

func TestAcquireLock(t *testing.T) {
    consul := testutil.NewTestServer(t)
    defer consul.Stop()

    firstClient, err := api.NewClient(&api.Config{
        Address: consul.HTTPAddr,
    })

    if err != nil {
        t.Fatalf("failed to create first client with %v", err)
    }

    secondClient, err := api.NewClient(&api.Config{
        Address: consul.HTTPAddr,
    })

    if err != nil {
        t.Fatalf("failed to create second client with %v", err)
    }

    lockKey := "sample-lock-key"

    firstLock, err := firstClient.LockOpts(&api.LockOptions{
        Key: lockKey,
    })

    if err != nil {
        t.Fatalf("failed to create first lock %v", err)
    }

    _, err = firstLock.Lock(nil)

    if err != nil {
        t.Fatalf("failed to acquire first lock %v", err)
    }

    defer firstLock.Unlock()

    secondLock, err := secondClient.LockOpts(&api.LockOptions{
        Key: lockKey,
        LockTryOnce: true,
        LockWaitTime: time.Second,
    })

    if err != nil {
        t.Fatalf("failed to create second lock %v", err)
    }

    _, err = secondLock.Lock(nil)

    if err == nil {
        t.Fatal("should not have acquired lock here")
    }
}

And when I run it this second lock call never fails, even if it should fail since the lock is already being held by another client as it can be seen by the logs:

2016/11/29 14:46:25 [DEBUG] http: Request GET /v1/catalog/nodes (1.115897356s) from=127.0.0.1:50492
2016/11/29 14:46:25 [DEBUG] http: Request PUT /v1/session/create (939.436µs) from=127.0.0.1:50493
2016/11/29 14:46:25 [DEBUG] http: Request GET /v1/kv/sample-lock-key?wait=15000ms (269.823µs) from=127.0.0.1:50493
2016/11/29 14:46:25 [DEBUG] http: Request PUT /v1/kv/sample-lock-key?acquire=aa8fb351-6b6e-8e17-f8d9-fb265c497a9f&flags=3304740253564472344 (899.564µs) from=127.0.0.1:50493
2016/11/29 14:46:25 [DEBUG] http: Request GET /v1/kv/sample-lock-key?consistent= (112.87µs) from=127.0.0.1:50493
2016/11/29 14:46:25 [DEBUG] http: Request PUT /v1/session/create (595.554µs) from=127.0.0.1:50494
2016/11/29 14:46:25 [DEBUG] http: Request GET /v1/kv/sample-lock-key?wait=1000ms (38.602µs) from=127.0.0.1:50494
2016/11/29 14:46:26 [DEBUG] http: Request GET /v1/kv/sample-lock-key?index=7&wait=999ms (1.007117154s) from=127.0.0.1:50494
2016/11/29 14:46:26 [DEBUG] http: Request PUT /v1/session/destroy/fd83b8f8-1757-d9d7-6f0e-5defaf26b886 (1.549558ms) from=127.0.0.1:50494
2016/11/29 14:46:26 [DEBUG] http: Request PUT /v1/kv/sample-lock-key?flags=3304740253564472344&release=aa8fb351-6b6e-8e17-f8d9-fb265c497a9f (1.56451ms) from=127.0.0.1:50495
2016/11/29 14:46:26 [DEBUG] http: Request GET /v1/kv/sample-lock-key?consistent=&index=7 (1.010637359s) from=127.0.0.1:50493

What am I missing here? Is there something else I must do to make sure the second client doesn't acquire the lock?


Solution

  • Issue was that you have to check the channel that's returned from Lock, if the lock is already held the channel is nil:

    package publicservice
    
    import (
        "time"
        "github.com/hashicorp/consul/api"
        "github.com/hashicorp/consul/testutil"
        "testing"
        "github.com/xtgo/uuid"
    )
    
    func TestAcquireLock(t *testing.T) {
        consul := testutil.NewTestServer(t)
        defer consul.Stop()
    
        firstClient, err := api.NewClient(&api.Config{
            Address: consul.HTTPAddr,
        })
    
        if err != nil {
            t.Fatalf("failed to create first client with %v", err)
        }
    
        secondClient, err := api.NewClient(&api.Config{
            Address: consul.HTTPAddr,
        })
    
        if err != nil {
            t.Fatalf("failed to create second client with %v", err)
        }
    
        lockKey := "sample-lock-key"
    
        firstLock, err := firstClient.LockOpts(&api.LockOptions{
            Key: lockKey,
            SessionName: uuid.NewRandom().String(),
        })
    
        if err != nil {
            t.Fatalf("failed to create first lock %v", err)
        }
    
        firstResult, err := firstLock.Lock(nil)
    
        t.Logf("=====> result for first lock is %v", firstResult)
    
        if err != nil {
            t.Fatalf("failed to acquire first lock %v", err)
        }
    
        defer firstLock.Unlock()
    
        secondLock, err := secondClient.LockOpts(&api.LockOptions{
            Key: lockKey,
            LockTryOnce: true,
            LockWaitTime: time.Second,
            SessionName: uuid.NewRandom().String(),
        })
    
        if err != nil {
            t.Fatalf("failed to create second lock %v", err)
        }
    
        secondResult, err := secondLock.Lock(nil)
    
        if secondResult != nil || err != nil {
            t.Fatal("should not have acquired lock here")
        }
    }