Revert "have StateHook periodically PersistState"

This reverts commit b73d037761.

This commit seems to have introduced a race condition where we can
concurrently keep updating state after we've checked if we need to
increase the serial, and thus end up writing partial changes
to the state backend.

In the case of Terraform Enterprise, this fails altogether because
of the state hash consistency check it does.
This commit is contained in:
Martin Atkins 2017-06-07 16:13:08 -07:00
parent ebec7fbe78
commit a42ebe389c
2 changed files with 0 additions and 123 deletions

View File

@ -2,27 +2,17 @@ package local
import (
"sync"
"time"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
)
// interval between forced PersistState calls by StateHook
const persistStateHookInterval = 10 * time.Second
// StateHook is a hook that continuously updates the state by calling
// WriteState on a state.State.
type StateHook struct {
terraform.NilHook
sync.Mutex
// lastPersist is the time of the last call to PersistState, for periodic
// updates to remote state. PostStateUpdate will force a call PersistState
// if it has been more that persistStateHookInterval since the last call to
// PersistState.
lastPersist time.Time
State state.State
}
@ -36,24 +26,8 @@ func (h *StateHook) PostStateUpdate(
if err := h.State.WriteState(s); err != nil {
return terraform.HookActionHalt, err
}
// periodically persist the state
if time.Since(h.lastPersist) > persistStateHookInterval {
if err := h.persistState(); err != nil {
return terraform.HookActionHalt, err
}
}
}
// Continue forth
return terraform.HookActionContinue, nil
}
func (h *StateHook) persistState() error {
if h.State != nil {
err := h.State.PersistState()
h.lastPersist = time.Now()
return err
}
return nil
}

View File

@ -1,9 +1,7 @@
package local
import (
"sync"
"testing"
"time"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
@ -29,98 +27,3 @@ func TestStateHook(t *testing.T) {
t.Fatalf("bad state: %#v", is.State())
}
}
// testPersistState stores the state on WriteState, and
type testPersistState struct {
*state.InmemState
mu sync.Mutex
persisted bool
}
func (s *testPersistState) WriteState(state *terraform.State) error {
s.mu.Lock()
defer s.mu.Unlock()
s.persisted = false
return s.InmemState.WriteState(state)
}
func (s *testPersistState) PersistState() error {
s.mu.Lock()
defer s.mu.Unlock()
s.persisted = true
return nil
}
// verify that StateHook calls PersistState if the last call was more than
// persistStateHookInterval
func TestStateHookPersist(t *testing.T) {
is := &testPersistState{
InmemState: &state.InmemState{},
}
hook := &StateHook{State: is}
s := state.TestStateInitial()
hook.PostStateUpdate(s)
// the first call should persist, since the last time was zero
if !is.persisted {
t.Fatal("PersistState not called")
}
s.Serial++
hook.PostStateUpdate(s)
// this call should not have persisted
if is.persisted {
t.Fatal("PostStateUpdate called PersistState early")
}
if !is.State().Equal(s) {
t.Fatalf("bad state: %#v", is.State())
}
// set the last call back to before our interval
hook.lastPersist = time.Now().Add(-2 * persistStateHookInterval)
s.Serial++
hook.PostStateUpdate(s)
if !is.persisted {
t.Fatal("PersistState not called")
}
if !is.State().Equal(s) {
t.Fatalf("bad state: %#v", is.State())
}
}
// verify that the satet hook is safe for concurrent use
func TestStateHookRace(t *testing.T) {
is := &state.InmemState{}
var hook terraform.Hook = &StateHook{State: is}
s := state.TestStateInitial()
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
action, err := hook.PostStateUpdate(s)
if err != nil {
t.Fatalf("err: %s", err)
}
if action != terraform.HookActionContinue {
t.Fatalf("bad: %v", action)
}
if !is.State().Equal(s) {
t.Fatalf("bad state: %#v", is.State())
}
}()
}
wg.Wait()
}