Skip to content

Commit

Permalink
Add directory locking
Browse files Browse the repository at this point in the history
  • Loading branch information
webmaster128 committed Jan 17, 2024
1 parent a690c10 commit 10a4e0c
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 3 deletions.
29 changes: 27 additions & 2 deletions internal/api/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "C"

import (
"fmt"
"os"
"runtime"
"strings"
"syscall"
Expand All @@ -32,12 +33,32 @@ type (
)

type Cache struct {
ptr *C.cache_t
ptr *C.cache_t
lockfile os.File
}

type Querier = types.Querier

func InitCache(dataDir string, supportedCapabilities []string, cacheSize uint32, instanceMemoryLimit uint32) (Cache, error) {
err := os.MkdirAll(dataDir, 0755)
if err != nil {
return Cache{}, fmt.Errorf("Could not create base directory")
}

lockfile, err := os.OpenFile(dataDir+"/exclusive.lock", os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return Cache{}, fmt.Errorf("Could not open exclusive.lock")
}
_, err = lockfile.WriteString("This is a lockfile that prevent two VM instances to operate on the same directory in parallel.\nSee codebase at github.com/CosmWasm/wasmvm for more information.\nSafety first – brought to you by Confio ❤️\n")
if err != nil {
return Cache{}, fmt.Errorf("Error writing to exclusive.lock")
}

err = syscall.Flock(int(lockfile.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
if err != nil {
return Cache{}, fmt.Errorf("Could not lock exclusive.lock. Is a different VM running in the same directory already?")
}

dataDirBytes := []byte(dataDir)
supportedCapabilitiesBytes := []byte(strings.Join(supportedCapabilities, ","))

Expand All @@ -52,11 +73,15 @@ func InitCache(dataDir string, supportedCapabilities []string, cacheSize uint32,
if err != nil {
return Cache{}, errorWithMessage(err, errmsg)
}
return Cache{ptr: ptr}, nil
return Cache{ptr: ptr, lockfile: *lockfile}, nil
}

func ReleaseCache(cache Cache) {
C.release_cache(cache.ptr)

// Release directory lock
syscall.Flock(int(cache.lockfile.Fd()), syscall.LOCK_UN)

Check failure on line 83 in internal/api/lib.go

View workflow job for this annotation

GitHub Actions / lint

Error return value of `syscall.Flock` is not checked (errcheck)
cache.lockfile.Close()
}

func StoreCode(cache Cache, wasm []byte) ([]byte, error) {
Expand Down
42 changes: 41 additions & 1 deletion internal/api/lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,47 @@ func TestInitCacheErrorsForBrokenDir(t *testing.T) {
// On Unix we should not have permission to create this.
cannotBeCreated := "/foo:bar"
_, err := InitCache(cannotBeCreated, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT)
require.ErrorContains(t, err, "Error creating state directory")
require.ErrorContains(t, err, "Could not create base directory")
}

func TestInitLockingPreventsConcurrentAccess(t *testing.T) {
tmpdir, err := os.MkdirTemp("", "wasmvm-testing")
require.NoError(t, err)
defer os.RemoveAll(tmpdir)

cache1, err1 := InitCache(tmpdir, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT)
require.NoError(t, err1)

_, err2 := InitCache(tmpdir, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT)
require.ErrorContains(t, err2, "Could not lock exclusive.lock")

ReleaseCache(cache1)

// Now we can try again
cache3, err3 := InitCache(tmpdir, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT)
require.NoError(t, err3)
ReleaseCache(cache3)
}

func TestInitLockingAllowsMultipleInstancesInDifferentDirs(t *testing.T) {
tmpdir1, err := os.MkdirTemp("", "wasmvm-testing1")

Check failure on line 81 in internal/api/lib_test.go

View workflow job for this annotation

GitHub Actions / lint

ineffectual assignment to err (ineffassign)
tmpdir2, err := os.MkdirTemp("", "wasmvm-testing2")

Check failure on line 82 in internal/api/lib_test.go

View workflow job for this annotation

GitHub Actions / lint

ineffectual assignment to err (ineffassign)
tmpdir3, err := os.MkdirTemp("", "wasmvm-testing3")
require.NoError(t, err)
defer os.RemoveAll(tmpdir1)
defer os.RemoveAll(tmpdir2)
defer os.RemoveAll(tmpdir3)

cache1, err1 := InitCache(tmpdir1, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT)
require.NoError(t, err1)
cache2, err2 := InitCache(tmpdir2, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT)
require.NoError(t, err2)
cache3, err3 := InitCache(tmpdir3, TESTING_CAPABILITIES, TESTING_CACHE_SIZE, TESTING_MEMORY_LIMIT)
require.NoError(t, err3)

ReleaseCache(cache1)
ReleaseCache(cache2)
ReleaseCache(cache3)
}

func TestInitCacheEmptyCapabilities(t *testing.T) {
Expand Down

0 comments on commit 10a4e0c

Please sign in to comment.