-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
proposal: io: add OnceCloser helper #25408
Comments
This is a possible alternative to #25390. |
On one hand this is a little simple for the stdlib but on the other it's a nasty thing everyone has to workaround at some point (and a cleaner solution than any I've come up with in the past). If it goes in, it would make more sense to put this in io/ioutil next to NopCloser. Since the wrapped Closer this returns hides all the methods except Close, perhaps it should just return |
I like everything about this proposal save the name of the type. Then again, CloseOncer is worse 😞 |
Yeah, ioutil could be a better place for this. |
IdempotentCloser because that word is super fun to say. But, seriously, I like OnceCloser because it's simple and evocative of its sync.Once + io.Closer nature. |
It would make me sad to put anything more in That said, we could always clean it up if/when we clean up |
I see arguments for both |
This approach has the advantage that it requires no changes to the (not saying we shouldn't add this helper for Go1 at least) |
I think that those 10 SLOC could and should be written outside the stdlib in user code when it makes sense, which I'm not sure can happen in proper code. The helper reminds me of something similar I think I saw somewhere for preventing closing of a closed channel. If it's not sound code in the case of channel, why it is in the case of an io.Closer? |
Closing a channel does not return an error that needs to be checked, so a channel-close is much easier to defer correctly. But perhaps that is an argument for not synchronizing |
@cznic, the difference between a channel and an io.Closer is that a channel is an implementation, and io.Closer is an interface. The channel behavior is documented. The io.Closer expected behavior on double-Close was never documented (on whether callers should not double Close, or how/whether implementations should handle double Close), so now we have tons of implementations of io.Closer with different behaviors. They roughly fall into three classes of behavior with respect to double closes:
The final case is often because they were implemented using a channel underneath. But in general, as a user of an io.Closer, it's not safe to double Close one because you don't know what it's going to do, and you have to assume it might deadlock or crash. The OnceCloser sanitizes its behavior.
We've had it in https://godoc.org/go4.org/types#NewOnceCloser for over 2 years, and it was in the @perkeep codebase for years before that. The point of putting in the standard library is so it's easily accessible for people who want to sanitize an io.Closer. |
Why should there be a helper to synchronize an
There's an app for that |
As I noted above: the main use-case for this proposal is for error-handling, not concurrency.
Failing to call |
This depends on what the suggested patterns / best practices are for using closers, which is tied up with error handling. I don't think we know that yet. |
Suggested patterns/best-practices appear to be:
Unfortunately, you can follow at most two of these. As a user, I think my preferred answer would be "define multiple close calls to be safe after all". Of course, that breaks at least some existing code, but I don't know how many cases there are where it's unsafe and would still be unsafe even if it became safe for channels. (Defining previously-undefined behavior is usually a lot safer than the other way around.) |
That's true of implementations, but not of interfaces. Adding new behavior to an interface potentially changes a correct implementation into an incorrect one, often with no visible error at compile-time. As Russ notes in https://research.swtch.com/vgo-import, “when you change a function’s behavior, you also change its name.” That's especially vital when you don't control the implementation of that function. |
... Yes, in retrospect that's pretty obvious and I'm not sure why I missed it. The current situation is sort of ugly; in practice, people tend not to actually do the full work to implement OnceCloser, thus there's mutexes and checks for nil and so on. And if two things execute So, there's a definite actual difficulty, and the workarounds for it are difficult to do quite correctly. |
Proposal, to add to the
io
package:With implementation like:
This would let people safely write code like:
... without worrying about double calls to
file.Close
, which is undefined since https://golang.org/cl/8575043https://golang.org/pkg/io/#Closer
/cc @bcmills @mpvl @ianlancetaylor
The text was updated successfully, but these errors were encountered: