From 180b593153ad702d32899c57c1f7089541b59fd0 Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Mon, 30 Aug 2021 12:07:34 -0700 Subject: [PATCH 01/17] Initial commit --- .vscode/launch.json | 15 ++ cross_platform_lock.go | 64 +++++++ cross_platform_lock_test.go | 77 ++++++++ flock/.gitignore | 24 +++ flock/.travis.yml | 10 ++ flock/LICENSE | 27 +++ flock/README.md | 41 +++++ flock/appveyor.yml | 25 +++ flock/flock.go | 144 +++++++++++++++ flock/flock_aix.go | 281 ++++++++++++++++++++++++++++++ flock/flock_example_test.go | 72 ++++++++ flock/flock_internal_test.go | 30 ++++ flock/flock_test.go | 300 ++++++++++++++++++++++++++++++++ flock/flock_unix.go | 197 +++++++++++++++++++++ flock/flock_winapi.go | 76 ++++++++ flock/flock_windows.go | 142 +++++++++++++++ go.mod | 10 ++ go.sum | 197 +++++++++++++++++++++ internal/cache_accessor.go | 70 ++++++++ internal/mac_persistence.go | 5 + internal/windows_persistence.go | 34 ++++ persistence_aspect.go | 51 ++++++ 22 files changed, 1892 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 cross_platform_lock.go create mode 100644 cross_platform_lock_test.go create mode 100644 flock/.gitignore create mode 100644 flock/.travis.yml create mode 100644 flock/LICENSE create mode 100644 flock/README.md create mode 100644 flock/appveyor.yml create mode 100644 flock/flock.go create mode 100644 flock/flock_aix.go create mode 100644 flock/flock_example_test.go create mode 100644 flock/flock_internal_test.go create mode 100644 flock/flock_test.go create mode 100644 flock/flock_unix.go create mode 100644 flock/flock_winapi.go create mode 100644 flock/flock_windows.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/cache_accessor.go create mode 100644 internal/mac_persistence.go create mode 100644 internal/windows_persistence.go create mode 100644 persistence_aspect.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..608d3c6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${fileDirname}" + } + ] +} \ No newline at end of file diff --git a/cross_platform_lock.go b/cross_platform_lock.go new file mode 100644 index 0000000..1ed1e7c --- /dev/null +++ b/cross_platform_lock.go @@ -0,0 +1,64 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/AzureAD/microsoft-authentication-extensions-for-go/flock" +) + +type CrossPlatLock struct { + retryNumber int + retryDelayMilliseconds int + + lockFile *os.File + + fileLock *flock.Flock + + lockfileName string + locked bool +} + +func NewLock(lockFileName string, retryNumber int) (CrossPlatLock, error) { + lockfile, err := os.Create("cache_trial.json.lockfile") + if err != nil { + fmt.Println(err) + } + return CrossPlatLock{ + lockfileName: lockFileName, + retryNumber: retryNumber, + lockFile: lockfile, + fileLock: flock.New(lockfile.Name()), + }, nil +} + +func (c CrossPlatLock) Lock() error { + + err := c.fileLock.Lock() + if err != nil { + return err + } + if c.fileLock.Locked() { + c.fileLock.Fh.WriteString("Hello \n") + } + c.locked = true + time.Sleep(10) + return nil +} + +func (c CrossPlatLock) UnLock() error { + if c.fileLock != nil { + if err := c.fileLock.Unlock(); err != nil { + fmt.Println("UnLock ", err.Error()) + // handle unlock error + } + c.lockFile.Close() + var err = os.Remove(c.fileLock.Path()) + if err != nil { + fmt.Println(err.Error()) + } + c.locked = false + } + return nil +} diff --git a/cross_platform_lock_test.go b/cross_platform_lock_test.go new file mode 100644 index 0000000..4d0f4a3 --- /dev/null +++ b/cross_platform_lock_test.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + "sync" + "testing" + "time" +) + +func doSomething(i int) { + cacheAccessor := NewFileCache("lock.lock") + cacheAccessor.lock.Lock() + defer cacheAccessor.lock.UnLock() + file, err := os.OpenFile("lockintervals.txt", os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + log.Println(err) + } + file.WriteString(fmt.Sprintf("< %d \n", i)) + time.Sleep(1 * time.Second) + file.WriteString(fmt.Sprintf("> %d \n", i)) +} +func validateResult() int { + count := 0 + var prevProc string = "" + var tag string + var proc string + data, err := os.ReadFile("lockintervals.txt") + if err != nil { + fmt.Println(err) + } + dat := string(data) + temp := strings.Split(dat, "\n") + for _, ele := range temp { + if ele != "" { + count += 1 + split := strings.Split(ele, " ") + tag = split[0] + proc = split[1] + if prevProc != "" { + if proc != prevProc { + fmt.Println("Process overlap found") + } + if tag != ">" { + fmt.Println("Process overlap found 1") + } + prevProc = "" + + } else { + if tag != "<" { + fmt.Println("Opening bracket not found") + } + prevProc = proc + } + } + } + return count +} + +func TestCrossPlatLock(t *testing.T) { + var wg sync.WaitGroup + wg.Add(30) + for i := 0; i < 30; i++ { + go func(i int) { + defer wg.Done() + doSomething(i) + }(i) + } + wg.Wait() + n := validateResult() + fmt.Println(n) + if n > 60 { + fmt.Println("Should not observe starvation") + } +} diff --git a/flock/.gitignore b/flock/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/flock/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/flock/.travis.yml b/flock/.travis.yml new file mode 100644 index 0000000..b16d040 --- /dev/null +++ b/flock/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.14.x + - 1.15.x +script: go test -v -check.vv -race ./... +sudo: false +notifications: + email: + on_success: never + on_failure: always diff --git a/flock/LICENSE b/flock/LICENSE new file mode 100644 index 0000000..8b8ff36 --- /dev/null +++ b/flock/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015-2020, Tim Heckman +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of gofrs nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/flock/README.md b/flock/README.md new file mode 100644 index 0000000..71ce636 --- /dev/null +++ b/flock/README.md @@ -0,0 +1,41 @@ +# flock +[![TravisCI Build Status](https://img.shields.io/travis/gofrs/flock/master.svg?style=flat)](https://travis-ci.org/gofrs/flock) +[![GoDoc](https://img.shields.io/badge/godoc-flock-blue.svg?style=flat)](https://godoc.org/github.com/gofrs/flock) +[![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/gofrs/flock/blob/master/LICENSE) +[![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/flock)](https://goreportcard.com/report/github.com/gofrs/flock) + +`flock` implements a thread-safe sync.Locker interface for file locking. It also +includes a non-blocking TryLock() function to allow locking without blocking execution. + +## License +`flock` is released under the BSD 3-Clause License. See the `LICENSE` file for more details. + +## Go Compatibility +This package makes use of the `context` package that was introduced in Go 1.7. As such, this +package has an implicit dependency on Go 1.7+. + +## Installation +``` +go get -u github.com/gofrs/flock +``` + +## Usage +```Go +import "github.com/gofrs/flock" + +fileLock := flock.New("/var/lock/go-lock.lock") + +locked, err := fileLock.TryLock() + +if err != nil { + // handle locking error +} + +if locked { + // do work + fileLock.Unlock() +} +``` + +For more detailed usage information take a look at the package API docs on +[GoDoc](https://godoc.org/github.com/gofrs/flock). diff --git a/flock/appveyor.yml b/flock/appveyor.yml new file mode 100644 index 0000000..909b4bf --- /dev/null +++ b/flock/appveyor.yml @@ -0,0 +1,25 @@ +version: '{build}' + +build: false +deploy: false + +clone_folder: 'c:\gopath\src\github.com\gofrs\flock' + +environment: + GOPATH: 'c:\gopath' + GOVERSION: '1.15' + +init: + - git config --global core.autocrlf input + +install: + - rmdir c:\go /s /q + - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi + - msiexec /i go%GOVERSION%.windows-amd64.msi /q + - set Path=c:\go\bin;c:\gopath\bin;%Path% + - go version + - go env + +test_script: + - go get -t ./... + - go test -race -v ./... diff --git a/flock/flock.go b/flock/flock.go new file mode 100644 index 0000000..f730294 --- /dev/null +++ b/flock/flock.go @@ -0,0 +1,144 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// Package flock implements a thread-safe interface for file locking. +// It also includes a non-blocking TryLock() function to allow locking +// without blocking execution. +// +// Package flock is released under the BSD 3-Clause License. See the LICENSE file +// for more details. +// +// While using this library, remember that the locking behaviors are not +// guaranteed to be the same on each platform. For example, some UNIX-like +// operating systems will transparently convert a shared lock to an exclusive +// lock. If you Unlock() the flock from a location where you believe that you +// have the shared lock, you may accidentally drop the exclusive lock. +package flock + +import ( + "context" + "os" + "runtime" + "sync" + "time" +) + +// Flock is the struct type to handle file locking. All fields are unexported, +// with access to some of the fields provided by getter methods (Path() and Locked()). +type Flock struct { + path string + m sync.RWMutex + Fh *os.File + l bool + r bool +} + +// New returns a new instance of *Flock. The only parameter +// it takes is the path to the desired lockfile. +func New(path string) *Flock { + return &Flock{path: path} +} + +// NewFlock returns a new instance of *Flock. The only parameter +// it takes is the path to the desired lockfile. +// +// Deprecated: Use New instead. +func NewFlock(path string) *Flock { + return New(path) +} + +// Close is equivalent to calling Unlock. +// +// This will release the lock and close the underlying file descriptor. +// It will not remove the file from disk, that's up to your application. +func (f *Flock) Close() error { + return f.Unlock() +} + +// Path returns the path as provided in NewFlock(). +func (f *Flock) Path() string { + return f.path +} + +// Locked returns the lock state (locked: true, unlocked: false). +// +// Warning: by the time you use the returned value, the state may have changed. +func (f *Flock) Locked() bool { + f.m.RLock() + defer f.m.RUnlock() + return f.l +} + +// RLocked returns the read lock state (locked: true, unlocked: false). +// +// Warning: by the time you use the returned value, the state may have changed. +func (f *Flock) RLocked() bool { + f.m.RLock() + defer f.m.RUnlock() + return f.r +} + +func (f *Flock) String() string { + return f.path +} + +// TryLockContext repeatedly tries to take an exclusive lock until one of the +// conditions is met: TryLock succeeds, TryLock fails with error, or Context +// Done channel is closed. +func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { + return tryCtx(ctx, f.TryLock, retryDelay) +} + +// TryRLockContext repeatedly tries to take a shared lock until one of the +// conditions is met: TryRLock succeeds, TryRLock fails with error, or Context +// Done channel is closed. +func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { + return tryCtx(ctx, f.TryRLock, retryDelay) +} + +func tryCtx(ctx context.Context, fn func() (bool, error), retryDelay time.Duration) (bool, error) { + if ctx.Err() != nil { + return false, ctx.Err() + } + for { + if ok, err := fn(); ok || err != nil { + return ok, err + } + select { + case <-ctx.Done(): + return false, ctx.Err() + case <-time.After(retryDelay): + // try again + } + } +} + +func (f *Flock) setFh() error { + // open a new os.File instance + // create it if it doesn't exist, and open the file read-only. + flags := os.O_CREATE + if runtime.GOOS == "aix" { + // AIX cannot preform write-lock (ie exclusive) on a + // read-only file. + flags |= os.O_RDWR + } else { + flags |= os.O_RDONLY + } + fh, err := os.OpenFile(f.path, flags, os.FileMode(0600)) + if err != nil { + return err + } + + // set the filehandle on the struct + f.Fh = fh + return nil +} + +// ensure the file handle is closed if no lock is held +func (f *Flock) ensureFhState() { + if !f.l && !f.r && f.Fh != nil { + f.Fh.Close() + f.Fh = nil + } +} diff --git a/flock/flock_aix.go b/flock/flock_aix.go new file mode 100644 index 0000000..7277c1b --- /dev/null +++ b/flock/flock_aix.go @@ -0,0 +1,281 @@ +// Copyright 2019 Tim Heckman. All rights reserved. Use of this source code is +// governed by the BSD 3-Clause license that can be found in the LICENSE file. + +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This code implements the filelock API using POSIX 'fcntl' locks, which attach +// to an (inode, process) pair rather than a file descriptor. To avoid unlocking +// files prematurely when the same file is opened through different descriptors, +// we allow only one read-lock at a time. +// +// This code is adapted from the Go package: +// cmd/go/internal/lockedfile/internal/filelock + +//+build aix + +package flock + +import ( + "errors" + "io" + "os" + "sync" + "syscall" + + "golang.org/x/sys/unix" +) + +type lockType int16 + +const ( + readLock lockType = unix.F_RDLCK + writeLock lockType = unix.F_WRLCK +) + +type cmdType int + +const ( + tryLock cmdType = unix.F_SETLK + waitLock cmdType = unix.F_SETLKW +) + +type inode = uint64 + +type inodeLock struct { + owner *Flock + queue []<-chan *Flock +} + +var ( + mu sync.Mutex + inodes = map[*Flock]inode{} + locks = map[inode]inodeLock{} +) + +// Lock is a blocking call to try and take an exclusive file lock. It will wait +// until it is able to obtain the exclusive file lock. It's recommended that +// TryLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already exclusive-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +// +// If the *Flock has a shared lock (RLock), this may transparently replace the +// shared lock with an exclusive lock on some UNIX-like operating systems. Be +// careful when using exclusive locks in conjunction with shared locks +// (RLock()), because calling Unlock() may accidentally release the exclusive +// lock that was once a shared lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, writeLock) +} + +// RLock is a blocking call to try and take a shared file lock. It will wait +// until it is able to obtain the shared file lock. It's recommended that +// TryRLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already shared-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, readLock) +} + +func (f *Flock) lock(locked *bool, flag lockType) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + defer f.ensureFhState() + } + + if _, err := f.doLock(waitLock, flag, true); err != nil { + return err + } + + *locked = true + return nil +} + +func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) { + // POSIX locks apply per inode and process, and the lock for an inode is + // released when *any* descriptor for that inode is closed. So we need to + // synchronize access to each inode internally, and must serialize lock and + // unlock calls that refer to the same inode through different descriptors. + fi, err := f.fh.Stat() + if err != nil { + return false, err + } + ino := inode(fi.Sys().(*syscall.Stat_t).Ino) + + mu.Lock() + if i, dup := inodes[f]; dup && i != ino { + mu.Unlock() + return false, &os.PathError{ + Path: f.Path(), + Err: errors.New("inode for file changed since last Lock or RLock"), + } + } + + inodes[f] = ino + + var wait chan *Flock + l := locks[ino] + if l.owner == f { + // This file already owns the lock, but the call may change its lock type. + } else if l.owner == nil { + // No owner: it's ours now. + l.owner = f + } else if !blocking { + // Already owned: cannot take the lock. + mu.Unlock() + return false, nil + } else { + // Already owned: add a channel to wait on. + wait = make(chan *Flock) + l.queue = append(l.queue, wait) + } + locks[ino] = l + mu.Unlock() + + if wait != nil { + wait <- f + } + + err = setlkw(f.fh.Fd(), cmd, lt) + + if err != nil { + f.doUnlock() + if cmd == tryLock && err == unix.EACCES { + return false, nil + } + return false, err + } + + return true, nil +} + +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + if err := f.doUnlock(); err != nil { + return err + } + + f.fh.Close() + + f.l = false + f.r = false + f.fh = nil + + return nil +} + +func (f *Flock) doUnlock() (err error) { + var owner *Flock + mu.Lock() + ino, ok := inodes[f] + if ok { + owner = locks[ino].owner + } + mu.Unlock() + + if owner == f { + err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK) + } + + mu.Lock() + l := locks[ino] + if len(l.queue) == 0 { + // No waiters: remove the map entry. + delete(locks, ino) + } else { + // The first waiter is sending us their file now. + // Receive it and update the queue. + l.owner = <-l.queue[0] + l.queue = l.queue[1:] + locks[ino] = l + } + delete(inodes, f) + mu.Unlock() + + return err +} + +// TryLock is the preferred function for taking an exclusive file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. If +// we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, writeLock) +} + +// TryRLock is the preferred function for taking a shared file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the shared file +// lock, the function will return false instead of waiting for the lock. If we +// get the lock, we also set the *Flock instance as being share-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, readLock) +} + +func (f *Flock) try(locked *bool, flag lockType) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + defer f.ensureFhState() + } + + haslock, err := f.doLock(tryLock, flag, false) + if err != nil { + return false, err + } + + *locked = haslock + return haslock, nil +} + +// setlkw calls FcntlFlock with cmd for the entire file indicated by fd. +func setlkw(fd uintptr, cmd cmdType, lt lockType) error { + for { + err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{ + Type: int16(lt), + Whence: io.SeekStart, + Start: 0, + Len: 0, // All bytes. + }) + if err != unix.EINTR { + return err + } + } +} diff --git a/flock/flock_example_test.go b/flock/flock_example_test.go new file mode 100644 index 0000000..0c2afa5 --- /dev/null +++ b/flock/flock_example_test.go @@ -0,0 +1,72 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Copyright 2018 The Gofrs. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +package flock_test + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/gofrs/flock" +) + +func ExampleFlock_Locked() { + f := flock.New(os.TempDir() + "/go-lock.lock") + f.TryLock() // unchecked errors here + + fmt.Printf("locked: %v\n", f.Locked()) + + f.Unlock() + + fmt.Printf("locked: %v\n", f.Locked()) + // Output: locked: true + // locked: false +} + +func ExampleFlock_TryLock() { + // should probably put these in /var/lock + fileLock := flock.New(os.TempDir() + "/go-lock.lock") + + locked, err := fileLock.TryLock() + + if err != nil { + // handle locking error + } + + if locked { + fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) + + if err := fileLock.Unlock(); err != nil { + // handle unlock error + } + } + + fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) +} + +func ExampleFlock_TryLockContext() { + // should probably put these in /var/lock + fileLock := flock.New(os.TempDir() + "/go-lock.lock") + + lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + locked, err := fileLock.TryLockContext(lockCtx, 678*time.Millisecond) + + if err != nil { + // handle locking error + } + + if locked { + fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) + + if err := fileLock.Unlock(); err != nil { + // handle unlock error + } + } + + fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) +} diff --git a/flock/flock_internal_test.go b/flock/flock_internal_test.go new file mode 100644 index 0000000..ff2a12a --- /dev/null +++ b/flock/flock_internal_test.go @@ -0,0 +1,30 @@ +package flock + +import ( + "io/ioutil" + "os" + "testing" +) + +func Test(t *testing.T) { + tmpFileFh, err := ioutil.TempFile(os.TempDir(), "go-flock-") + tmpFileFh.Close() + tmpFile := tmpFileFh.Name() + os.Remove(tmpFile) + + lock := New(tmpFile) + locked, err := lock.TryLock() + if locked == false || err != nil { + t.Fatalf("failed to lock: locked: %t, err: %v", locked, err) + } + + newLock := New(tmpFile) + locked, err = newLock.TryLock() + if locked != false || err != nil { + t.Fatalf("should have failed locking: locked: %t, err: %v", locked, err) + } + + if newLock.fh != nil { + t.Fatal("file handle should have been released and be nil") + } +} diff --git a/flock/flock_test.go b/flock/flock_test.go new file mode 100644 index 0000000..abf98d3 --- /dev/null +++ b/flock/flock_test.go @@ -0,0 +1,300 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Copyright 2018 The Gofrs. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +package flock_test + +import ( + "context" + "io/ioutil" + "os" + "runtime" + "testing" + "time" + + "github.com/gofrs/flock" + + . "gopkg.in/check.v1" +) + +type TestSuite struct { + path string + flock *flock.Flock +} + +var _ = Suite(&TestSuite{}) + +func Test(t *testing.T) { TestingT(t) } + +func (t *TestSuite) SetUpTest(c *C) { + tmpFile, err := ioutil.TempFile(os.TempDir(), "go-flock-") + c.Assert(err, IsNil) + c.Assert(tmpFile, Not(IsNil)) + + t.path = tmpFile.Name() + + defer os.Remove(t.path) + tmpFile.Close() + + t.flock = flock.New(t.path) +} + +func (t *TestSuite) TearDownTest(c *C) { + t.flock.Unlock() + os.Remove(t.path) +} + +func (t *TestSuite) TestNew(c *C) { + var f *flock.Flock + + f = flock.New(t.path) + c.Assert(f, Not(IsNil)) + c.Check(f.Path(), Equals, t.path) + c.Check(f.Locked(), Equals, false) + c.Check(f.RLocked(), Equals, false) +} + +func (t *TestSuite) TestFlock_Path(c *C) { + var path string + path = t.flock.Path() + c.Check(path, Equals, t.path) +} + +func (t *TestSuite) TestFlock_Locked(c *C) { + var locked bool + locked = t.flock.Locked() + c.Check(locked, Equals, false) +} + +func (t *TestSuite) TestFlock_RLocked(c *C) { + var locked bool + locked = t.flock.RLocked() + c.Check(locked, Equals, false) +} + +func (t *TestSuite) TestFlock_String(c *C) { + var str string + str = t.flock.String() + c.Assert(str, Equals, t.path) +} + +func (t *TestSuite) TestFlock_TryLock(c *C) { + c.Assert(t.flock.Locked(), Equals, false) + c.Assert(t.flock.RLocked(), Equals, false) + + var locked bool + var err error + + locked, err = t.flock.TryLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, true) + c.Check(t.flock.Locked(), Equals, true) + c.Check(t.flock.RLocked(), Equals, false) + + locked, err = t.flock.TryLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, true) + + // make sure we just return false with no error in cases + // where we would have been blocked + locked, err = flock.New(t.path).TryLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, false) +} + +func (t *TestSuite) TestFlock_TryRLock(c *C) { + c.Assert(t.flock.Locked(), Equals, false) + c.Assert(t.flock.RLocked(), Equals, false) + + var locked bool + var err error + + locked, err = t.flock.TryRLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, true) + c.Check(t.flock.Locked(), Equals, false) + c.Check(t.flock.RLocked(), Equals, true) + + locked, err = t.flock.TryRLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, true) + + // shared lock should not block. + flock2 := flock.New(t.path) + locked, err = flock2.TryRLock() + c.Assert(err, IsNil) + if runtime.GOOS == "aix" { + // When using POSIX locks, we can't safely read-lock the same + // inode through two different descriptors at the same time: + // when the first descriptor is closed, the second descriptor + // would still be open but silently unlocked. So a second + // TryRLock must return false. + c.Check(locked, Equals, false) + } else { + c.Check(locked, Equals, true) + } + + // make sure we just return false with no error in cases + // where we would have been blocked + t.flock.Unlock() + flock2.Unlock() + t.flock.Lock() + locked, err = flock.New(t.path).TryRLock() + c.Assert(err, IsNil) + c.Check(locked, Equals, false) +} + +func (t *TestSuite) TestFlock_TryLockContext(c *C) { + // happy path + ctx, cancel := context.WithCancel(context.Background()) + locked, err := t.flock.TryLockContext(ctx, time.Second) + c.Assert(err, IsNil) + c.Check(locked, Equals, true) + + // context already canceled + cancel() + locked, err = flock.New(t.path).TryLockContext(ctx, time.Second) + c.Assert(err, Equals, context.Canceled) + c.Check(locked, Equals, false) + + // timeout + ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + locked, err = flock.New(t.path).TryLockContext(ctx, time.Second) + c.Assert(err, Equals, context.DeadlineExceeded) + c.Check(locked, Equals, false) +} + +func (t *TestSuite) TestFlock_TryRLockContext(c *C) { + // happy path + ctx, cancel := context.WithCancel(context.Background()) + locked, err := t.flock.TryRLockContext(ctx, time.Second) + c.Assert(err, IsNil) + c.Check(locked, Equals, true) + + // context already canceled + cancel() + locked, err = flock.New(t.path).TryRLockContext(ctx, time.Second) + c.Assert(err, Equals, context.Canceled) + c.Check(locked, Equals, false) + + // timeout + t.flock.Unlock() + t.flock.Lock() + ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) + defer cancel() + locked, err = flock.New(t.path).TryRLockContext(ctx, time.Second) + c.Assert(err, Equals, context.DeadlineExceeded) + c.Check(locked, Equals, false) +} + +func (t *TestSuite) TestFlock_Unlock(c *C) { + var err error + + err = t.flock.Unlock() + c.Assert(err, IsNil) + + // get a lock for us to unlock + locked, err := t.flock.TryLock() + c.Assert(err, IsNil) + c.Assert(locked, Equals, true) + c.Assert(t.flock.Locked(), Equals, true) + c.Check(t.flock.RLocked(), Equals, false) + + _, err = os.Stat(t.path) + c.Assert(os.IsNotExist(err), Equals, false) + + err = t.flock.Unlock() + c.Assert(err, IsNil) + c.Check(t.flock.Locked(), Equals, false) + c.Check(t.flock.RLocked(), Equals, false) +} + +func (t *TestSuite) TestFlock_Lock(c *C) { + c.Assert(t.flock.Locked(), Equals, false) + c.Check(t.flock.RLocked(), Equals, false) + + var err error + + err = t.flock.Lock() + c.Assert(err, IsNil) + c.Check(t.flock.Locked(), Equals, true) + c.Check(t.flock.RLocked(), Equals, false) + + // test that the short-circuit works + err = t.flock.Lock() + c.Assert(err, IsNil) + + // + // Test that Lock() is a blocking call + // + ch := make(chan error, 2) + gf := flock.New(t.path) + defer gf.Unlock() + + go func(ch chan<- error) { + ch <- nil + ch <- gf.Lock() + close(ch) + }(ch) + + errCh, ok := <-ch + c.Assert(ok, Equals, true) + c.Assert(errCh, IsNil) + + err = t.flock.Unlock() + c.Assert(err, IsNil) + + errCh, ok = <-ch + c.Assert(ok, Equals, true) + c.Assert(errCh, IsNil) + c.Check(t.flock.Locked(), Equals, false) + c.Check(t.flock.RLocked(), Equals, false) + c.Check(gf.Locked(), Equals, true) + c.Check(gf.RLocked(), Equals, false) +} + +func (t *TestSuite) TestFlock_RLock(c *C) { + c.Assert(t.flock.Locked(), Equals, false) + c.Check(t.flock.RLocked(), Equals, false) + + var err error + + err = t.flock.RLock() + c.Assert(err, IsNil) + c.Check(t.flock.Locked(), Equals, false) + c.Check(t.flock.RLocked(), Equals, true) + + // test that the short-circuit works + err = t.flock.RLock() + c.Assert(err, IsNil) + + // + // Test that RLock() is a blocking call + // + ch := make(chan error, 2) + gf := flock.New(t.path) + defer gf.Unlock() + + go func(ch chan<- error) { + ch <- nil + ch <- gf.RLock() + close(ch) + }(ch) + + errCh, ok := <-ch + c.Assert(ok, Equals, true) + c.Assert(errCh, IsNil) + + err = t.flock.Unlock() + c.Assert(err, IsNil) + + errCh, ok = <-ch + c.Assert(ok, Equals, true) + c.Assert(errCh, IsNil) + c.Check(t.flock.Locked(), Equals, false) + c.Check(t.flock.RLocked(), Equals, false) + c.Check(gf.Locked(), Equals, false) + c.Check(gf.RLocked(), Equals, true) +} diff --git a/flock/flock_unix.go b/flock/flock_unix.go new file mode 100644 index 0000000..c315a3e --- /dev/null +++ b/flock/flock_unix.go @@ -0,0 +1,197 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// +build !aix,!windows + +package flock + +import ( + "os" + "syscall" +) + +// Lock is a blocking call to try and take an exclusive file lock. It will wait +// until it is able to obtain the exclusive file lock. It's recommended that +// TryLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already exclusive-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +// +// If the *Flock has a shared lock (RLock), this may transparently replace the +// shared lock with an exclusive lock on some UNIX-like operating systems. Be +// careful when using exclusive locks in conjunction with shared locks +// (RLock()), because calling Unlock() may accidentally release the exclusive +// lock that was once a shared lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, syscall.LOCK_EX) +} + +// RLock is a blocking call to try and take a shared file lock. It will wait +// until it is able to obtain the shared file lock. It's recommended that +// TryRLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already shared-locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, syscall.LOCK_SH) +} + +func (f *Flock) lock(locked *bool, flag int) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return err + } + defer f.ensureFhState() + } + + if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil { + shouldRetry, reopenErr := f.reopenFDOnError(err) + if reopenErr != nil { + return reopenErr + } + + if !shouldRetry { + return err + } + + if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil { + return err + } + } + + *locked = true + return nil +} + +// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so +// while it is running the Locked() and RLocked() functions will be blocked. +// +// This function short-circuits if we are unlocked already. If not, it calls +// syscall.LOCK_UN on the file and closes the file descriptor. It does not +// remove the file from disk. It's up to your application to do. +// +// Please note, if your shared lock became an exclusive lock this may +// unintentionally drop the exclusive lock if called by the consumer that +// believes they have a shared lock. Please see Lock() for more details. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.fh == nil { + return nil + } + + // mark the file as unlocked + if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil { + return err + } + + f.fh.Close() + + f.l = false + f.r = false + f.fh = nil + + return nil +} + +// TryLock is the preferred function for taking an exclusive file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. If +// we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, syscall.LOCK_EX) +} + +// TryRLock is the preferred function for taking a shared file lock. This +// function takes an RW-mutex lock before it tries to lock the file, so there is +// the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the shared file +// lock, the function will return false instead of waiting for the lock. If we +// get the lock, we also set the *Flock instance as being share-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, syscall.LOCK_SH) +} + +func (f *Flock) try(locked *bool, flag int) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + defer f.ensureFhState() + } + + var retried bool +retry: + err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB) + + switch err { + case syscall.EWOULDBLOCK: + return false, nil + case nil: + *locked = true + return true, nil + } + if !retried { + if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil { + return false, reopenErr + } else if shouldRetry { + retried = true + goto retry + } + } + + return false, err +} + +// reopenFDOnError determines whether we should reopen the file handle +// in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c: +// Since Linux 3.4 (commit 55725513) +// Probably NFSv4 where flock() is emulated by fcntl(). +func (f *Flock) reopenFDOnError(err error) (bool, error) { + if err != syscall.EIO && err != syscall.EBADF { + return false, nil + } + if st, err := f.fh.Stat(); err == nil { + // if the file is able to be read and written + if st.Mode()&0600 == 0600 { + f.fh.Close() + f.fh = nil + + // reopen in read-write mode and set the filehandle + fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600)) + if err != nil { + return false, err + } + f.fh = fh + return true, nil + } + } + + return false, nil +} diff --git a/flock/flock_winapi.go b/flock/flock_winapi.go new file mode 100644 index 0000000..fe405a2 --- /dev/null +++ b/flock/flock_winapi.go @@ -0,0 +1,76 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +// +build windows + +package flock + +import ( + "syscall" + "unsafe" +) + +var ( + kernel32, _ = syscall.LoadLibrary("kernel32.dll") + procLockFileEx, _ = syscall.GetProcAddress(kernel32, "LockFileEx") + procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx") +) + +const ( + winLockfileFailImmediately = 0x00000001 + winLockfileExclusiveLock = 0x00000002 + winLockfileSharedLock = 0x00000000 +) + +// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows +// `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as: +// +// > The function requests an exclusive lock. Otherwise, it requests a shared +// > lock. +// +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx + +func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { + r1, _, errNo := syscall.Syscall6( + uintptr(procLockFileEx), + 6, + uintptr(handle), + uintptr(flags), + uintptr(reserved), + uintptr(numberOfBytesToLockLow), + uintptr(numberOfBytesToLockHigh), + uintptr(unsafe.Pointer(offset))) + + if r1 != 1 { + if errNo == 0 { + return false, syscall.EINVAL + } + + return false, errNo + } + + return true, 0 +} + +func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { + r1, _, errNo := syscall.Syscall6( + uintptr(procUnlockFileEx), + 5, + uintptr(handle), + uintptr(reserved), + uintptr(numberOfBytesToLockLow), + uintptr(numberOfBytesToLockHigh), + uintptr(unsafe.Pointer(offset)), + 0) + + if r1 != 1 { + if errNo == 0 { + return false, syscall.EINVAL + } + + return false, errNo + } + + return true, 0 +} diff --git a/flock/flock_windows.go b/flock/flock_windows.go new file mode 100644 index 0000000..8637c56 --- /dev/null +++ b/flock/flock_windows.go @@ -0,0 +1,142 @@ +// Copyright 2015 Tim Heckman. All rights reserved. +// Use of this source code is governed by the BSD 3-Clause +// license that can be found in the LICENSE file. + +package flock + +import ( + "syscall" +) + +// ErrorLockViolation is the error code returned from the Windows syscall when a +// lock would block and you ask to fail immediately. +const ErrorLockViolation syscall.Errno = 0x21 // 33 + +// Lock is a blocking call to try and take an exclusive file lock. It will wait +// until it is able to obtain the exclusive file lock. It's recommended that +// TryLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) Lock() error { + return f.lock(&f.l, winLockfileExclusiveLock) +} + +// RLock is a blocking call to try and take a shared file lock. It will wait +// until it is able to obtain the shared file lock. It's recommended that +// TryRLock() be used over this function. This function may block the ability to +// query the current Locked() or RLocked() status due to a RW-mutex lock. +// +// If we are already locked, this function short-circuits and returns +// immediately assuming it can take the mutex lock. +func (f *Flock) RLock() error { + return f.lock(&f.r, winLockfileSharedLock) +} + +func (f *Flock) lock(locked *bool, flag uint32) error { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return nil + } + + if f.Fh == nil { + if err := f.setFh(); err != nil { + return err + } + defer f.ensureFhState() + } + + if _, errNo := lockFileEx(syscall.Handle(f.Fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { + return errNo + } + + *locked = true + return nil +} + +// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so +// while it is running the Locked() and RLocked() functions will be blocked. +// +// This function short-circuits if we are unlocked already. If not, it calls +// UnlockFileEx() on the file and closes the file descriptor. It does not remove +// the file from disk. It's up to your application to do. +func (f *Flock) Unlock() error { + f.m.Lock() + defer f.m.Unlock() + + // if we aren't locked or if the lockfile instance is nil + // just return a nil error because we are unlocked + if (!f.l && !f.r) || f.Fh == nil { + return nil + } + + // mark the file as unlocked + if _, errNo := unlockFileEx(syscall.Handle(f.Fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { + return errNo + } + + f.Fh.Close() + + f.l = false + f.r = false + f.Fh = nil + + return nil +} + +// TryLock is the preferred function for taking an exclusive file lock. This +// function does take a RW-mutex lock before it tries to lock the file, so there +// is the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the exclusive +// file lock, the function will return false instead of waiting for the lock. If +// we get the lock, we also set the *Flock instance as being exclusive-locked. +func (f *Flock) TryLock() (bool, error) { + return f.try(&f.l, winLockfileExclusiveLock) +} + +// TryRLock is the preferred function for taking a shared file lock. This +// function does take a RW-mutex lock before it tries to lock the file, so there +// is the possibility that this function may block for a short time if another +// goroutine is trying to take any action. +// +// The actual file lock is non-blocking. If we are unable to get the shared file +// lock, the function will return false instead of waiting for the lock. If we +// get the lock, we also set the *Flock instance as being shared-locked. +func (f *Flock) TryRLock() (bool, error) { + return f.try(&f.r, winLockfileSharedLock) +} + +func (f *Flock) try(locked *bool, flag uint32) (bool, error) { + f.m.Lock() + defer f.m.Unlock() + + if *locked { + return true, nil + } + + if f.Fh == nil { + if err := f.setFh(); err != nil { + return false, err + } + defer f.ensureFhState() + } + + _, errNo := lockFileEx(syscall.Handle(f.Fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{}) + + if errNo > 0 { + if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING { + return false, nil + } + + return false, errNo + } + + *locked = true + + return true, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2ead327 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module github.com/AzureAD/microsoft-authentication-extensions-for-go + +go 1.14 + +require ( + github.com/AzureAD/microsoft-authentication-library-for-go v0.3.0 + github.com/billgraziano/dpapi v0.4.0 + github.com/gofrs/flock v0.8.1 + +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..241840d --- /dev/null +++ b/go.sum @@ -0,0 +1,197 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= +github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= +github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= +github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +github.com/AzureAD/microsoft-authentication-library-for-go v0.3.0 h1:2iUGNrLWpRaZ8EFbrU6LxwcJuPkBxus5z5BHaBfu0lo= +github.com/AzureAD/microsoft-authentication-library-for-go v0.3.0/go.mod h1:5aEdWF4KzHyRg5i5ItqNcv12iMQr/kYTYaX+GpdEcj8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/billgraziano/dpapi v0.4.0 h1:t39THI1Ld1hkkLVrhkOX6u5TUxwzRddOffq4jcwh2AE= +github.com/billgraziano/dpapi v0.4.0/go.mod h1:gi1Lin0jvovT53j0EXITkY6UPb3hTfI92POaZgj9JBA= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= +github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.0 h1:MSdYClljsF3PbENUUEx85nkWfJSGfzYI9yEBZOJz6CY= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/werf/lockgate v0.0.0-20210423043214-fd4df31c9ab0 h1:ldIhKpFGehs8st/vNFeHyaufEopwED9j4/EPrGtfK8U= +github.com/werf/lockgate v0.0.0-20210423043214-fd4df31c9ab0/go.mod h1:/CeY6KDiBSCU9PUmjt7zGhqpzp8FAPg/wNVfLZHQGWI= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828161417-c663848e9a16 h1:54u1berWyLujz9htI1BHtZpcCEYaYNUSDFLXMNDd7To= +golang.org/x/sys v0.0.0-20200828161417-c663848e9a16/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI= +k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko= +k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= +k8s.io/kube-openapi v0.0.0-20200410145947-61e04a5be9a6/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/internal/cache_accessor.go b/internal/cache_accessor.go new file mode 100644 index 0000000..54d7001 --- /dev/null +++ b/internal/cache_accessor.go @@ -0,0 +1,70 @@ +package internal + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "runtime" + + "github.com/billgraziano/dpapi" +) + +type cacheAccessor interface { + Read() ([]byte, error) + Write(data []byte) + Delete() +} + +type FileAccessor struct { + cacheFilePath string +} + +func NewFileAccessor(cacheFilePath string) *FileAccessor { + return &FileAccessor{cacheFilePath: cacheFilePath} +} + +func (f *FileAccessor) Read() ([]byte, error) { + var data []byte + file, err := os.Open(f.cacheFilePath) + if err != nil { + log.Println(err) + } + defer file.Close() + data, err = ioutil.ReadAll(file) + if err != nil { + log.Println(err) + } + if data != nil && len(data) != 0 && runtime.GOOS == "windows" { + data, err = dpapi.DecryptBytes(data) + if err != nil { + fmt.Println("err from Decrypt: ", err) + } + } + return data, nil +} + +func (f *FileAccessor) Write(data []byte) { + if runtime.GOOS == "windows" { + data, err := dpapi.EncryptBytes(data) + if err != nil { + fmt.Println("Error from Encrypt") + } + err = ioutil.WriteFile(f.cacheFilePath, data, 0600) + if err != nil { + log.Println(err) + } + } else { + f.WriteAtomic(data) + } + +} + +func (f *FileAccessor) WriteAtomic(data []byte) { + // Not implemented yet + return +} + +func (f *FileAccessor) Delete() { + +} diff --git a/internal/mac_persistence.go b/internal/mac_persistence.go new file mode 100644 index 0000000..fba2e0b --- /dev/null +++ b/internal/mac_persistence.go @@ -0,0 +1,5 @@ +package internal + +func placeHolder() error { + return nil +} diff --git a/internal/windows_persistence.go b/internal/windows_persistence.go new file mode 100644 index 0000000..90c082d --- /dev/null +++ b/internal/windows_persistence.go @@ -0,0 +1,34 @@ +package internal + +import ( + "fmt" + + "github.com/billgraziano/dpapi" +) + +func encryptWindows() { + secret := "Hello World!;" + + enc, err := dpapi.Encrypt(secret) + if err != nil { + fmt.Println("err from Encrypt: ", err) + } + fmt.Println(enc) + dec, err := dpapi.Decrypt(enc) + if err != nil { + fmt.Println("err from Decrypt: ", err) + } + fmt.Println(dec) + if dec != secret { + fmt.Printf("expected: '%s' got: '%s'", secret, dec) + } + +} + +func encryptWindowsusingLibraries() { + +} + +func main() { + encryptWindows() +} diff --git a/persistence_aspect.go b/persistence_aspect.go new file mode 100644 index 0000000..511b175 --- /dev/null +++ b/persistence_aspect.go @@ -0,0 +1,51 @@ +package main + +import ( + "log" + + "github.com/AzureAD/microsoft-authentication-extensions-for-go/internal" + "github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache" +) + +type FileTokenCache struct { + lock CrossPlatLock + filename string + lastSeenCacheFileModifiedTimestamp int + fileAccessor internal.FileAccessor +} + +func NewFileCache(file string) *FileTokenCache { + lock, err := NewLock(file, 600) + if err != nil { + log.Print(err) + } + return &FileTokenCache{ + lock: lock, + fileAccessor: *internal.NewFileAccessor("cache_trial.json"), + } +} + +func (t *FileTokenCache) Replace(cache cache.Unmarshaler, key string) { + t.lock.Lock() + + data, err := t.fileAccessor.Read() + + if err != nil { + log.Println(err) + } + err = cache.Unmarshal(data) + if err != nil { + log.Println(err) + } + t.lock.UnLock() +} + +func (t *FileTokenCache) Export(cache cache.Marshaler, key string) { + t.lock.Lock() + data, err := cache.Marshal() + if err != nil { + log.Println(err) + } + t.fileAccessor.Write(data) + t.lock.UnLock() +} From 187858ea5885cc2a7e7a805cf51a1b596eb9d8cf Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Wed, 1 Sep 2021 03:04:43 -0700 Subject: [PATCH 02/17] Changes 1 --- internal/cache_accessor.go | 25 ++------------ internal/windows_persistence.go | 60 +++++++++++++++++++++++++-------- 2 files changed, 49 insertions(+), 36 deletions(-) diff --git a/internal/cache_accessor.go b/internal/cache_accessor.go index 54d7001..5e002d8 100644 --- a/internal/cache_accessor.go +++ b/internal/cache_accessor.go @@ -1,13 +1,9 @@ package internal import ( - "fmt" "io/ioutil" "log" "os" - "runtime" - - "github.com/billgraziano/dpapi" ) type cacheAccessor interface { @@ -35,29 +31,14 @@ func (f *FileAccessor) Read() ([]byte, error) { if err != nil { log.Println(err) } - if data != nil && len(data) != 0 && runtime.GOOS == "windows" { - data, err = dpapi.DecryptBytes(data) - if err != nil { - fmt.Println("err from Decrypt: ", err) - } - } return data, nil } func (f *FileAccessor) Write(data []byte) { - if runtime.GOOS == "windows" { - data, err := dpapi.EncryptBytes(data) - if err != nil { - fmt.Println("Error from Encrypt") - } - err = ioutil.WriteFile(f.cacheFilePath, data, 0600) - if err != nil { - log.Println(err) - } - } else { - f.WriteAtomic(data) + err := ioutil.WriteFile(f.cacheFilePath, data, 0600) + if err != nil { + log.Println(err) } - } func (f *FileAccessor) WriteAtomic(data []byte) { diff --git a/internal/windows_persistence.go b/internal/windows_persistence.go index 90c082d..a5dfef2 100644 --- a/internal/windows_persistence.go +++ b/internal/windows_persistence.go @@ -1,34 +1,66 @@ +// +build windows + package internal import ( "fmt" + "io/ioutil" + "log" + "os" + "runtime" "github.com/billgraziano/dpapi" ) -func encryptWindows() { - secret := "Hello World!;" +type WindowsAccessor struct { + cacheFilePath string +} - enc, err := dpapi.Encrypt(secret) +func NewWindowsAccessor(cacheFilePath string) *WindowsAccessor { + return &WindowsAccessor{cacheFilePath: cacheFilePath} +} + +func (w *WindowsAccessor) Read() ([]byte, error) { + var data []byte + file, err := os.Open(w.cacheFilePath) if err != nil { - fmt.Println("err from Encrypt: ", err) + log.Println(err) } - fmt.Println(enc) - dec, err := dpapi.Decrypt(enc) + defer file.Close() + data, err = ioutil.ReadAll(file) if err != nil { - fmt.Println("err from Decrypt: ", err) + log.Println(err) } - fmt.Println(dec) - if dec != secret { - fmt.Printf("expected: '%s' got: '%s'", secret, dec) + if data != nil && len(data) != 0 && runtime.GOOS == "windows" { + data, err = dpapi.DecryptBytes(data) + if err != nil { + fmt.Println("err from Decrypt: ", err) + } } - + return data, nil } -func encryptWindowsusingLibraries() { +func (w *WindowsAccessor) Write(data []byte) { + if runtime.GOOS == "windows" { + data, err := dpapi.EncryptBytes(data) + if err != nil { + fmt.Println("Error from Encrypt") + } + err = ioutil.WriteFile(w.cacheFilePath, data, 0600) + if err != nil { + log.Println(err) + } + } else { + w.WriteAtomic(data) + } + +} +func (w *WindowsAccessor) WriteAtomic(data []byte) { + // Not implemented yet + return } -func main() { - encryptWindows() +func (w *WindowsAccessor) Delete() { + } From 93a1abd6ec05d7572a824706fc99c19b1c95f302 Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Mon, 13 Sep 2021 06:39:58 -0700 Subject: [PATCH 03/17] More changes --- cross_platform_lock.go | 41 ++++----- cross_platform_lock_test.go | 79 ++++++++++++------ persistence_aspect.go | 51 ------------ persistence_token_cache_access_aspect.go | 102 +++++++++++++++++++++++ 4 files changed, 178 insertions(+), 95 deletions(-) delete mode 100644 persistence_aspect.go create mode 100644 persistence_token_cache_access_aspect.go diff --git a/cross_platform_lock.go b/cross_platform_lock.go index 1ed1e7c..d22a8d4 100644 --- a/cross_platform_lock.go +++ b/cross_platform_lock.go @@ -1,7 +1,9 @@ package main import ( + "errors" "fmt" + "log" "os" "time" @@ -10,7 +12,7 @@ import ( type CrossPlatLock struct { retryNumber int - retryDelayMilliseconds int + retryDelayMilliseconds time.Duration lockFile *os.File @@ -20,10 +22,10 @@ type CrossPlatLock struct { locked bool } -func NewLock(lockFileName string, retryNumber int) (CrossPlatLock, error) { - lockfile, err := os.Create("cache_trial.json.lockfile") +func NewLock(lockFileName string, retryNumber int, retryDelayMilliseconds time.Duration) (CrossPlatLock, error) { + lockfile, err := os.Create(lockFileName) if err != nil { - fmt.Println(err) + log.Println("Error creating cache file", err) } return CrossPlatLock{ lockfileName: lockFileName, @@ -34,29 +36,30 @@ func NewLock(lockFileName string, retryNumber int) (CrossPlatLock, error) { } func (c CrossPlatLock) Lock() error { - - err := c.fileLock.Lock() - if err != nil { - return err - } - if c.fileLock.Locked() { - c.fileLock.Fh.WriteString("Hello \n") + for tryCount := 0; tryCount < c.retryNumber; tryCount++ { + err := c.fileLock.Lock() + if err != nil { + time.Sleep(c.retryDelayMilliseconds * time.Millisecond) + continue + } else { + if c.fileLock.Locked() { + c.fileLock.Fh.WriteString(fmt.Sprintf("{%d} {%s}", os.Getpid(), os.Args[0])) + } + c.locked = true + return nil + } } - c.locked = true - time.Sleep(10) - return nil + return errors.New("Failed to acquire lock") } func (c CrossPlatLock) UnLock() error { if c.fileLock != nil { if err := c.fileLock.Unlock(); err != nil { - fmt.Println("UnLock ", err.Error()) - // handle unlock error + log.Println("Unlock error", err.Error()) } c.lockFile.Close() - var err = os.Remove(c.fileLock.Path()) - if err != nil { - fmt.Println(err.Error()) + if err := os.Remove(c.fileLock.Path()); err != nil { + return err } c.locked = false } diff --git a/cross_platform_lock_test.go b/cross_platform_lock_test.go index 4d0f4a3..e6390b7 100644 --- a/cross_platform_lock_test.go +++ b/cross_platform_lock_test.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "log" "os" @@ -8,28 +9,53 @@ import ( "sync" "testing" "time" + + "github.com/AzureAD/microsoft-authentication-extensions-for-go/internal" ) -func doSomething(i int) { - cacheAccessor := NewFileCache("lock.lock") - cacheAccessor.lock.Lock() - defer cacheAccessor.lock.UnLock() - file, err := os.OpenFile("lockintervals.txt", os.O_APPEND|os.O_WRONLY, 0644) +func spinThreads(noOfThreads int, sleepInterval time.Duration) int { + cacheFile := "cache.txt" + var wg sync.WaitGroup + wg.Add(noOfThreads) + for i := 0; i < noOfThreads; i++ { + go func(i int) { + defer wg.Done() + acquireLockAndWriteToCache(i, sleepInterval, cacheFile) + }(i) + } + wg.Wait() + return validateResult(cacheFile) +} + +func acquireLockAndWriteToCache(threadNo int, sleepInterval time.Duration, cacheFile string) { + cacheAccessor := internal.NewFileAccessor(cacheFile) + lockfileName := cacheFile + ".lockfile" + lock, err := NewLock(lockfileName, 60, 100) + if err := lock.Lock(); err != nil { + log.Println("Couldn't acquire lock", err.Error()) + return + } + defer lock.UnLock() + data, err := cacheAccessor.Read() if err != nil { log.Println(err) } - file.WriteString(fmt.Sprintf("< %d \n", i)) - time.Sleep(1 * time.Second) - file.WriteString(fmt.Sprintf("> %d \n", i)) + var buffer bytes.Buffer + buffer.Write(data) + buffer.WriteString(fmt.Sprintf("< %d \n", threadNo)) + time.Sleep(sleepInterval * time.Millisecond) + buffer.WriteString(fmt.Sprintf("> %d \n", threadNo)) + cacheAccessor.Write(buffer.Bytes()) } -func validateResult() int { + +func validateResult(cacheFile string) int { count := 0 var prevProc string = "" var tag string var proc string - data, err := os.ReadFile("lockintervals.txt") + data, err := os.ReadFile(cacheFile) if err != nil { - fmt.Println(err) + log.Println(err) } dat := string(data) temp := strings.Split(dat, "\n") @@ -55,23 +81,26 @@ func validateResult() int { prevProc = proc } } + if err := os.Remove(cacheFile); err != nil { + log.Println("Failed to remove cache file", err) + } } return count } - -func TestCrossPlatLock(t *testing.T) { - var wg sync.WaitGroup - wg.Add(30) - for i := 0; i < 30; i++ { - go func(i int) { - defer wg.Done() - doSomething(i) - }(i) +func TestForNormalWorkload(t *testing.T) { + noOfThreads := 4 + sleepInterval := 100 + n := spinThreads(noOfThreads, time.Duration(sleepInterval)) + if n != 4*2 { + t.Fatalf("Should not observe starvation") } - wg.Wait() - n := validateResult() - fmt.Println(n) - if n > 60 { - fmt.Println("Should not observe starvation") +} + +func TestForHighWorkload(t *testing.T) { + noOfThreads := 80 + sleepInterval := 100 + n := spinThreads(noOfThreads, time.Duration(sleepInterval)) + if n > 80*2 { + t.Fatalf("Starvation or not, we should not observe garbled payload") } } diff --git a/persistence_aspect.go b/persistence_aspect.go deleted file mode 100644 index 511b175..0000000 --- a/persistence_aspect.go +++ /dev/null @@ -1,51 +0,0 @@ -package main - -import ( - "log" - - "github.com/AzureAD/microsoft-authentication-extensions-for-go/internal" - "github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache" -) - -type FileTokenCache struct { - lock CrossPlatLock - filename string - lastSeenCacheFileModifiedTimestamp int - fileAccessor internal.FileAccessor -} - -func NewFileCache(file string) *FileTokenCache { - lock, err := NewLock(file, 600) - if err != nil { - log.Print(err) - } - return &FileTokenCache{ - lock: lock, - fileAccessor: *internal.NewFileAccessor("cache_trial.json"), - } -} - -func (t *FileTokenCache) Replace(cache cache.Unmarshaler, key string) { - t.lock.Lock() - - data, err := t.fileAccessor.Read() - - if err != nil { - log.Println(err) - } - err = cache.Unmarshal(data) - if err != nil { - log.Println(err) - } - t.lock.UnLock() -} - -func (t *FileTokenCache) Export(cache cache.Marshaler, key string) { - t.lock.Lock() - data, err := cache.Marshal() - if err != nil { - log.Println(err) - } - t.fileAccessor.Write(data) - t.lock.UnLock() -} diff --git a/persistence_token_cache_access_aspect.go b/persistence_token_cache_access_aspect.go new file mode 100644 index 0000000..fc2efd0 --- /dev/null +++ b/persistence_token_cache_access_aspect.go @@ -0,0 +1,102 @@ +package main + +import ( + "log" + "os" + + "github.com/AzureAD/microsoft-authentication-extensions-for-go/internal" + "github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache" +) + +type FileTokenCache struct { + lock CrossPlatLock + filename string + lastSeenCacheFileModifiedTimestamp int + fileAccessor internal.FileAccessor +} + +func NewFileCache(file string) *FileTokenCache { + os.Create(file) + lock, err := NewLock(file+".lock", 60, 100) + if err != nil { + log.Print(err) + } + return &FileTokenCache{ + lock: lock, + filename: file, + fileAccessor: *internal.NewFileAccessor(file), + } +} + +func (t *FileTokenCache) Replace(cache cache.Unmarshaler, key string) { + if err := t.lock.Lock(); err != nil { + log.Println("Couldn't acquire lock", err.Error()) + return + } + defer t.lock.UnLock() + data, err := t.fileAccessor.Read() + + if err != nil { + log.Println(err) + } + err = cache.Unmarshal(data) + if err != nil { + log.Println(err) + } +} + +func (t *FileTokenCache) Export(cache cache.Marshaler, key string) { + if err := t.lock.Lock(); err != nil { + log.Println("Couldn't acquire lock", err.Error()) + return + } + defer t.lock.UnLock() + data, err := cache.Marshal() + if err != nil { + log.Println(err) + } + t.fileAccessor.Write(data) +} + +type WindowsTokenCache struct { + lock CrossPlatLock + filename string + lastSeenCacheFileModifiedTimestamp int + windowsAccessor internal.WindowsAccessor +} + +func NewWindowsCache(file string) *WindowsTokenCache { + lock, err := NewLock(file, 60, 100) + if err != nil { + log.Print(err) + } + return &WindowsTokenCache{ + lock: lock, + windowsAccessor: *internal.NewWindowsAccessor("cache_trial.json"), + } +} + +func (t *WindowsTokenCache) Replace(cache cache.Unmarshaler, key string) { + t.lock.Lock() + + data, err := t.windowsAccessor.Read() + + if err != nil { + log.Println(err) + } + err = cache.Unmarshal(data) + if err != nil { + log.Println(err) + } + t.lock.UnLock() +} + +func (t *WindowsTokenCache) Export(cache cache.Marshaler, key string) { + t.lock.Lock() + data, err := cache.Marshal() + if err != nil { + log.Println(err) + } + t.windowsAccessor.Write(data) + t.lock.UnLock() +} From 43149eb631bec98838817156c4485add8291f537 Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Mon, 13 Sep 2021 06:58:48 -0700 Subject: [PATCH 04/17] Adding test cleanup --- cross_platform_lock_test.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/cross_platform_lock_test.go b/cross_platform_lock_test.go index e6390b7..8f51829 100644 --- a/cross_platform_lock_test.go +++ b/cross_platform_lock_test.go @@ -13,7 +13,7 @@ import ( "github.com/AzureAD/microsoft-authentication-extensions-for-go/internal" ) -func spinThreads(noOfThreads int, sleepInterval time.Duration) int { +func spinThreads(noOfThreads int, sleepInterval time.Duration, t *testing.T) int { cacheFile := "cache.txt" var wg sync.WaitGroup wg.Add(noOfThreads) @@ -24,7 +24,12 @@ func spinThreads(noOfThreads int, sleepInterval time.Duration) int { }(i) } wg.Wait() - return validateResult(cacheFile) + t.Cleanup(func() { + if err := os.Remove(cacheFile); err != nil { + log.Println("Failed to remove cache file", err) + } + }) + return validateResult(cacheFile, t) } func acquireLockAndWriteToCache(threadNo int, sleepInterval time.Duration, cacheFile string) { @@ -48,7 +53,7 @@ func acquireLockAndWriteToCache(threadNo int, sleepInterval time.Duration, cache cacheAccessor.Write(buffer.Bytes()) } -func validateResult(cacheFile string) int { +func validateResult(cacheFile string, t *testing.T) int { count := 0 var prevProc string = "" var tag string @@ -67,30 +72,27 @@ func validateResult(cacheFile string) int { proc = split[1] if prevProc != "" { if proc != prevProc { - fmt.Println("Process overlap found") + t.Fatal("Process overlap found") } if tag != ">" { - fmt.Println("Process overlap found 1") + t.Fatal("Process overlap found") } prevProc = "" } else { if tag != "<" { - fmt.Println("Opening bracket not found") + t.Fatal("Opening bracket not found") } prevProc = proc } } - if err := os.Remove(cacheFile); err != nil { - log.Println("Failed to remove cache file", err) - } } return count } func TestForNormalWorkload(t *testing.T) { noOfThreads := 4 sleepInterval := 100 - n := spinThreads(noOfThreads, time.Duration(sleepInterval)) + n := spinThreads(noOfThreads, time.Duration(sleepInterval), t) if n != 4*2 { t.Fatalf("Should not observe starvation") } @@ -99,7 +101,7 @@ func TestForNormalWorkload(t *testing.T) { func TestForHighWorkload(t *testing.T) { noOfThreads := 80 sleepInterval := 100 - n := spinThreads(noOfThreads, time.Duration(sleepInterval)) + n := spinThreads(noOfThreads, time.Duration(sleepInterval), t) if n > 80*2 { t.Fatalf("Starvation or not, we should not observe garbled payload") } From cbd3859be957e4a82a3279c817182731d9918c70 Mon Sep 17 00:00:00 2001 From: abhidnya13 Date: Wed, 13 Oct 2021 12:52:58 -0700 Subject: [PATCH 05/17] Error handling changes --- cross_platform_lock.go | 5 ++-- internal/cache_accessor.go | 11 ++++---- internal/windows_persistence.go | 29 ++++++++----------- persistence_token_cache_access_aspect.go | 36 +++++++++++------------- 4 files changed, 36 insertions(+), 45 deletions(-) diff --git a/cross_platform_lock.go b/cross_platform_lock.go index d22a8d4..1537209 100644 --- a/cross_platform_lock.go +++ b/cross_platform_lock.go @@ -3,7 +3,6 @@ package main import ( "errors" "fmt" - "log" "os" "time" @@ -25,7 +24,7 @@ type CrossPlatLock struct { func NewLock(lockFileName string, retryNumber int, retryDelayMilliseconds time.Duration) (CrossPlatLock, error) { lockfile, err := os.Create(lockFileName) if err != nil { - log.Println("Error creating cache file", err) + return CrossPlatLock{}, err } return CrossPlatLock{ lockfileName: lockFileName, @@ -55,7 +54,7 @@ func (c CrossPlatLock) Lock() error { func (c CrossPlatLock) UnLock() error { if c.fileLock != nil { if err := c.fileLock.Unlock(); err != nil { - log.Println("Unlock error", err.Error()) + return err } c.lockFile.Close() if err := os.Remove(c.fileLock.Path()); err != nil { diff --git a/internal/cache_accessor.go b/internal/cache_accessor.go index 5e002d8..ed5195b 100644 --- a/internal/cache_accessor.go +++ b/internal/cache_accessor.go @@ -2,7 +2,6 @@ package internal import ( "io/ioutil" - "log" "os" ) @@ -24,26 +23,26 @@ func (f *FileAccessor) Read() ([]byte, error) { var data []byte file, err := os.Open(f.cacheFilePath) if err != nil { - log.Println(err) + return nil, err } defer file.Close() data, err = ioutil.ReadAll(file) if err != nil { - log.Println(err) + return nil, err } return data, nil } -func (f *FileAccessor) Write(data []byte) { +func (f *FileAccessor) Write(data []byte) error { err := ioutil.WriteFile(f.cacheFilePath, data, 0600) if err != nil { - log.Println(err) + return err } + return nil } func (f *FileAccessor) WriteAtomic(data []byte) { // Not implemented yet - return } func (f *FileAccessor) Delete() { diff --git a/internal/windows_persistence.go b/internal/windows_persistence.go index a5dfef2..6339c7b 100644 --- a/internal/windows_persistence.go +++ b/internal/windows_persistence.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows package internal @@ -24,41 +25,35 @@ func (w *WindowsAccessor) Read() ([]byte, error) { var data []byte file, err := os.Open(w.cacheFilePath) if err != nil { - log.Println(err) + nil, err } defer file.Close() data, err = ioutil.ReadAll(file) if err != nil { - log.Println(err) + nil, err } if data != nil && len(data) != 0 && runtime.GOOS == "windows" { data, err = dpapi.DecryptBytes(data) if err != nil { - fmt.Println("err from Decrypt: ", err) + nil, err } } return data, nil } -func (w *WindowsAccessor) Write(data []byte) { - if runtime.GOOS == "windows" { - data, err := dpapi.EncryptBytes(data) - if err != nil { - fmt.Println("Error from Encrypt") - } - err = ioutil.WriteFile(w.cacheFilePath, data, 0600) - if err != nil { - log.Println(err) - } - } else { - w.WriteAtomic(data) +func (w *WindowsAccessor) Write(data []byte) error { + data, err := dpapi.EncryptBytes(data) + if err != nil { + return err + } + err = ioutil.WriteFile(w.cacheFilePath, data, 0600) + if err != nil { + return err } - } func (w *WindowsAccessor) WriteAtomic(data []byte) { // Not implemented yet - return } func (w *WindowsAccessor) Delete() { diff --git a/persistence_token_cache_access_aspect.go b/persistence_token_cache_access_aspect.go index fc2efd0..865d88d 100644 --- a/persistence_token_cache_access_aspect.go +++ b/persistence_token_cache_access_aspect.go @@ -1,3 +1,6 @@ +//go:build windows +// +build windows + package main import ( @@ -19,7 +22,7 @@ func NewFileCache(file string) *FileTokenCache { os.Create(file) lock, err := NewLock(file+".lock", 60, 100) if err != nil { - log.Print(err) + return &FileTokenCache{} } return &FileTokenCache{ lock: lock, @@ -28,32 +31,29 @@ func NewFileCache(file string) *FileTokenCache { } } -func (t *FileTokenCache) Replace(cache cache.Unmarshaler, key string) { +func (t *FileTokenCache) Replace(cache cache.Unmarshaler, key string) error { if err := t.lock.Lock(); err != nil { - log.Println("Couldn't acquire lock", err.Error()) - return + return err } defer t.lock.UnLock() data, err := t.fileAccessor.Read() - if err != nil { - log.Println(err) + return err } err = cache.Unmarshal(data) if err != nil { - log.Println(err) + return err } } -func (t *FileTokenCache) Export(cache cache.Marshaler, key string) { +func (t *FileTokenCache) Export(cache cache.Marshaler, key string) error { if err := t.lock.Lock(); err != nil { - log.Println("Couldn't acquire lock", err.Error()) - return + return err } defer t.lock.UnLock() data, err := cache.Marshal() if err != nil { - log.Println(err) + return err } t.fileAccessor.Write(data) } @@ -68,7 +68,7 @@ type WindowsTokenCache struct { func NewWindowsCache(file string) *WindowsTokenCache { lock, err := NewLock(file, 60, 100) if err != nil { - log.Print(err) + return &WindowsTokenCache{} } return &WindowsTokenCache{ lock: lock, @@ -76,26 +76,24 @@ func NewWindowsCache(file string) *WindowsTokenCache { } } -func (t *WindowsTokenCache) Replace(cache cache.Unmarshaler, key string) { +func (t *WindowsTokenCache) Replace(cache cache.Unmarshaler, key string) error { t.lock.Lock() - data, err := t.windowsAccessor.Read() - if err != nil { - log.Println(err) + return err } err = cache.Unmarshal(data) if err != nil { - log.Println(err) + return err } t.lock.UnLock() } -func (t *WindowsTokenCache) Export(cache cache.Marshaler, key string) { +func (t *WindowsTokenCache) Export(cache cache.Marshaler, key string) error { t.lock.Lock() data, err := cache.Marshal() if err != nil { - log.Println(err) + return err } t.windowsAccessor.Write(data) t.lock.UnLock() From b231fb4b4c6585643ee91a98b8ea3c7524c7ac50 Mon Sep 17 00:00:00 2001 From: abhidnya13 Date: Thu, 14 Oct 2021 10:02:05 -0700 Subject: [PATCH 06/17] Adding modified time --- persistence_token_cache_access_aspect.go | 72 +++++++++++++++++------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/persistence_token_cache_access_aspect.go b/persistence_token_cache_access_aspect.go index 865d88d..c0d636c 100644 --- a/persistence_token_cache_access_aspect.go +++ b/persistence_token_cache_access_aspect.go @@ -6,6 +6,7 @@ package main import ( "log" "os" + "path/filepath" "github.com/AzureAD/microsoft-authentication-extensions-for-go/internal" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache" @@ -25,24 +26,32 @@ func NewFileCache(file string) *FileTokenCache { return &FileTokenCache{} } return &FileTokenCache{ - lock: lock, - filename: file, - fileAccessor: *internal.NewFileAccessor(file), + lock: lock, + filename: file, + fileAccessor: *internal.NewFileAccessor(file), + lastSeenCacheFileModifiedTimestamp: time.Time{}, } } func (t *FileTokenCache) Replace(cache cache.Unmarshaler, key string) error { - if err := t.lock.Lock(); err != nil { - return err - } - defer t.lock.UnLock() - data, err := t.fileAccessor.Read() + info, err := os.Stat(t.filename) if err != nil { return err } - err = cache.Unmarshal(data) - if err != nil { - return err + currentCacheFileModifiedTime := info.ModTime() + if currentCacheFileModifiedTime != t.lastSeenCacheFileModifiedTimestamp { + if err := t.lock.Lock(); err != nil { + return err + } + defer t.lock.UnLock() + data, err := t.fileAccessor.Read() + if err != nil { + return err + } + err = cache.Unmarshal(data) + if err != nil { + return err + } } } @@ -56,6 +65,11 @@ func (t *FileTokenCache) Export(cache cache.Marshaler, key string) error { return err } t.fileAccessor.Write(data) + info, err := os.Stat(t.filename) + if err != nil { + return err + } + t.lastSeenCacheFileModifiedTimestamp := info.ModTime() } type WindowsTokenCache struct { @@ -66,35 +80,51 @@ type WindowsTokenCache struct { } func NewWindowsCache(file string) *WindowsTokenCache { - lock, err := NewLock(file, 60, 100) + os.Create(file) + lock, err := NewLock(file+".lock", 60, 100) if err != nil { - return &WindowsTokenCache{} + return &WindowsTokenCache } return &WindowsTokenCache{ lock: lock, - windowsAccessor: *internal.NewWindowsAccessor("cache_trial.json"), + filename: file, + windowsAccessor: *internal.NewWindowsAccessor(filepath), + lastSeenCacheFileModifiedTimestamp: time.Time{}, } } func (t *WindowsTokenCache) Replace(cache cache.Unmarshaler, key string) error { - t.lock.Lock() - data, err := t.windowsAccessor.Read() + info, err := os.Stat(t.filename) if err != nil { return err } - err = cache.Unmarshal(data) - if err != nil { - return err + currentCacheFileModifiedTime := info.ModTime() + if currentCacheFileModifiedTime != t.lastSeenCacheFileModifiedTimestamp { + t.lock.Lock() + defer t.lock.UnLock() + data, err := t.windowsAccessor.Read() + if err != nil { + return err + } + err = cache.Unmarshal(data) + if err != nil { + return err + } } - t.lock.UnLock() } func (t *WindowsTokenCache) Export(cache cache.Marshaler, key string) error { t.lock.Lock() + defer t.lock.UnLock() data, err := cache.Marshal() if err != nil { return err } t.windowsAccessor.Write(data) - t.lock.UnLock() + + info, err := os.Stat(t.filename) + if err != nil { + return err + } + t.lastSeenCacheFileModifiedTimestamp := info.ModTime() } From 54f1c22843a95a3276858f758157fa2d39a7e4f0 Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Thu, 14 Oct 2021 10:15:38 -0700 Subject: [PATCH 07/17] Cleanup --- internal/windows_persistence.go | 9 ++-- persistence_token_cache_access_aspect.go | 54 ++++++++++++------------ 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/internal/windows_persistence.go b/internal/windows_persistence.go index 6339c7b..3980a0e 100644 --- a/internal/windows_persistence.go +++ b/internal/windows_persistence.go @@ -4,9 +4,7 @@ package internal import ( - "fmt" "io/ioutil" - "log" "os" "runtime" @@ -25,17 +23,17 @@ func (w *WindowsAccessor) Read() ([]byte, error) { var data []byte file, err := os.Open(w.cacheFilePath) if err != nil { - nil, err + return nil, err } defer file.Close() data, err = ioutil.ReadAll(file) if err != nil { - nil, err + return nil, err } if data != nil && len(data) != 0 && runtime.GOOS == "windows" { data, err = dpapi.DecryptBytes(data) if err != nil { - nil, err + return nil, err } } return data, nil @@ -50,6 +48,7 @@ func (w *WindowsAccessor) Write(data []byte) error { if err != nil { return err } + return nil } func (w *WindowsAccessor) WriteAtomic(data []byte) { diff --git a/persistence_token_cache_access_aspect.go b/persistence_token_cache_access_aspect.go index c0d636c..92cab3f 100644 --- a/persistence_token_cache_access_aspect.go +++ b/persistence_token_cache_access_aspect.go @@ -6,7 +6,7 @@ package main import ( "log" "os" - "path/filepath" + "time" "github.com/AzureAD/microsoft-authentication-extensions-for-go/internal" "github.com/AzureAD/microsoft-authentication-library-for-go/apps/cache" @@ -15,7 +15,7 @@ import ( type FileTokenCache struct { lock CrossPlatLock filename string - lastSeenCacheFileModifiedTimestamp int + lastSeenCacheFileModifiedTimestamp time.Time fileAccessor internal.FileAccessor } @@ -33,49 +33,49 @@ func NewFileCache(file string) *FileTokenCache { } } -func (t *FileTokenCache) Replace(cache cache.Unmarshaler, key string) error { +func (t *FileTokenCache) Replace(cache cache.Unmarshaler, key string) { info, err := os.Stat(t.filename) if err != nil { - return err + log.Println(err) } currentCacheFileModifiedTime := info.ModTime() if currentCacheFileModifiedTime != t.lastSeenCacheFileModifiedTimestamp { if err := t.lock.Lock(); err != nil { - return err + log.Println(err) } defer t.lock.UnLock() data, err := t.fileAccessor.Read() if err != nil { - return err + log.Println(err) } err = cache.Unmarshal(data) if err != nil { - return err + log.Println(err) } } } -func (t *FileTokenCache) Export(cache cache.Marshaler, key string) error { +func (t *FileTokenCache) Export(cache cache.Marshaler, key string) { if err := t.lock.Lock(); err != nil { - return err + log.Println(err) } defer t.lock.UnLock() data, err := cache.Marshal() if err != nil { - return err + log.Println(err) } t.fileAccessor.Write(data) info, err := os.Stat(t.filename) if err != nil { - return err + log.Println(err) } - t.lastSeenCacheFileModifiedTimestamp := info.ModTime() + t.lastSeenCacheFileModifiedTimestamp = info.ModTime() } type WindowsTokenCache struct { lock CrossPlatLock filename string - lastSeenCacheFileModifiedTimestamp int + lastSeenCacheFileModifiedTimestamp time.Time windowsAccessor internal.WindowsAccessor } @@ -83,48 +83,48 @@ func NewWindowsCache(file string) *WindowsTokenCache { os.Create(file) lock, err := NewLock(file+".lock", 60, 100) if err != nil { - return &WindowsTokenCache + return &WindowsTokenCache{} } return &WindowsTokenCache{ - lock: lock, - filename: file, - windowsAccessor: *internal.NewWindowsAccessor(filepath), + lock: lock, + filename: file, + windowsAccessor: *internal.NewWindowsAccessor(file), lastSeenCacheFileModifiedTimestamp: time.Time{}, } } -func (t *WindowsTokenCache) Replace(cache cache.Unmarshaler, key string) error { +func (t *WindowsTokenCache) Replace(cache cache.Unmarshaler, key string) { info, err := os.Stat(t.filename) if err != nil { - return err + log.Println(err) } currentCacheFileModifiedTime := info.ModTime() - if currentCacheFileModifiedTime != t.lastSeenCacheFileModifiedTimestamp { + if currentCacheFileModifiedTime != t.lastSeenCacheFileModifiedTimestamp { t.lock.Lock() defer t.lock.UnLock() data, err := t.windowsAccessor.Read() if err != nil { - return err + log.Println(err) } err = cache.Unmarshal(data) if err != nil { - return err + log.Println(err) } } } -func (t *WindowsTokenCache) Export(cache cache.Marshaler, key string) error { +func (t *WindowsTokenCache) Export(cache cache.Marshaler, key string) { t.lock.Lock() defer t.lock.UnLock() data, err := cache.Marshal() if err != nil { - return err + log.Println(err) } t.windowsAccessor.Write(data) - + info, err := os.Stat(t.filename) if err != nil { - return err + log.Println(err) } - t.lastSeenCacheFileModifiedTimestamp := info.ModTime() + t.lastSeenCacheFileModifiedTimestamp = info.ModTime() } From 15852ec4e05913863cbbee270447c648f43e070a Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 00:11:58 -0700 Subject: [PATCH 08/17] PR review 1 --- cross_platform_lock.go | 66 ---------------- extensions/lock/lock.go | 78 +++++++++++++++++++ .../lock/lock_test.go | 8 +- 3 files changed, 82 insertions(+), 70 deletions(-) delete mode 100644 cross_platform_lock.go create mode 100644 extensions/lock/lock.go rename cross_platform_lock_test.go => extensions/lock/lock_test.go (94%) diff --git a/cross_platform_lock.go b/cross_platform_lock.go deleted file mode 100644 index 1537209..0000000 --- a/cross_platform_lock.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "os" - "time" - - "github.com/AzureAD/microsoft-authentication-extensions-for-go/flock" -) - -type CrossPlatLock struct { - retryNumber int - retryDelayMilliseconds time.Duration - - lockFile *os.File - - fileLock *flock.Flock - - lockfileName string - locked bool -} - -func NewLock(lockFileName string, retryNumber int, retryDelayMilliseconds time.Duration) (CrossPlatLock, error) { - lockfile, err := os.Create(lockFileName) - if err != nil { - return CrossPlatLock{}, err - } - return CrossPlatLock{ - lockfileName: lockFileName, - retryNumber: retryNumber, - lockFile: lockfile, - fileLock: flock.New(lockfile.Name()), - }, nil -} - -func (c CrossPlatLock) Lock() error { - for tryCount := 0; tryCount < c.retryNumber; tryCount++ { - err := c.fileLock.Lock() - if err != nil { - time.Sleep(c.retryDelayMilliseconds * time.Millisecond) - continue - } else { - if c.fileLock.Locked() { - c.fileLock.Fh.WriteString(fmt.Sprintf("{%d} {%s}", os.Getpid(), os.Args[0])) - } - c.locked = true - return nil - } - } - return errors.New("Failed to acquire lock") -} - -func (c CrossPlatLock) UnLock() error { - if c.fileLock != nil { - if err := c.fileLock.Unlock(); err != nil { - return err - } - c.lockFile.Close() - if err := os.Remove(c.fileLock.Path()); err != nil { - return err - } - c.locked = false - } - return nil -} diff --git a/extensions/lock/lock.go b/extensions/lock/lock.go new file mode 100644 index 0000000..0ec8651 --- /dev/null +++ b/extensions/lock/lock.go @@ -0,0 +1,78 @@ +package lock + +import ( + "errors" + "fmt" + "os" + "sync" + "time" + + "github.com/AzureAD/microsoft-authentication-extensions-for-go/flock" +) + +type Lock struct { + retries int + retryDelay time.Duration + + lockFile *os.File + + fLock *flock.Flock + mu sync.Mutex +} + +type Option func(l *Lock) + +func WithRetries(n int) Option { + return func(l *Lock) { + l.retries = n + } +} +func WithRetryDelay(t time.Duration) Option { + return func(l *Lock) { + l.retryDelay = t + } +} + +func New(lockFileName string, options ...Option) (*Lock, error) { + l := &Lock{} + for _, o := range options { + o(l) + } + lockfile, err := os.Create(lockFileName) + if err != nil { + return &Lock{}, err + } + l.fLock = flock.New(lockfile.Name()) + return l, nil +} + +func (l *Lock) Lock() error { + l.mu.Lock() + defer l.mu.Unlock() + for tryCount := 0; tryCount < l.retries; tryCount++ { + err := l.fLock.Lock() + if err != nil { + time.Sleep(l.retryDelay * time.Millisecond) + continue + } else { + if l.fLock.Locked() { + l.fLock.Fh.WriteString(fmt.Sprintf("{%d} {%s}", os.Getpid(), os.Args[0])) + } + return nil + } + } + return errors.New("failed to acquire lock") +} + +func (l *Lock) UnLock() error { + if l.fLock != nil { + if err := l.fLock.Unlock(); err != nil { + return err + } + l.lockFile.Close() + if err := os.Remove(l.fLock.Path()); err != nil { + return err + } + } + return nil +} diff --git a/cross_platform_lock_test.go b/extensions/lock/lock_test.go similarity index 94% rename from cross_platform_lock_test.go rename to extensions/lock/lock_test.go index 8f51829..6a32cb3 100644 --- a/cross_platform_lock_test.go +++ b/extensions/lock/lock_test.go @@ -1,4 +1,4 @@ -package main +package lock import ( "bytes" @@ -35,12 +35,12 @@ func spinThreads(noOfThreads int, sleepInterval time.Duration, t *testing.T) int func acquireLockAndWriteToCache(threadNo int, sleepInterval time.Duration, cacheFile string) { cacheAccessor := internal.NewFileAccessor(cacheFile) lockfileName := cacheFile + ".lockfile" - lock, err := NewLock(lockfileName, 60, 100) - if err := lock.Lock(); err != nil { + l, err := New(lockfileName, WithRetries(60), WithRetryDelay(100)) + if err := l.Lock(); err != nil { log.Println("Couldn't acquire lock", err.Error()) return } - defer lock.UnLock() + defer l.UnLock() data, err := cacheAccessor.Read() if err != nil { log.Println(err) From f973d8835e994f2b6389f08fef616dac630dbb34 Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 00:15:41 -0700 Subject: [PATCH 09/17] Modifying git ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 66fd13c..849faab 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ # Dependency directories (remove the comment below to include it) # vendor/ +.vscode/ \ No newline at end of file From e32eddf8c88cc421567607e4f4b584a9e37c3abc Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 00:18:14 -0700 Subject: [PATCH 10/17] Removing vscode folder --- .gitignore | 2 +- .vscode/launch.json | 15 --------------- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index 849faab..e3d7614 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,4 @@ # Dependency directories (remove the comment below to include it) # vendor/ -.vscode/ \ No newline at end of file +.vscode/ diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 608d3c6..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch Package", - "type": "go", - "request": "launch", - "mode": "auto", - "program": "${fileDirname}" - } - ] -} \ No newline at end of file From d139ff0d896d18698f9d437d4bea4276d6c3253b Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 00:20:04 -0700 Subject: [PATCH 11/17] Removing flock from this PR --- flock/.gitignore | 24 --- flock/.travis.yml | 10 -- flock/LICENSE | 27 ---- flock/README.md | 41 ----- flock/appveyor.yml | 25 --- flock/flock.go | 144 ----------------- flock/flock_aix.go | 281 -------------------------------- flock/flock_example_test.go | 72 --------- flock/flock_internal_test.go | 30 ---- flock/flock_test.go | 300 ----------------------------------- flock/flock_unix.go | 197 ----------------------- flock/flock_winapi.go | 76 --------- flock/flock_windows.go | 142 ----------------- 13 files changed, 1369 deletions(-) delete mode 100644 flock/.gitignore delete mode 100644 flock/.travis.yml delete mode 100644 flock/LICENSE delete mode 100644 flock/README.md delete mode 100644 flock/appveyor.yml delete mode 100644 flock/flock.go delete mode 100644 flock/flock_aix.go delete mode 100644 flock/flock_example_test.go delete mode 100644 flock/flock_internal_test.go delete mode 100644 flock/flock_test.go delete mode 100644 flock/flock_unix.go delete mode 100644 flock/flock_winapi.go delete mode 100644 flock/flock_windows.go diff --git a/flock/.gitignore b/flock/.gitignore deleted file mode 100644 index daf913b..0000000 --- a/flock/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.prof diff --git a/flock/.travis.yml b/flock/.travis.yml deleted file mode 100644 index b16d040..0000000 --- a/flock/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: go -go: - - 1.14.x - - 1.15.x -script: go test -v -check.vv -race ./... -sudo: false -notifications: - email: - on_success: never - on_failure: always diff --git a/flock/LICENSE b/flock/LICENSE deleted file mode 100644 index 8b8ff36..0000000 --- a/flock/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2015-2020, Tim Heckman -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of gofrs nor the names of its contributors may be used - to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/flock/README.md b/flock/README.md deleted file mode 100644 index 71ce636..0000000 --- a/flock/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# flock -[![TravisCI Build Status](https://img.shields.io/travis/gofrs/flock/master.svg?style=flat)](https://travis-ci.org/gofrs/flock) -[![GoDoc](https://img.shields.io/badge/godoc-flock-blue.svg?style=flat)](https://godoc.org/github.com/gofrs/flock) -[![License](https://img.shields.io/badge/license-BSD_3--Clause-brightgreen.svg?style=flat)](https://github.com/gofrs/flock/blob/master/LICENSE) -[![Go Report Card](https://goreportcard.com/badge/github.com/gofrs/flock)](https://goreportcard.com/report/github.com/gofrs/flock) - -`flock` implements a thread-safe sync.Locker interface for file locking. It also -includes a non-blocking TryLock() function to allow locking without blocking execution. - -## License -`flock` is released under the BSD 3-Clause License. See the `LICENSE` file for more details. - -## Go Compatibility -This package makes use of the `context` package that was introduced in Go 1.7. As such, this -package has an implicit dependency on Go 1.7+. - -## Installation -``` -go get -u github.com/gofrs/flock -``` - -## Usage -```Go -import "github.com/gofrs/flock" - -fileLock := flock.New("/var/lock/go-lock.lock") - -locked, err := fileLock.TryLock() - -if err != nil { - // handle locking error -} - -if locked { - // do work - fileLock.Unlock() -} -``` - -For more detailed usage information take a look at the package API docs on -[GoDoc](https://godoc.org/github.com/gofrs/flock). diff --git a/flock/appveyor.yml b/flock/appveyor.yml deleted file mode 100644 index 909b4bf..0000000 --- a/flock/appveyor.yml +++ /dev/null @@ -1,25 +0,0 @@ -version: '{build}' - -build: false -deploy: false - -clone_folder: 'c:\gopath\src\github.com\gofrs\flock' - -environment: - GOPATH: 'c:\gopath' - GOVERSION: '1.15' - -init: - - git config --global core.autocrlf input - -install: - - rmdir c:\go /s /q - - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi - - msiexec /i go%GOVERSION%.windows-amd64.msi /q - - set Path=c:\go\bin;c:\gopath\bin;%Path% - - go version - - go env - -test_script: - - go get -t ./... - - go test -race -v ./... diff --git a/flock/flock.go b/flock/flock.go deleted file mode 100644 index f730294..0000000 --- a/flock/flock.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -// Package flock implements a thread-safe interface for file locking. -// It also includes a non-blocking TryLock() function to allow locking -// without blocking execution. -// -// Package flock is released under the BSD 3-Clause License. See the LICENSE file -// for more details. -// -// While using this library, remember that the locking behaviors are not -// guaranteed to be the same on each platform. For example, some UNIX-like -// operating systems will transparently convert a shared lock to an exclusive -// lock. If you Unlock() the flock from a location where you believe that you -// have the shared lock, you may accidentally drop the exclusive lock. -package flock - -import ( - "context" - "os" - "runtime" - "sync" - "time" -) - -// Flock is the struct type to handle file locking. All fields are unexported, -// with access to some of the fields provided by getter methods (Path() and Locked()). -type Flock struct { - path string - m sync.RWMutex - Fh *os.File - l bool - r bool -} - -// New returns a new instance of *Flock. The only parameter -// it takes is the path to the desired lockfile. -func New(path string) *Flock { - return &Flock{path: path} -} - -// NewFlock returns a new instance of *Flock. The only parameter -// it takes is the path to the desired lockfile. -// -// Deprecated: Use New instead. -func NewFlock(path string) *Flock { - return New(path) -} - -// Close is equivalent to calling Unlock. -// -// This will release the lock and close the underlying file descriptor. -// It will not remove the file from disk, that's up to your application. -func (f *Flock) Close() error { - return f.Unlock() -} - -// Path returns the path as provided in NewFlock(). -func (f *Flock) Path() string { - return f.path -} - -// Locked returns the lock state (locked: true, unlocked: false). -// -// Warning: by the time you use the returned value, the state may have changed. -func (f *Flock) Locked() bool { - f.m.RLock() - defer f.m.RUnlock() - return f.l -} - -// RLocked returns the read lock state (locked: true, unlocked: false). -// -// Warning: by the time you use the returned value, the state may have changed. -func (f *Flock) RLocked() bool { - f.m.RLock() - defer f.m.RUnlock() - return f.r -} - -func (f *Flock) String() string { - return f.path -} - -// TryLockContext repeatedly tries to take an exclusive lock until one of the -// conditions is met: TryLock succeeds, TryLock fails with error, or Context -// Done channel is closed. -func (f *Flock) TryLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { - return tryCtx(ctx, f.TryLock, retryDelay) -} - -// TryRLockContext repeatedly tries to take a shared lock until one of the -// conditions is met: TryRLock succeeds, TryRLock fails with error, or Context -// Done channel is closed. -func (f *Flock) TryRLockContext(ctx context.Context, retryDelay time.Duration) (bool, error) { - return tryCtx(ctx, f.TryRLock, retryDelay) -} - -func tryCtx(ctx context.Context, fn func() (bool, error), retryDelay time.Duration) (bool, error) { - if ctx.Err() != nil { - return false, ctx.Err() - } - for { - if ok, err := fn(); ok || err != nil { - return ok, err - } - select { - case <-ctx.Done(): - return false, ctx.Err() - case <-time.After(retryDelay): - // try again - } - } -} - -func (f *Flock) setFh() error { - // open a new os.File instance - // create it if it doesn't exist, and open the file read-only. - flags := os.O_CREATE - if runtime.GOOS == "aix" { - // AIX cannot preform write-lock (ie exclusive) on a - // read-only file. - flags |= os.O_RDWR - } else { - flags |= os.O_RDONLY - } - fh, err := os.OpenFile(f.path, flags, os.FileMode(0600)) - if err != nil { - return err - } - - // set the filehandle on the struct - f.Fh = fh - return nil -} - -// ensure the file handle is closed if no lock is held -func (f *Flock) ensureFhState() { - if !f.l && !f.r && f.Fh != nil { - f.Fh.Close() - f.Fh = nil - } -} diff --git a/flock/flock_aix.go b/flock/flock_aix.go deleted file mode 100644 index 7277c1b..0000000 --- a/flock/flock_aix.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2019 Tim Heckman. All rights reserved. Use of this source code is -// governed by the BSD 3-Clause license that can be found in the LICENSE file. - -// Copyright 2018 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This code implements the filelock API using POSIX 'fcntl' locks, which attach -// to an (inode, process) pair rather than a file descriptor. To avoid unlocking -// files prematurely when the same file is opened through different descriptors, -// we allow only one read-lock at a time. -// -// This code is adapted from the Go package: -// cmd/go/internal/lockedfile/internal/filelock - -//+build aix - -package flock - -import ( - "errors" - "io" - "os" - "sync" - "syscall" - - "golang.org/x/sys/unix" -) - -type lockType int16 - -const ( - readLock lockType = unix.F_RDLCK - writeLock lockType = unix.F_WRLCK -) - -type cmdType int - -const ( - tryLock cmdType = unix.F_SETLK - waitLock cmdType = unix.F_SETLKW -) - -type inode = uint64 - -type inodeLock struct { - owner *Flock - queue []<-chan *Flock -} - -var ( - mu sync.Mutex - inodes = map[*Flock]inode{} - locks = map[inode]inodeLock{} -) - -// Lock is a blocking call to try and take an exclusive file lock. It will wait -// until it is able to obtain the exclusive file lock. It's recommended that -// TryLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already exclusive-locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -// -// If the *Flock has a shared lock (RLock), this may transparently replace the -// shared lock with an exclusive lock on some UNIX-like operating systems. Be -// careful when using exclusive locks in conjunction with shared locks -// (RLock()), because calling Unlock() may accidentally release the exclusive -// lock that was once a shared lock. -func (f *Flock) Lock() error { - return f.lock(&f.l, writeLock) -} - -// RLock is a blocking call to try and take a shared file lock. It will wait -// until it is able to obtain the shared file lock. It's recommended that -// TryRLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already shared-locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -func (f *Flock) RLock() error { - return f.lock(&f.r, readLock) -} - -func (f *Flock) lock(locked *bool, flag lockType) error { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return nil - } - - if f.fh == nil { - if err := f.setFh(); err != nil { - return err - } - defer f.ensureFhState() - } - - if _, err := f.doLock(waitLock, flag, true); err != nil { - return err - } - - *locked = true - return nil -} - -func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) { - // POSIX locks apply per inode and process, and the lock for an inode is - // released when *any* descriptor for that inode is closed. So we need to - // synchronize access to each inode internally, and must serialize lock and - // unlock calls that refer to the same inode through different descriptors. - fi, err := f.fh.Stat() - if err != nil { - return false, err - } - ino := inode(fi.Sys().(*syscall.Stat_t).Ino) - - mu.Lock() - if i, dup := inodes[f]; dup && i != ino { - mu.Unlock() - return false, &os.PathError{ - Path: f.Path(), - Err: errors.New("inode for file changed since last Lock or RLock"), - } - } - - inodes[f] = ino - - var wait chan *Flock - l := locks[ino] - if l.owner == f { - // This file already owns the lock, but the call may change its lock type. - } else if l.owner == nil { - // No owner: it's ours now. - l.owner = f - } else if !blocking { - // Already owned: cannot take the lock. - mu.Unlock() - return false, nil - } else { - // Already owned: add a channel to wait on. - wait = make(chan *Flock) - l.queue = append(l.queue, wait) - } - locks[ino] = l - mu.Unlock() - - if wait != nil { - wait <- f - } - - err = setlkw(f.fh.Fd(), cmd, lt) - - if err != nil { - f.doUnlock() - if cmd == tryLock && err == unix.EACCES { - return false, nil - } - return false, err - } - - return true, nil -} - -func (f *Flock) Unlock() error { - f.m.Lock() - defer f.m.Unlock() - - // if we aren't locked or if the lockfile instance is nil - // just return a nil error because we are unlocked - if (!f.l && !f.r) || f.fh == nil { - return nil - } - - if err := f.doUnlock(); err != nil { - return err - } - - f.fh.Close() - - f.l = false - f.r = false - f.fh = nil - - return nil -} - -func (f *Flock) doUnlock() (err error) { - var owner *Flock - mu.Lock() - ino, ok := inodes[f] - if ok { - owner = locks[ino].owner - } - mu.Unlock() - - if owner == f { - err = setlkw(f.fh.Fd(), waitLock, unix.F_UNLCK) - } - - mu.Lock() - l := locks[ino] - if len(l.queue) == 0 { - // No waiters: remove the map entry. - delete(locks, ino) - } else { - // The first waiter is sending us their file now. - // Receive it and update the queue. - l.owner = <-l.queue[0] - l.queue = l.queue[1:] - locks[ino] = l - } - delete(inodes, f) - mu.Unlock() - - return err -} - -// TryLock is the preferred function for taking an exclusive file lock. This -// function takes an RW-mutex lock before it tries to lock the file, so there is -// the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the exclusive -// file lock, the function will return false instead of waiting for the lock. If -// we get the lock, we also set the *Flock instance as being exclusive-locked. -func (f *Flock) TryLock() (bool, error) { - return f.try(&f.l, writeLock) -} - -// TryRLock is the preferred function for taking a shared file lock. This -// function takes an RW-mutex lock before it tries to lock the file, so there is -// the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the shared file -// lock, the function will return false instead of waiting for the lock. If we -// get the lock, we also set the *Flock instance as being share-locked. -func (f *Flock) TryRLock() (bool, error) { - return f.try(&f.r, readLock) -} - -func (f *Flock) try(locked *bool, flag lockType) (bool, error) { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return true, nil - } - - if f.fh == nil { - if err := f.setFh(); err != nil { - return false, err - } - defer f.ensureFhState() - } - - haslock, err := f.doLock(tryLock, flag, false) - if err != nil { - return false, err - } - - *locked = haslock - return haslock, nil -} - -// setlkw calls FcntlFlock with cmd for the entire file indicated by fd. -func setlkw(fd uintptr, cmd cmdType, lt lockType) error { - for { - err := unix.FcntlFlock(fd, int(cmd), &unix.Flock_t{ - Type: int16(lt), - Whence: io.SeekStart, - Start: 0, - Len: 0, // All bytes. - }) - if err != unix.EINTR { - return err - } - } -} diff --git a/flock/flock_example_test.go b/flock/flock_example_test.go deleted file mode 100644 index 0c2afa5..0000000 --- a/flock/flock_example_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Copyright 2018 The Gofrs. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -package flock_test - -import ( - "context" - "fmt" - "os" - "time" - - "github.com/gofrs/flock" -) - -func ExampleFlock_Locked() { - f := flock.New(os.TempDir() + "/go-lock.lock") - f.TryLock() // unchecked errors here - - fmt.Printf("locked: %v\n", f.Locked()) - - f.Unlock() - - fmt.Printf("locked: %v\n", f.Locked()) - // Output: locked: true - // locked: false -} - -func ExampleFlock_TryLock() { - // should probably put these in /var/lock - fileLock := flock.New(os.TempDir() + "/go-lock.lock") - - locked, err := fileLock.TryLock() - - if err != nil { - // handle locking error - } - - if locked { - fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) - - if err := fileLock.Unlock(); err != nil { - // handle unlock error - } - } - - fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) -} - -func ExampleFlock_TryLockContext() { - // should probably put these in /var/lock - fileLock := flock.New(os.TempDir() + "/go-lock.lock") - - lockCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - locked, err := fileLock.TryLockContext(lockCtx, 678*time.Millisecond) - - if err != nil { - // handle locking error - } - - if locked { - fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) - - if err := fileLock.Unlock(); err != nil { - // handle unlock error - } - } - - fmt.Printf("path: %s; locked: %v\n", fileLock.Path(), fileLock.Locked()) -} diff --git a/flock/flock_internal_test.go b/flock/flock_internal_test.go deleted file mode 100644 index ff2a12a..0000000 --- a/flock/flock_internal_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package flock - -import ( - "io/ioutil" - "os" - "testing" -) - -func Test(t *testing.T) { - tmpFileFh, err := ioutil.TempFile(os.TempDir(), "go-flock-") - tmpFileFh.Close() - tmpFile := tmpFileFh.Name() - os.Remove(tmpFile) - - lock := New(tmpFile) - locked, err := lock.TryLock() - if locked == false || err != nil { - t.Fatalf("failed to lock: locked: %t, err: %v", locked, err) - } - - newLock := New(tmpFile) - locked, err = newLock.TryLock() - if locked != false || err != nil { - t.Fatalf("should have failed locking: locked: %t, err: %v", locked, err) - } - - if newLock.fh != nil { - t.Fatal("file handle should have been released and be nil") - } -} diff --git a/flock/flock_test.go b/flock/flock_test.go deleted file mode 100644 index abf98d3..0000000 --- a/flock/flock_test.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Copyright 2018 The Gofrs. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -package flock_test - -import ( - "context" - "io/ioutil" - "os" - "runtime" - "testing" - "time" - - "github.com/gofrs/flock" - - . "gopkg.in/check.v1" -) - -type TestSuite struct { - path string - flock *flock.Flock -} - -var _ = Suite(&TestSuite{}) - -func Test(t *testing.T) { TestingT(t) } - -func (t *TestSuite) SetUpTest(c *C) { - tmpFile, err := ioutil.TempFile(os.TempDir(), "go-flock-") - c.Assert(err, IsNil) - c.Assert(tmpFile, Not(IsNil)) - - t.path = tmpFile.Name() - - defer os.Remove(t.path) - tmpFile.Close() - - t.flock = flock.New(t.path) -} - -func (t *TestSuite) TearDownTest(c *C) { - t.flock.Unlock() - os.Remove(t.path) -} - -func (t *TestSuite) TestNew(c *C) { - var f *flock.Flock - - f = flock.New(t.path) - c.Assert(f, Not(IsNil)) - c.Check(f.Path(), Equals, t.path) - c.Check(f.Locked(), Equals, false) - c.Check(f.RLocked(), Equals, false) -} - -func (t *TestSuite) TestFlock_Path(c *C) { - var path string - path = t.flock.Path() - c.Check(path, Equals, t.path) -} - -func (t *TestSuite) TestFlock_Locked(c *C) { - var locked bool - locked = t.flock.Locked() - c.Check(locked, Equals, false) -} - -func (t *TestSuite) TestFlock_RLocked(c *C) { - var locked bool - locked = t.flock.RLocked() - c.Check(locked, Equals, false) -} - -func (t *TestSuite) TestFlock_String(c *C) { - var str string - str = t.flock.String() - c.Assert(str, Equals, t.path) -} - -func (t *TestSuite) TestFlock_TryLock(c *C) { - c.Assert(t.flock.Locked(), Equals, false) - c.Assert(t.flock.RLocked(), Equals, false) - - var locked bool - var err error - - locked, err = t.flock.TryLock() - c.Assert(err, IsNil) - c.Check(locked, Equals, true) - c.Check(t.flock.Locked(), Equals, true) - c.Check(t.flock.RLocked(), Equals, false) - - locked, err = t.flock.TryLock() - c.Assert(err, IsNil) - c.Check(locked, Equals, true) - - // make sure we just return false with no error in cases - // where we would have been blocked - locked, err = flock.New(t.path).TryLock() - c.Assert(err, IsNil) - c.Check(locked, Equals, false) -} - -func (t *TestSuite) TestFlock_TryRLock(c *C) { - c.Assert(t.flock.Locked(), Equals, false) - c.Assert(t.flock.RLocked(), Equals, false) - - var locked bool - var err error - - locked, err = t.flock.TryRLock() - c.Assert(err, IsNil) - c.Check(locked, Equals, true) - c.Check(t.flock.Locked(), Equals, false) - c.Check(t.flock.RLocked(), Equals, true) - - locked, err = t.flock.TryRLock() - c.Assert(err, IsNil) - c.Check(locked, Equals, true) - - // shared lock should not block. - flock2 := flock.New(t.path) - locked, err = flock2.TryRLock() - c.Assert(err, IsNil) - if runtime.GOOS == "aix" { - // When using POSIX locks, we can't safely read-lock the same - // inode through two different descriptors at the same time: - // when the first descriptor is closed, the second descriptor - // would still be open but silently unlocked. So a second - // TryRLock must return false. - c.Check(locked, Equals, false) - } else { - c.Check(locked, Equals, true) - } - - // make sure we just return false with no error in cases - // where we would have been blocked - t.flock.Unlock() - flock2.Unlock() - t.flock.Lock() - locked, err = flock.New(t.path).TryRLock() - c.Assert(err, IsNil) - c.Check(locked, Equals, false) -} - -func (t *TestSuite) TestFlock_TryLockContext(c *C) { - // happy path - ctx, cancel := context.WithCancel(context.Background()) - locked, err := t.flock.TryLockContext(ctx, time.Second) - c.Assert(err, IsNil) - c.Check(locked, Equals, true) - - // context already canceled - cancel() - locked, err = flock.New(t.path).TryLockContext(ctx, time.Second) - c.Assert(err, Equals, context.Canceled) - c.Check(locked, Equals, false) - - // timeout - ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) - defer cancel() - locked, err = flock.New(t.path).TryLockContext(ctx, time.Second) - c.Assert(err, Equals, context.DeadlineExceeded) - c.Check(locked, Equals, false) -} - -func (t *TestSuite) TestFlock_TryRLockContext(c *C) { - // happy path - ctx, cancel := context.WithCancel(context.Background()) - locked, err := t.flock.TryRLockContext(ctx, time.Second) - c.Assert(err, IsNil) - c.Check(locked, Equals, true) - - // context already canceled - cancel() - locked, err = flock.New(t.path).TryRLockContext(ctx, time.Second) - c.Assert(err, Equals, context.Canceled) - c.Check(locked, Equals, false) - - // timeout - t.flock.Unlock() - t.flock.Lock() - ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond) - defer cancel() - locked, err = flock.New(t.path).TryRLockContext(ctx, time.Second) - c.Assert(err, Equals, context.DeadlineExceeded) - c.Check(locked, Equals, false) -} - -func (t *TestSuite) TestFlock_Unlock(c *C) { - var err error - - err = t.flock.Unlock() - c.Assert(err, IsNil) - - // get a lock for us to unlock - locked, err := t.flock.TryLock() - c.Assert(err, IsNil) - c.Assert(locked, Equals, true) - c.Assert(t.flock.Locked(), Equals, true) - c.Check(t.flock.RLocked(), Equals, false) - - _, err = os.Stat(t.path) - c.Assert(os.IsNotExist(err), Equals, false) - - err = t.flock.Unlock() - c.Assert(err, IsNil) - c.Check(t.flock.Locked(), Equals, false) - c.Check(t.flock.RLocked(), Equals, false) -} - -func (t *TestSuite) TestFlock_Lock(c *C) { - c.Assert(t.flock.Locked(), Equals, false) - c.Check(t.flock.RLocked(), Equals, false) - - var err error - - err = t.flock.Lock() - c.Assert(err, IsNil) - c.Check(t.flock.Locked(), Equals, true) - c.Check(t.flock.RLocked(), Equals, false) - - // test that the short-circuit works - err = t.flock.Lock() - c.Assert(err, IsNil) - - // - // Test that Lock() is a blocking call - // - ch := make(chan error, 2) - gf := flock.New(t.path) - defer gf.Unlock() - - go func(ch chan<- error) { - ch <- nil - ch <- gf.Lock() - close(ch) - }(ch) - - errCh, ok := <-ch - c.Assert(ok, Equals, true) - c.Assert(errCh, IsNil) - - err = t.flock.Unlock() - c.Assert(err, IsNil) - - errCh, ok = <-ch - c.Assert(ok, Equals, true) - c.Assert(errCh, IsNil) - c.Check(t.flock.Locked(), Equals, false) - c.Check(t.flock.RLocked(), Equals, false) - c.Check(gf.Locked(), Equals, true) - c.Check(gf.RLocked(), Equals, false) -} - -func (t *TestSuite) TestFlock_RLock(c *C) { - c.Assert(t.flock.Locked(), Equals, false) - c.Check(t.flock.RLocked(), Equals, false) - - var err error - - err = t.flock.RLock() - c.Assert(err, IsNil) - c.Check(t.flock.Locked(), Equals, false) - c.Check(t.flock.RLocked(), Equals, true) - - // test that the short-circuit works - err = t.flock.RLock() - c.Assert(err, IsNil) - - // - // Test that RLock() is a blocking call - // - ch := make(chan error, 2) - gf := flock.New(t.path) - defer gf.Unlock() - - go func(ch chan<- error) { - ch <- nil - ch <- gf.RLock() - close(ch) - }(ch) - - errCh, ok := <-ch - c.Assert(ok, Equals, true) - c.Assert(errCh, IsNil) - - err = t.flock.Unlock() - c.Assert(err, IsNil) - - errCh, ok = <-ch - c.Assert(ok, Equals, true) - c.Assert(errCh, IsNil) - c.Check(t.flock.Locked(), Equals, false) - c.Check(t.flock.RLocked(), Equals, false) - c.Check(gf.Locked(), Equals, false) - c.Check(gf.RLocked(), Equals, true) -} diff --git a/flock/flock_unix.go b/flock/flock_unix.go deleted file mode 100644 index c315a3e..0000000 --- a/flock/flock_unix.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -// +build !aix,!windows - -package flock - -import ( - "os" - "syscall" -) - -// Lock is a blocking call to try and take an exclusive file lock. It will wait -// until it is able to obtain the exclusive file lock. It's recommended that -// TryLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already exclusive-locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -// -// If the *Flock has a shared lock (RLock), this may transparently replace the -// shared lock with an exclusive lock on some UNIX-like operating systems. Be -// careful when using exclusive locks in conjunction with shared locks -// (RLock()), because calling Unlock() may accidentally release the exclusive -// lock that was once a shared lock. -func (f *Flock) Lock() error { - return f.lock(&f.l, syscall.LOCK_EX) -} - -// RLock is a blocking call to try and take a shared file lock. It will wait -// until it is able to obtain the shared file lock. It's recommended that -// TryRLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already shared-locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -func (f *Flock) RLock() error { - return f.lock(&f.r, syscall.LOCK_SH) -} - -func (f *Flock) lock(locked *bool, flag int) error { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return nil - } - - if f.fh == nil { - if err := f.setFh(); err != nil { - return err - } - defer f.ensureFhState() - } - - if err := syscall.Flock(int(f.fh.Fd()), flag); err != nil { - shouldRetry, reopenErr := f.reopenFDOnError(err) - if reopenErr != nil { - return reopenErr - } - - if !shouldRetry { - return err - } - - if err = syscall.Flock(int(f.fh.Fd()), flag); err != nil { - return err - } - } - - *locked = true - return nil -} - -// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so -// while it is running the Locked() and RLocked() functions will be blocked. -// -// This function short-circuits if we are unlocked already. If not, it calls -// syscall.LOCK_UN on the file and closes the file descriptor. It does not -// remove the file from disk. It's up to your application to do. -// -// Please note, if your shared lock became an exclusive lock this may -// unintentionally drop the exclusive lock if called by the consumer that -// believes they have a shared lock. Please see Lock() for more details. -func (f *Flock) Unlock() error { - f.m.Lock() - defer f.m.Unlock() - - // if we aren't locked or if the lockfile instance is nil - // just return a nil error because we are unlocked - if (!f.l && !f.r) || f.fh == nil { - return nil - } - - // mark the file as unlocked - if err := syscall.Flock(int(f.fh.Fd()), syscall.LOCK_UN); err != nil { - return err - } - - f.fh.Close() - - f.l = false - f.r = false - f.fh = nil - - return nil -} - -// TryLock is the preferred function for taking an exclusive file lock. This -// function takes an RW-mutex lock before it tries to lock the file, so there is -// the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the exclusive -// file lock, the function will return false instead of waiting for the lock. If -// we get the lock, we also set the *Flock instance as being exclusive-locked. -func (f *Flock) TryLock() (bool, error) { - return f.try(&f.l, syscall.LOCK_EX) -} - -// TryRLock is the preferred function for taking a shared file lock. This -// function takes an RW-mutex lock before it tries to lock the file, so there is -// the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the shared file -// lock, the function will return false instead of waiting for the lock. If we -// get the lock, we also set the *Flock instance as being share-locked. -func (f *Flock) TryRLock() (bool, error) { - return f.try(&f.r, syscall.LOCK_SH) -} - -func (f *Flock) try(locked *bool, flag int) (bool, error) { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return true, nil - } - - if f.fh == nil { - if err := f.setFh(); err != nil { - return false, err - } - defer f.ensureFhState() - } - - var retried bool -retry: - err := syscall.Flock(int(f.fh.Fd()), flag|syscall.LOCK_NB) - - switch err { - case syscall.EWOULDBLOCK: - return false, nil - case nil: - *locked = true - return true, nil - } - if !retried { - if shouldRetry, reopenErr := f.reopenFDOnError(err); reopenErr != nil { - return false, reopenErr - } else if shouldRetry { - retried = true - goto retry - } - } - - return false, err -} - -// reopenFDOnError determines whether we should reopen the file handle -// in readwrite mode and try again. This comes from util-linux/sys-utils/flock.c: -// Since Linux 3.4 (commit 55725513) -// Probably NFSv4 where flock() is emulated by fcntl(). -func (f *Flock) reopenFDOnError(err error) (bool, error) { - if err != syscall.EIO && err != syscall.EBADF { - return false, nil - } - if st, err := f.fh.Stat(); err == nil { - // if the file is able to be read and written - if st.Mode()&0600 == 0600 { - f.fh.Close() - f.fh = nil - - // reopen in read-write mode and set the filehandle - fh, err := os.OpenFile(f.path, os.O_CREATE|os.O_RDWR, os.FileMode(0600)) - if err != nil { - return false, err - } - f.fh = fh - return true, nil - } - } - - return false, nil -} diff --git a/flock/flock_winapi.go b/flock/flock_winapi.go deleted file mode 100644 index fe405a2..0000000 --- a/flock/flock_winapi.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -// +build windows - -package flock - -import ( - "syscall" - "unsafe" -) - -var ( - kernel32, _ = syscall.LoadLibrary("kernel32.dll") - procLockFileEx, _ = syscall.GetProcAddress(kernel32, "LockFileEx") - procUnlockFileEx, _ = syscall.GetProcAddress(kernel32, "UnlockFileEx") -) - -const ( - winLockfileFailImmediately = 0x00000001 - winLockfileExclusiveLock = 0x00000002 - winLockfileSharedLock = 0x00000000 -) - -// Use of 0x00000000 for the shared lock is a guess based on some the MS Windows -// `LockFileEX` docs, which document the `LOCKFILE_EXCLUSIVE_LOCK` flag as: -// -// > The function requests an exclusive lock. Otherwise, it requests a shared -// > lock. -// -// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx - -func lockFileEx(handle syscall.Handle, flags uint32, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { - r1, _, errNo := syscall.Syscall6( - uintptr(procLockFileEx), - 6, - uintptr(handle), - uintptr(flags), - uintptr(reserved), - uintptr(numberOfBytesToLockLow), - uintptr(numberOfBytesToLockHigh), - uintptr(unsafe.Pointer(offset))) - - if r1 != 1 { - if errNo == 0 { - return false, syscall.EINVAL - } - - return false, errNo - } - - return true, 0 -} - -func unlockFileEx(handle syscall.Handle, reserved uint32, numberOfBytesToLockLow uint32, numberOfBytesToLockHigh uint32, offset *syscall.Overlapped) (bool, syscall.Errno) { - r1, _, errNo := syscall.Syscall6( - uintptr(procUnlockFileEx), - 5, - uintptr(handle), - uintptr(reserved), - uintptr(numberOfBytesToLockLow), - uintptr(numberOfBytesToLockHigh), - uintptr(unsafe.Pointer(offset)), - 0) - - if r1 != 1 { - if errNo == 0 { - return false, syscall.EINVAL - } - - return false, errNo - } - - return true, 0 -} diff --git a/flock/flock_windows.go b/flock/flock_windows.go deleted file mode 100644 index 8637c56..0000000 --- a/flock/flock_windows.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2015 Tim Heckman. All rights reserved. -// Use of this source code is governed by the BSD 3-Clause -// license that can be found in the LICENSE file. - -package flock - -import ( - "syscall" -) - -// ErrorLockViolation is the error code returned from the Windows syscall when a -// lock would block and you ask to fail immediately. -const ErrorLockViolation syscall.Errno = 0x21 // 33 - -// Lock is a blocking call to try and take an exclusive file lock. It will wait -// until it is able to obtain the exclusive file lock. It's recommended that -// TryLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -func (f *Flock) Lock() error { - return f.lock(&f.l, winLockfileExclusiveLock) -} - -// RLock is a blocking call to try and take a shared file lock. It will wait -// until it is able to obtain the shared file lock. It's recommended that -// TryRLock() be used over this function. This function may block the ability to -// query the current Locked() or RLocked() status due to a RW-mutex lock. -// -// If we are already locked, this function short-circuits and returns -// immediately assuming it can take the mutex lock. -func (f *Flock) RLock() error { - return f.lock(&f.r, winLockfileSharedLock) -} - -func (f *Flock) lock(locked *bool, flag uint32) error { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return nil - } - - if f.Fh == nil { - if err := f.setFh(); err != nil { - return err - } - defer f.ensureFhState() - } - - if _, errNo := lockFileEx(syscall.Handle(f.Fh.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { - return errNo - } - - *locked = true - return nil -} - -// Unlock is a function to unlock the file. This file takes a RW-mutex lock, so -// while it is running the Locked() and RLocked() functions will be blocked. -// -// This function short-circuits if we are unlocked already. If not, it calls -// UnlockFileEx() on the file and closes the file descriptor. It does not remove -// the file from disk. It's up to your application to do. -func (f *Flock) Unlock() error { - f.m.Lock() - defer f.m.Unlock() - - // if we aren't locked or if the lockfile instance is nil - // just return a nil error because we are unlocked - if (!f.l && !f.r) || f.Fh == nil { - return nil - } - - // mark the file as unlocked - if _, errNo := unlockFileEx(syscall.Handle(f.Fh.Fd()), 0, 1, 0, &syscall.Overlapped{}); errNo > 0 { - return errNo - } - - f.Fh.Close() - - f.l = false - f.r = false - f.Fh = nil - - return nil -} - -// TryLock is the preferred function for taking an exclusive file lock. This -// function does take a RW-mutex lock before it tries to lock the file, so there -// is the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the exclusive -// file lock, the function will return false instead of waiting for the lock. If -// we get the lock, we also set the *Flock instance as being exclusive-locked. -func (f *Flock) TryLock() (bool, error) { - return f.try(&f.l, winLockfileExclusiveLock) -} - -// TryRLock is the preferred function for taking a shared file lock. This -// function does take a RW-mutex lock before it tries to lock the file, so there -// is the possibility that this function may block for a short time if another -// goroutine is trying to take any action. -// -// The actual file lock is non-blocking. If we are unable to get the shared file -// lock, the function will return false instead of waiting for the lock. If we -// get the lock, we also set the *Flock instance as being shared-locked. -func (f *Flock) TryRLock() (bool, error) { - return f.try(&f.r, winLockfileSharedLock) -} - -func (f *Flock) try(locked *bool, flag uint32) (bool, error) { - f.m.Lock() - defer f.m.Unlock() - - if *locked { - return true, nil - } - - if f.Fh == nil { - if err := f.setFh(); err != nil { - return false, err - } - defer f.ensureFhState() - } - - _, errNo := lockFileEx(syscall.Handle(f.Fh.Fd()), flag|winLockfileFailImmediately, 0, 1, 0, &syscall.Overlapped{}) - - if errNo > 0 { - if errNo == ErrorLockViolation || errNo == syscall.ERROR_IO_PENDING { - return false, nil - } - - return false, errNo - } - - *locked = true - - return true, nil -} From c825fca7e01e19d0745522f5d63737af2bd7d3ae Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 00:21:48 -0700 Subject: [PATCH 12/17] Removing mac persistence from this PR --- internal/mac_persistence.go | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 internal/mac_persistence.go diff --git a/internal/mac_persistence.go b/internal/mac_persistence.go deleted file mode 100644 index fba2e0b..0000000 --- a/internal/mac_persistence.go +++ /dev/null @@ -1,5 +0,0 @@ -package internal - -func placeHolder() error { - return nil -} From 9c21596e10859ccec3679ab16556772f7aec7c6d Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 01:43:33 -0700 Subject: [PATCH 13/17] Changes to lock open --- extensions/lock/lock.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/lock/lock.go b/extensions/lock/lock.go index 0ec8651..62ea914 100644 --- a/extensions/lock/lock.go +++ b/extensions/lock/lock.go @@ -14,7 +14,8 @@ type Lock struct { retries int retryDelay time.Duration - lockFile *os.File + lockFile *os.File + lockfileName string fLock *flock.Flock mu sync.Mutex @@ -38,11 +39,8 @@ func New(lockFileName string, options ...Option) (*Lock, error) { for _, o := range options { o(l) } - lockfile, err := os.Create(lockFileName) - if err != nil { - return &Lock{}, err - } - l.fLock = flock.New(lockfile.Name()) + l.fLock = flock.New(lockFileName) + l.lockfileName = lockFileName return l, nil } @@ -50,6 +48,10 @@ func (l *Lock) Lock() error { l.mu.Lock() defer l.mu.Unlock() for tryCount := 0; tryCount < l.retries; tryCount++ { + lockfile, err := os.OpenFile(lockFileName, os.O_RDWR|os.O_CREATE) + if err != nil { + return &Lock{}, err + } err := l.fLock.Lock() if err != nil { time.Sleep(l.retryDelay * time.Millisecond) From 666b417dc1bfa9d4950273c3183593689057e7d6 Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 14:31:21 -0700 Subject: [PATCH 14/17] PR review 2 --- extensions/lock/lock.go | 29 ++++---- extensions/lock/lock_test.go | 137 +++++++++++++++++++---------------- 2 files changed, 89 insertions(+), 77 deletions(-) diff --git a/extensions/lock/lock.go b/extensions/lock/lock.go index 62ea914..771af8f 100644 --- a/extensions/lock/lock.go +++ b/extensions/lock/lock.go @@ -2,7 +2,7 @@ package lock import ( "errors" - "fmt" + "io/fs" "os" "sync" "time" @@ -14,7 +14,6 @@ type Lock struct { retries int retryDelay time.Duration - lockFile *os.File lockfileName string fLock *flock.Flock @@ -48,18 +47,15 @@ func (l *Lock) Lock() error { l.mu.Lock() defer l.mu.Unlock() for tryCount := 0; tryCount < l.retries; tryCount++ { - lockfile, err := os.OpenFile(lockFileName, os.O_RDWR|os.O_CREATE) - if err != nil { - return &Lock{}, err - } - err := l.fLock.Lock() - if err != nil { + locked, err := l.fLock.TryLock() + if err != nil || !locked { time.Sleep(l.retryDelay * time.Millisecond) continue - } else { - if l.fLock.Locked() { - l.fLock.Fh.WriteString(fmt.Sprintf("{%d} {%s}", os.Getpid(), os.Args[0])) - } + } else if locked { + // commenting this till we have flock merged in and ready to use with changes + // otherwise it would not compile + //l.fLock.Fh.WriteString(fmt.Sprintf("{%d} {%s}", os.Getpid(), os.Args[0])) + //l.fLock.Fh.Sync() return nil } } @@ -71,9 +67,12 @@ func (l *Lock) UnLock() error { if err := l.fLock.Unlock(); err != nil { return err } - l.lockFile.Close() - if err := os.Remove(l.fLock.Path()); err != nil { - return err + err := os.Remove(l.fLock.Path()) + if err != nil { + errVal := err.(*fs.PathError) + if errVal != fs.ErrNotExist || errVal != fs.ErrPermission { + return err + } } } return nil diff --git a/extensions/lock/lock_test.go b/extensions/lock/lock_test.go index 6a32cb3..9f53208 100644 --- a/extensions/lock/lock_test.go +++ b/extensions/lock/lock_test.go @@ -2,7 +2,9 @@ package lock import ( "bytes" + "errors" "fmt" + "io/ioutil" "log" "os" "strings" @@ -13,26 +15,34 @@ import ( "github.com/AzureAD/microsoft-authentication-extensions-for-go/internal" ) -func spinThreads(noOfThreads int, sleepInterval time.Duration, t *testing.T) int { - cacheFile := "cache.txt" - var wg sync.WaitGroup - wg.Add(noOfThreads) - for i := 0; i < noOfThreads; i++ { - go func(i int) { - defer wg.Done() - acquireLockAndWriteToCache(i, sleepInterval, cacheFile) - }(i) +const cacheFile = "cache.txt" + +func TestLocking(t *testing.T) { + + tests := []struct { + desc string + concurrency int + sleepInterval time.Duration + cacheFile string + }{ + {"Normal", 4, 100 * time.Millisecond, "cache_normal"}, + {"High", 40, 100 * time.Millisecond, "cache_high"}, } - wg.Wait() - t.Cleanup(func() { - if err := os.Remove(cacheFile); err != nil { - log.Println("Failed to remove cache file", err) + + for _, test := range tests { + tmpfile, err := ioutil.TempFile("", test.cacheFile) + defer os.Remove(tmpfile.Name()) + if err != nil { + t.Fatalf("TestLocking(%s): Could not create cache file", test.desc) } - }) - return validateResult(cacheFile, t) -} + err = spinThreads(test.concurrency, time.Duration(test.sleepInterval), tmpfile.Name()) + if err != nil { + t.Fatalf("TestLocking(%s): %s", test.desc, err) + } + } -func acquireLockAndWriteToCache(threadNo int, sleepInterval time.Duration, cacheFile string) { +} +func acquire(threadNo int, sleepInterval time.Duration, cacheFile string) { cacheAccessor := internal.NewFileAccessor(cacheFile) lockfileName := cacheFile + ".lockfile" l, err := New(lockfileName, WithRetries(60), WithRetryDelay(100)) @@ -48,61 +58,64 @@ func acquireLockAndWriteToCache(threadNo int, sleepInterval time.Duration, cache var buffer bytes.Buffer buffer.Write(data) buffer.WriteString(fmt.Sprintf("< %d \n", threadNo)) - time.Sleep(sleepInterval * time.Millisecond) + time.Sleep(sleepInterval) buffer.WriteString(fmt.Sprintf("> %d \n", threadNo)) cacheAccessor.Write(buffer.Bytes()) } -func validateResult(cacheFile string, t *testing.T) int { - count := 0 - var prevProc string = "" - var tag string - var proc string +func spinThreads(concurrency int, sleepInterval time.Duration, cacheFile string) error { + var wg sync.WaitGroup + wg.Add(concurrency) + for i := 0; i < concurrency; i++ { + go func(i int) { + defer wg.Done() + acquire(i, sleepInterval, cacheFile) + }(i) + } + wg.Wait() + i, err := validateResult(cacheFile) + if err != nil { + return err + } + if i != concurrency*2 { + return fmt.Errorf("should have seen %d line entries, found %d", concurrency*2, i) + } + return nil +} + +func validateResult(cacheFile string) (int, error) { + var ( + count int + prevProc, tag, proc string + ) data, err := os.ReadFile(cacheFile) if err != nil { log.Println(err) } - dat := string(data) - temp := strings.Split(dat, "\n") - for _, ele := range temp { - if ele != "" { - count += 1 - split := strings.Split(ele, " ") - tag = split[0] - proc = split[1] - if prevProc != "" { - if proc != prevProc { - t.Fatal("Process overlap found") - } - if tag != ">" { - t.Fatal("Process overlap found") - } - prevProc = "" + temp := strings.Split(string(data), "\n") - } else { - if tag != "<" { - t.Fatal("Opening bracket not found") - } - prevProc = proc + for _, s := range temp { + if strings.TrimSpace(s) == "" { + continue + } + count += 1 + split := strings.Split(s, " ") + tag = split[0] + proc = split[1] + if prevProc == "" { + if tag != "<" { + return 0, errors.New("opening bracket not found") } + prevProc = proc + continue } + if proc != prevProc { + return 0, errors.New("process overlap found") + } + if tag != ">" { + return 0, errors.New("process overlap found") + } + prevProc = "" } - return count -} -func TestForNormalWorkload(t *testing.T) { - noOfThreads := 4 - sleepInterval := 100 - n := spinThreads(noOfThreads, time.Duration(sleepInterval), t) - if n != 4*2 { - t.Fatalf("Should not observe starvation") - } -} - -func TestForHighWorkload(t *testing.T) { - noOfThreads := 80 - sleepInterval := 100 - n := spinThreads(noOfThreads, time.Duration(sleepInterval), t) - if n > 80*2 { - t.Fatalf("Starvation or not, we should not observe garbled payload") - } + return count, nil } From 357fd535bcd138da801b3a56a0663c630c3a82cc Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 14:33:10 -0700 Subject: [PATCH 15/17] Minor removal --- extensions/lock/lock.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/extensions/lock/lock.go b/extensions/lock/lock.go index 771af8f..1e74f65 100644 --- a/extensions/lock/lock.go +++ b/extensions/lock/lock.go @@ -14,8 +14,6 @@ type Lock struct { retries int retryDelay time.Duration - lockfileName string - fLock *flock.Flock mu sync.Mutex } @@ -39,7 +37,6 @@ func New(lockFileName string, options ...Option) (*Lock, error) { o(l) } l.fLock = flock.New(lockFileName) - l.lockfileName = lockFileName return l, nil } From 53249aa7f860988ab36e71b20f426763d5b67dcb Mon Sep 17 00:00:00 2001 From: Abhidnya Date: Tue, 19 Oct 2021 14:37:16 -0700 Subject: [PATCH 16/17] Naming changes --- extensions/lock/lock_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/extensions/lock/lock_test.go b/extensions/lock/lock_test.go index 9f53208..3d7ca78 100644 --- a/extensions/lock/lock_test.go +++ b/extensions/lock/lock_test.go @@ -25,8 +25,8 @@ func TestLocking(t *testing.T) { sleepInterval time.Duration cacheFile string }{ - {"Normal", 4, 100 * time.Millisecond, "cache_normal"}, - {"High", 40, 100 * time.Millisecond, "cache_high"}, + {"Normal", 4, 50 * time.Millisecond, "cache_normal"}, + {"High", 40, 50 * time.Millisecond, "cache_high"}, } for _, test := range tests { @@ -35,7 +35,7 @@ func TestLocking(t *testing.T) { if err != nil { t.Fatalf("TestLocking(%s): Could not create cache file", test.desc) } - err = spinThreads(test.concurrency, time.Duration(test.sleepInterval), tmpfile.Name()) + err = spin(test.concurrency, time.Duration(test.sleepInterval), tmpfile.Name()) if err != nil { t.Fatalf("TestLocking(%s): %s", test.desc, err) } @@ -63,7 +63,7 @@ func acquire(threadNo int, sleepInterval time.Duration, cacheFile string) { cacheAccessor.Write(buffer.Bytes()) } -func spinThreads(concurrency int, sleepInterval time.Duration, cacheFile string) error { +func spin(concurrency int, sleepInterval time.Duration, cacheFile string) error { var wg sync.WaitGroup wg.Add(concurrency) for i := 0; i < concurrency; i++ { @@ -73,7 +73,7 @@ func spinThreads(concurrency int, sleepInterval time.Duration, cacheFile string) }(i) } wg.Wait() - i, err := validateResult(cacheFile) + i, err := validate(cacheFile) if err != nil { return err } @@ -83,7 +83,7 @@ func spinThreads(concurrency int, sleepInterval time.Duration, cacheFile string) return nil } -func validateResult(cacheFile string) (int, error) { +func validate(cacheFile string) (int, error) { var ( count int prevProc, tag, proc string From 92fe18758bbff046a8e11e858d5388afde89b466 Mon Sep 17 00:00:00 2001 From: abhidnya13 Date: Mon, 25 Oct 2021 12:18:35 -0700 Subject: [PATCH 17/17] Adding new structure --- internal/accessor/accessor.go | 1 + internal/accessor/file/file.go | 1 + internal/accessor/windows/windows.go | 0 3 files changed, 2 insertions(+) create mode 100644 internal/accessor/accessor.go create mode 100644 internal/accessor/file/file.go create mode 100644 internal/accessor/windows/windows.go diff --git a/internal/accessor/accessor.go b/internal/accessor/accessor.go new file mode 100644 index 0000000..81b2a8b --- /dev/null +++ b/internal/accessor/accessor.go @@ -0,0 +1 @@ +package accessor diff --git a/internal/accessor/file/file.go b/internal/accessor/file/file.go new file mode 100644 index 0000000..b691ba5 --- /dev/null +++ b/internal/accessor/file/file.go @@ -0,0 +1 @@ +package file diff --git a/internal/accessor/windows/windows.go b/internal/accessor/windows/windows.go new file mode 100644 index 0000000..e69de29