Merge pull request #17636 from hashicorp/jbardin/windows-refresh-lock

Don't open a new file descriptor for a locked state.
This commit is contained in:
James Bardin 2018-03-19 19:17:12 -04:00 committed by GitHub
commit 6b6b92f6b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 65 additions and 5 deletions

View File

@ -119,8 +119,20 @@ func (s *LocalState) RefreshState() error {
s.mu.Lock()
defer s.mu.Unlock()
if s.PathOut == "" {
s.PathOut = s.Path
}
var reader io.Reader
if !s.written {
// The s.Path file is only OK to read if we have not written any state out
// (in which case the same state needs to be read in), and no state output file
// has been opened (possibly via a lock) or the input path is different
// than the output path.
// This is important for Windows, as if the input file is the same as the
// output file, and the output file has been locked already, we can't open
// the file again.
if !s.written && (s.stateFileOut == nil || s.Path != s.PathOut) {
// we haven't written a state file yet, so load from Path
f, err := os.Open(s.Path)
if err != nil {

View File

@ -166,3 +166,42 @@ func testLocalState(t *testing.T) *LocalState {
return ls
}
// Make sure we can refresh while the state is locked
func TestLocalState_refreshWhileLocked(t *testing.T) {
f, err := ioutil.TempFile("", "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
err = terraform.WriteState(TestStateInitial(), f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
s := &LocalState{Path: f.Name()}
defer os.Remove(s.Path)
// lock first
info := NewLockInfo()
info.Operation = "test"
lockID, err := s.Lock(info)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := s.Unlock(lockID); err != nil {
t.Fatal(err)
}
}()
if err := s.RefreshState(); err != nil {
t.Fatal(err)
}
readState := s.State()
if readState == nil || readState.Lineage == "" {
t.Fatal("missing state")
}
}

View File

@ -9,6 +9,7 @@ import (
"io"
"io/ioutil"
"log"
"os"
"reflect"
"sort"
"strconv"
@ -1876,13 +1877,21 @@ var ErrNoState = errors.New("no state")
// ReadState reads a state structure out of a reader in the format that
// was written by WriteState.
func ReadState(src io.Reader) (*State, error) {
buf := bufio.NewReader(src)
if _, err := buf.Peek(1); err != nil {
// the error is either io.EOF or "invalid argument", and both are from
// an empty state.
// check for a nil file specifically, since that produces a platform
// specific error if we try to use it in a bufio.Reader.
if f, ok := src.(*os.File); ok && f == nil {
return nil, ErrNoState
}
buf := bufio.NewReader(src)
if _, err := buf.Peek(1); err != nil {
if err == io.EOF {
return nil, ErrNoState
}
return nil, err
}
if err := testForV0State(buf); err != nil {
return nil, err
}