Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always use transaction in JSONFile.Save() #1724

Merged
merged 2 commits into from
Jan 13, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions go/engine/device_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ func (d *DeviceRegister) Run(ctx *Context) error {
if err := wr.SetDeviceID(d.deviceID); err != nil {
return err
}
if err := wr.Save(); err != nil {
return err
}
ctx.LogUI.Debug("Setting Device ID to %s", d.deviceID)
}

Expand Down
22 changes: 8 additions & 14 deletions go/libkb/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ package libkb
import (
"errors"
"fmt"
"time"

keybase1 "github.com/keybase/client/go/protocol"
triplesec "github.com/keybase/go-triplesec"
"time"
)

type timedGenericKey struct {
Expand Down Expand Up @@ -383,34 +384,27 @@ func (a *Account) UserInfo() (uid keybase1.UID, username NormalizedUsername,
// SaveState saves the logins state to memory, and to the user
// config file.
func (a *Account) SaveState(sessionID, csrf string, username NormalizedUsername, uid keybase1.UID, deviceID keybase1.DeviceID) error {
cw, err := a.newUserConfigWriter(username, uid, deviceID)
if err != nil {
return err
}
if err := cw.Save(); err != nil {
if err := a.saveUserConfig(username, uid, deviceID); err != nil {
return err
}
return a.LocalSession().SetLoggedIn(sessionID, csrf, username, uid, deviceID)
}

func (a *Account) newUserConfigWriter(username NormalizedUsername, uid keybase1.UID, deviceID keybase1.DeviceID) (ConfigWriter, error) {
func (a *Account) saveUserConfig(username NormalizedUsername, uid keybase1.UID, deviceID keybase1.DeviceID) error {
cw := a.G().Env.GetConfigWriter()
if cw == nil {
return nil, NoConfigWriterError{}
return NoConfigWriterError{}
}

if err := a.LoginSession().Clear(); err != nil {
return nil, err
return err
}
salt, err := a.LoginSession().Salt()
if err != nil {
return nil, err
}
if err := cw.SetUserConfig(NewUserConfig(uid, username, salt, deviceID), false); err != nil {
return nil, err
return err
}

return cw, nil
return cw.SetUserConfig(NewUserConfig(uid, username, salt, deviceID), false)
}

func (a *Account) Dump() {
Expand Down
1 change: 0 additions & 1 deletion go/libkb/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ type ConfigWriter interface {
SetUpdatePreferenceSnoozeUntil(keybase1.Time) error
SetUpdateLastChecked(keybase1.Time) error
Reset()
Save() error
BeginTransaction() (ConfigWriterTransacter, error)
}

Expand Down
81 changes: 56 additions & 25 deletions go/libkb/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ package libkb

import (
"encoding/json"
"errors"
"fmt"
jsonw "github.com/keybase/go-jsonw"
"io"
"os"
"sync"

jsonw "github.com/keybase/go-jsonw"
)

type jsonFileTransaction struct {
Expand Down Expand Up @@ -84,14 +86,6 @@ func (f *JSONFile) Nuke() error {
return err
}

func (f *JSONFile) getFilename() string {
tx := f.getTx()
if tx != nil {
return tx.tmpname
}
return f.filename
}

func (f *JSONFile) BeginTransaction() (ConfigWriterTransacter, error) {
tx, err := newJSONFileTransaction(f)
if err != nil {
Expand All @@ -114,10 +108,25 @@ func (f *JSONFile) setTx(tx *jsonFileTransaction) error {
return nil
}

func (f *JSONFile) getTx() *jsonFileTransaction {
func (f *JSONFile) getOrMakeTx() (*jsonFileTransaction, bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any more need for getTx?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not after above change is complete.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool so let's yank it

f.txMutex.Lock()
defer f.txMutex.Unlock()
return f.tx

// if a transaction exists, use it
if f.tx != nil {
return f.tx, false, nil
}

// make a new transaction
tx, err := newJSONFileTransaction(f)
if err != nil {
return nil, false, err
}

f.tx = tx

// return true so caller knows that a transaction was created
return f.tx, true, nil
}

func newJSONFileTransaction(f *JSONFile) (*jsonFileTransaction, error) {
Expand All @@ -131,13 +140,43 @@ func newJSONFileTransaction(f *JSONFile) (*jsonFileTransaction, error) {
}

func (f *JSONFile) Save() error {
if err := f.save(f.getFilename(), true, 0); err != nil {
tx, txCreated, err := f.getOrMakeTx()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this function be simplified at all? since it's only a 3-liner, maybe don't use a defer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't we want Abort to be called if Commit fails?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely, but can't we do that in the error case of save?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the Commit happens in capital Save() if it creates a transaction.

if err != nil {
return err
}
if txCreated {
// if Save() created a transaction, then abort it if it
// still exists on exit
defer func() {
if tx != nil {
tx.Abort()
}
}()
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f.getFilename should now never return the main file. maybe we should assert that somehow? Maybe we should pass tx.getFilename into save?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on this now...

if err := f.save(); err != nil {
return err
}

if txCreated {
// this Save() call created a transaction, so commit it
if err := tx.Commit(); err != nil {
return err
}

// Commit worked, clear the transaction so defer() doesn't
// abort it.
tx = nil
}

return nil
}

func (f *JSONFile) save(filename string, pretty bool, mode os.FileMode) (err error) {
func (f *JSONFile) save() (err error) {
if f.tx == nil {
return errors.New("save() called with nil transaction")
}
filename := f.tx.tmpname
f.G().Log.Debug("+ saving %s file %s", f.which, filename)

err = MakeParentDirs(filename)
Expand All @@ -162,25 +201,17 @@ func (f *JSONFile) save(filename string, pretty bool, mode os.FileMode) (err err
}
var writer *os.File
flags := (os.O_WRONLY | os.O_CREATE | os.O_TRUNC)
if mode == 0 {
mode = PermFile // By default, secrecy
}
writer, err = os.OpenFile(filename, flags, mode)
writer, err = os.OpenFile(filename, flags, PermFile)
if err != nil {
f.G().Log.Errorf("Failed to open %s file %s for writing: %s",
f.which, filename, err)
return err
}
defer writer.Close()

if pretty {
encoded, err := json.MarshalIndent(dat, "", " ")
if err == nil {
_, err = writer.Write(encoded)
}
} else {
encoder := json.NewEncoder(writer)
err = encoder.Encode(dat)
encoded, err := json.MarshalIndent(dat, "", " ")
if err == nil {
_, err = writer.Write(encoded)
}

if err != nil {
Expand Down