Skip to content

storage: fix an error message when trying to backup to local drive on… #41164

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

Merged
merged 1 commit into from
Oct 3, 2019

Conversation

darinpp
Copy link
Contributor

@darinpp darinpp commented Sep 27, 2019

… Windows

An often occuring pattern is:

{
...
f, _ := os.Create(someFile)
defer f.Close()
...rename or delete the file...
}

This does not work on Windows by default. It can be made to work (kindof) when
the file is opened with a special flag but the behaviour is not the same as
on a posix system.
There seems to be an effort to fix this in go as seen in issue https://github.com/golang/go/issues/32088
Also the default for the newer Windows versions seems to be to allow this https://github.com/golang/go/issues/32088#issuecomment-502850674
Until then - we should do an extra close before remove or delete (assuming these are the last operations).

Fixes #41085

Release justification: bug fix for existing functionality

Release note: None

@darinpp darinpp requested review from dt and danhhz September 27, 2019 17:04
@cockroach-teamcity
Copy link
Member

This change is Reviewable

Copy link
Collaborator

@petermattis petermattis left a comment

Choose a reason for hiding this comment

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

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @danhhz, @darinpp, and @dt)


pkg/ccl/storageccl/export_storage.go, line 345 at r1 (raw file):

		return errors.Wrapf(err, "syncing to local export tmp file %q", tmpP)
	}
	f.Close()

If io.Copy or f.Sync() return an error we'll leak this open file. Another alternative is to move the rename into a defer. You'd have to give name the error return value. Something like:

func (l *localFileStorage) WriteFile(...) (err error) {

  f, err := os.Create(tmpP)
  ...
  defer func() {
    f.Close()
    if err == nil {
      err = errors.Wrapf(os.Rename(tmpP, p), "renaming to local export file %q", p)
    }
  }()

  // Copy, Sync, etc.
}

@dt
Copy link
Member

dt commented Sep 27, 2019

I'd also be fine with just expanding the defer by hand, putting f.Close() before each of the two error returns, given the small number of early returns.

That said, given the numerous existing caveats with nodelocal, I'm also okay with it just not working on Windows for now, since I'm also wary of introducing any potential leaked FDs or other complexity this late in the release.

@darinpp
Copy link
Contributor Author

darinpp commented Sep 27, 2019

Reviewable status: :shipit: complete! 0 of 0 LGTMs obtained (waiting on @danhhz, @darinpp, and @dt)

pkg/ccl/storageccl/export_storage.go, line 345 at r1 (raw file):

		return errors.Wrapf(err, "syncing to local export tmp file %q", tmpP)
	}
	f.Close()

If io.Copy or f.Sync() return an error we'll leak this open file. Another alternative is to move the rename into a defer. You'd have to give name the error return value. Something like:

func (l *localFileStorage) WriteFile(...) (err error) {

  f, err := os.Create(tmpP)
  ...
  defer func() {
    f.Close()
    if err == nil {
      err = errors.Wrapf(os.Rename(tmpP, p), "renaming to local export file %q", p)
    }
  }()

  // Copy, Sync, etc.
}

This is a good point but deferring the rename doesn't allow us to return rename errors. So I simply added an extra defer f.Close() as there is no harm to always do an extra Close .

@darinpp
Copy link
Contributor Author

darinpp commented Sep 27, 2019

I'd also be fine with just expanding the defer by hand, putting f.Close() before each of the two error returns, given the small number of early returns.

That said, given the numerous existing caveats with nodelocal, I'm also okay with it just not working on Windows for now, since I'm also wary of introducing any potential leaked FDs or other complexity this late in the release.

I changed it so we will now have always the deferred Close. So the new code is just an addition to the existing code and simply closes the file before rename.

@darinpp darinpp force-pushed the fix_defer_close branch 2 times, most recently from 9ac235e to 5fbbb44 Compare September 27, 2019 20:59
@dt
Copy link
Member

dt commented Sep 27, 2019

is it okay to double-close?

@darinpp
Copy link
Contributor Author

darinpp commented Sep 27, 2019

is it okay to double-close?

Yes. You will get an error file already closed the second time that will be ignored for the deferred close.

@petermattis
Copy link
Collaborator

petermattis commented Sep 27, 2019

Yes. You will get an error file already closed the second time that will be ignored for the deferred close.

