Skip to content

Commit

Permalink
Fix issue with creating concurrent releases.
Browse files Browse the repository at this point in the history
  • Loading branch information
ejholmes committed Jan 4, 2016
1 parent f2c29c8 commit 395d91f
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 6 deletions.
5 changes: 5 additions & 0 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ func (db *DB) IsHealthy() bool {
return db.DB.DB().Ping() == nil
}

// Debug puts the db in debug mode, which logs all queries.
func (db *DB) Debug() {
db.DB = db.DB.Debug()
}

// Scope is an interface that scopes a gorm.DB. Scopes are used in
// ThingsFirst and ThingsAll methods on the store for filtering/querying.
type Scope interface {
Expand Down
2 changes: 1 addition & 1 deletion empiretest/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func NewEmpire(t testing.TB) *empire.Empire {

// Log queries if verbose mode is set.
if testing.Verbose() {
//db = db.Debug()
db.Debug()
}

e := empire.New(db, empire.DefaultOptions)
Expand Down
14 changes: 9 additions & 5 deletions releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,15 @@ type releasesService struct {

// ReleasesCreate creates a new release then submits it to the scheduler.
func (s *releasesService) ReleasesCreate(ctx context.Context, db *gorm.DB, r *Release) (*Release, error) {
// Lock all releases for the given application to ensure that the
// release version is updated automically.
if err := db.Exec(`select 1 from releases where app_id = ? for update`, r.App.ID).Error; err != nil {
return r, err
}

// Create a new formation for this release.
if err := createFormation(db, r); err != nil {
return nil, err
return r, err
}

r, err := releasesCreate(db, r)
Expand Down Expand Up @@ -200,13 +206,11 @@ func createFormation(db *gorm.DB, release *Release) error {
return nil
}

// ReleasesLastVersion returns the last ReleaseVersion for the given App. This
// function also ensures that the last release is locked until the transaction
// is commited, so the release version can be incremented atomically.
// ReleasesLastVersion returns the last ReleaseVersion for the given App.
func releasesLastVersion(db *gorm.DB, appID string) (int, error) {
var version int

rows, err := db.Raw(`select version from releases where app_id = ? order by version desc for update`, appID).Rows()
rows, err := db.Raw(`select version from releases where app_id = ? order by version desc`, appID).Rows()
if err != nil {
return version, err
}
Expand Down
60 changes: 60 additions & 0 deletions tests/empire/empire_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,66 @@ func TestEmpire_Deploy_ImageNotFound(t *testing.T) {
s.AssertExpectations(t)
}

func TestEmpire_Deploy_Concurrent(t *testing.T) {
e := empiretest.NewEmpire(t)
s := new(mockScheduler)
e.Scheduler = scheduler.NewFakeScheduler()
e.ExtractProcfile = func(ctx context.Context, img image.Image, w io.Writer) (procfile.Procfile, error) {
return nil, nil
}

user := &empire.User{Name: "ejholmes"}

// Create the first release for this app.
r, err := e.Deploy(context.Background(), empire.DeploymentsCreateOpts{
User: user,
Output: ioutil.Discard,
Image: image.Image{Repository: "remind101/acme-inc"},
})
assert.NoError(t, err)
assert.Equal(t, 1, r.Version)

// We'll use the procfile extractor to synchronize two concurrent
// deployments.
v2Started, v3Started := make(chan struct{}), make(chan struct{})
e.ExtractProcfile = func(ctx context.Context, img image.Image, w io.Writer) (procfile.Procfile, error) {
switch img.Tag {
case "v2":
close(v2Started)
<-v3Started
case "v3":
close(v3Started)
}
return nil, nil
}

v2Done := make(chan struct{})
go func() {
r, err = e.Deploy(context.Background(), empire.DeploymentsCreateOpts{
User: user,
Output: ioutil.Discard,
Image: image.Image{Repository: "remind101/acme-inc", Tag: "v2"},
})
assert.NoError(t, err)
assert.Equal(t, 2, r.Version)
close(v2Done)
}()

<-v2Started

r, err = e.Deploy(context.Background(), empire.DeploymentsCreateOpts{
User: user,
Output: ioutil.Discard,
Image: image.Image{Repository: "remind101/acme-inc", Tag: "v3"},
})
assert.NoError(t, err)
assert.Equal(t, 3, r.Version)

<-v2Done

s.AssertExpectations(t)
}

func TestEmpire_Set(t *testing.T) {
e := empiretest.NewEmpire(t)
s := new(mockScheduler)
Expand Down

0 comments on commit 395d91f

Please sign in to comment.