Skip to content

Commit

Permalink
pgsql: Move lock to lock module
Browse files Browse the repository at this point in the history
  • Loading branch information
KeyboardNerd committed Mar 13, 2019
1 parent 0b32b36 commit ba50d7c
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 114 deletions.
33 changes: 18 additions & 15 deletions database/pgsql/lock.go → database/pgsql/lock/lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package pgsql
package lock

import (
"database/sql"
"time"

"github.com/coreos/clair/database/pgsql/monitoring"
"github.com/coreos/clair/database/pgsql/util"
log "github.com/sirupsen/logrus"
)

Expand All @@ -38,12 +41,12 @@ const (
SELECT owner, until FROM lock WHERE name = $1`
)

func (tx *pgSession) AcquireLock(lockName, whoami string, desiredDuration time.Duration) (bool, time.Time, error) {
func AcquireLock(tx *sql.Tx, lockName, whoami string, desiredDuration time.Duration) (bool, time.Time, error) {
if lockName == "" || whoami == "" || desiredDuration == 0 {
panic("invalid lock parameters")
}

if err := tx.pruneLocks(); err != nil {
if err := PruneLocks(tx); err != nil {
return false, time.Time{}, err
}

Expand All @@ -54,50 +57,50 @@ func (tx *pgSession) AcquireLock(lockName, whoami string, desiredDuration time.D
lockOwner string
)

defer observeQueryTime("Lock", "soiLock", time.Now())
defer monitoring.ObserveQueryTime("Lock", "soiLock", time.Now())
err := tx.QueryRow(soiLock, lockName, whoami, desiredLockedUntil).Scan(&lockOwner, &lockedUntil)
return lockOwner == whoami, lockedUntil, err
return lockOwner == whoami, lockedUntil, util.HandleError("AcquireLock", err)
}

func (tx *pgSession) ExtendLock(lockName, whoami string, desiredDuration time.Duration) (bool, time.Time, error) {
func ExtendLock(tx *sql.Tx, lockName, whoami string, desiredDuration time.Duration) (bool, time.Time, error) {
if lockName == "" || whoami == "" || desiredDuration == 0 {
panic("invalid lock parameters")
}

desiredLockedUntil := time.Now().Add(desiredDuration)

defer observeQueryTime("Lock", "update", time.Now())
defer monitoring.ObserveQueryTime("Lock", "update", time.Now())
result, err := tx.Exec(updateLock, lockName, whoami, desiredLockedUntil)
if err != nil {
return false, time.Time{}, handleError("updateLock", err)
return false, time.Time{}, util.HandleError("updateLock", err)
}

if numRows, err := result.RowsAffected(); err == nil {
// This is the only happy path.
return numRows > 0, desiredLockedUntil, nil
}

return false, time.Time{}, handleError("updateLock", err)
return false, time.Time{}, util.HandleError("updateLock", err)
}

func (tx *pgSession) ReleaseLock(name, owner string) error {
func ReleaseLock(tx *sql.Tx, name, owner string) error {
if name == "" || owner == "" {
panic("invalid lock parameters")
}

defer observeQueryTime("Unlock", "all", time.Now())
defer monitoring.ObserveQueryTime("Unlock", "all", time.Now())
_, err := tx.Exec(removeLock, name, owner)
return err
}

// pruneLocks removes every expired locks from the database
func (tx *pgSession) pruneLocks() error {
defer observeQueryTime("pruneLocks", "all", time.Now())
func PruneLocks(tx *sql.Tx) error {
defer monitoring.ObserveQueryTime("pruneLocks", "all", time.Now())

if r, err := tx.Exec(removeLockExpired, time.Now().UTC()); err != nil {
return handleError("removeLockExpired", err)
return util.HandleError("removeLockExpired", err)
} else if affected, err := r.RowsAffected(); err != nil {
return handleError("removeLockExpired", err)
return util.HandleError("removeLockExpired", err)
} else {
log.Debugf("Pruned %d Locks", affected)
}
Expand Down
100 changes: 100 additions & 0 deletions database/pgsql/lock/lock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2019 clair authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lock

import (
"testing"
"time"

"github.com/coreos/clair/database/pgsql/testutil"
"github.com/stretchr/testify/require"
)

func TestAcquireLockReturnsExistingLockDuration(t *testing.T) {
tx, cleanup := testutil.CreateTestTxWithFixtures(t, "Lock")
defer cleanup()

acquired, originalExpiration, err := AcquireLock(tx, "test1", "owner1", time.Minute)
require.Nil(t, err)
require.True(t, acquired)

acquired2, expiration, err := AcquireLock(tx, "test1", "owner2", time.Hour)
require.Nil(t, err)
require.False(t, acquired2)
require.Equal(t, expiration, originalExpiration)
}

func TestLock(t *testing.T) {
db, cleanup := testutil.CreateTestDBWithFixture(t, "Lock")
defer cleanup()

tx, err := db.Begin()
if err != nil {
panic(err)
}

// Create a first lock.
l, _, err := AcquireLock(tx, "test1", "owner1", time.Minute)
require.Nil(t, err)
require.True(t, l)
tx = testutil.RestartTransaction(db, tx, true)

// lock again by itself, the previous lock is not expired yet.
l, _, err = AcquireLock(tx, "test1", "owner1", time.Minute)
require.Nil(t, err)
require.True(t, l)
tx = testutil.RestartTransaction(db, tx, false)

// Try to renew the same lock with another owner.
l, _, err = ExtendLock(tx, "test1", "owner2", time.Minute)
require.Nil(t, err)
require.False(t, l)
tx = testutil.RestartTransaction(db, tx, false)

l, _, err = AcquireLock(tx, "test1", "owner2", time.Minute)
require.Nil(t, err)
require.False(t, l)
tx = testutil.RestartTransaction(db, tx, false)

// Renew the lock.
l, _, err = ExtendLock(tx, "test1", "owner1", 2*time.Minute)
require.Nil(t, err)
require.True(t, l)
tx = testutil.RestartTransaction(db, tx, true)

// Unlock and then relock by someone else.
err = ReleaseLock(tx, "test1", "owner1")
require.Nil(t, err)
tx = testutil.RestartTransaction(db, tx, true)

l, _, err = AcquireLock(tx, "test1", "owner2", time.Minute)
require.Nil(t, err)
require.True(t, l)
tx = testutil.RestartTransaction(db, tx, true)

// Create a second lock which is actually already expired ...
l, _, err = AcquireLock(tx, "test2", "owner1", -time.Minute)
require.Nil(t, err)
require.True(t, l)
tx = testutil.RestartTransaction(db, tx, true)

// Take over the lock
l, _, err = AcquireLock(tx, "test2", "owner2", time.Minute)
require.Nil(t, err)
require.True(t, l)
tx = testutil.RestartTransaction(db, tx, true)

require.Nil(t, tx.Rollback())
}
99 changes: 0 additions & 99 deletions database/pgsql/lock_test.go

This file was deleted.

0 comments on commit ba50d7c

Please sign in to comment.