Is that true on Unix? close(2); close(2) might actually succeed if another thread happened to open file descriptor 2 in the interim. Note that you can change the err return value from the defer with my suggestion:

defer func() {
  if err == nil {
    err = <rename>
  }
}

@dt
Copy link
Member

dt commented Sep 27, 2019

I'd find it a tad easier to follow if we just got rid of the defer and called close in the two early returns and then where we actually want to close the file / 2¢

@darinpp
Copy link
Contributor Author

darinpp commented Sep 27, 2019

Yes. You will get an error file already closed the second time that will be ignored for the deferred close.

Is that true on Unix? close(2); close(2) might actually succeed if another thread happened to open file descriptor 2 in the interim. Note that you can change the err return value from the defer with my suggestion:

defer func() {
  if err == nil {
    err = <rename>
  }
}

Ok. I changed it back to do the rename in the defer.
The problem with closing twice doesn't exist as the second close is not just blindly passed to the OS. The error message is generated by GO and the second call is not allowed to result in a syscall,
But I can imagine some platform where this is not the case and your concern is valid.

Copy link
Collaborator

@petermattis petermattis left a comment

Choose a reason for hiding this comment

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

:lgtm: Thanks for checking further into the double-close. This structure makes me feel safer.

Reviewable status: :shipit: complete! 1 of 0 LGTMs obtained (waiting on @danhhz and @petermattis)

@darinpp
Copy link
Contributor Author

darinpp commented Sep 28, 2019

bors r+

@craig
Copy link
Contributor

craig bot commented Sep 28, 2019

Build failed

@darinpp
Copy link
Contributor Author

darinpp commented Sep 29, 2019

bors r+

craig bot pushed a commit that referenced this pull request Sep 29, 2019
41164: storage: fix an error message when trying to backup to local drive on… r=darinpp a=darinpp

… Windows

An often occuring pattern is:
```
{
...
f, _ := os.Create(someFile)
defer f.Close()
...rename or delete the file...
}
```
This does not work on Windows by default. It can be made to work (kindof) when
the file is opened with a special flag but the behaviour is not the same as
on a posix system.
There seems to be an effort to fix this in go as seen here golang/go#32088
Also the default for the newer Windows versions seems to be to allow this golang/go#32088 (comment)
Until then - we should do an extra close before remove or delete (assuming these are the last operations).

Fixes #41085

Release justification: bug fix for existing functionality

Release note: None

Co-authored-by: Darin <[email protected]>
@craig
Copy link
Contributor

craig bot commented Sep 29, 2019

Build failed

… Windows

An often occuring pattern is:
{
...
f, _ := os.Create(someFile)
defer f.Close()
...rename or delete the file...
}

This does not work on Windows by default. It can be made to work (kindof) when
the file is opened with a special flag but the behaviour is not the same as
on a posix system.
There seems to be an effort to fix this is go as seen here
https://github.com/golang/go/issues/<i></i>32088
Also the default for the newer Windows versions seems to be to allow this
https://github.com/golang/go/issues/<i></i>32088#issuecomment-502850674
Until then - we should defer a remove or delete to be done after the Close.

Fixes cockroachdb#41085

Release justification: bug fix for existing functionality

Release note: None
@darinpp
Copy link
Contributor Author

darinpp commented Oct 3, 2019

bors r+

craig bot pushed a commit that referenced this pull request Oct 3, 2019
41164: storage: fix an error message when trying to backup to local drive on… r=darinpp a=darinpp

… Windows

An often occuring pattern is:
```
{
...
f, _ := os.Create(someFile)
defer f.Close()
...rename or delete the file...
}
```
This does not work on Windows by default. It can be made to work (kindof) when
the file is opened with a special flag but the behaviour is not the same as
on a posix system.
There seems to be an effort to fix this in go as seen in issue  https://github.com/golang/go/issues/<i></i>32088
Also the default for the newer Windows versions seems to be to allow this https://github.com/golang/go/issues/<i></i>32088#issuecomment-502850674
Until then - we should do an extra close before remove or delete (assuming these are the last operations).

Fixes #41085

Release justification: bug fix for existing functionality

Release note: None

Co-authored-by: Darin <[email protected]>
@craig
Copy link
Contributor

craig bot commented Oct 3, 2019

Build succeeded

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unable to backup to local drive on Windows
4 participants