-
Notifications
You must be signed in to change notification settings - Fork 31
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
Introduce a cancelable.Closer type to manage net.Conn lifetimes #27
Conversation
I'm interested to know what you all think of this. It's an attempt to bring C++ smart pointer style resource management to Go. I think it does make things easier to manage, but I'm not sure if this really counts as "idiomatic Go"; maybe there are more idiomatic ways to write these functions so that I would have avoided the need for a Although I generally prefer to squash merge and preserve simple linear histories, for this PR I've followed @camh-'s style of having multiple atomic commits in a single PR. This separates the changes to error logging from the |
c33b4b1
to
a24a3e0
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like the idea - no comment on whether it is too C++ like - if it works, and makes for simpler more robust code, then go for it.
With all my comments, I ended up writing my own to see what it would look like with those comments addressed. Here's what I came up with:
// ErrCloser wraps an io.Closer with its own Close() method that ensures
// that the wrapped io.Closer is closed at most once and not at all if
// Release() is called before Close(). It is intended to be used with
// defer so the io.Closer is closed on the error paths out of a function,
// but the success path can call Release() to ensure that Close() does
// not actually close the io.Closer.
type ErrCloser struct {
io.Closer
}
// Release releases the wrapped io.Closer so that we will no longer try to
// close it when Close() is called.
func (ec *ErrCloser) Release() {
ec.Closer = nil
}
// Close closes the wrapped io.Closer if it has not already been closed by
// this ErrCloser. Close will release the wrapped io.Closer so this ErrCloser
// will not try to close it again.
func (ec *ErrCloser) Close() error {
if ec.Closer == nil {
return nil
}
if err := ec.Closer.Close(); err != nil {
return err
}
ec.Release()
return nil
}
I didn't test it - I just wrote it in the playground and it compiles, so it is ready to be shipped.
[edit: updated to make receivers to be pointers]
Here's an alternate idea that is a little more light weight (see also at https://goplay.space/#l_2QKOXTkGK):
Use it like:
[edited to simplify |
I was thinking of doing something similar earlier - the generic (not limited to I'm thinking that cancelling a
|
I like the naming by using the package I'm not sure for the use case of But I like this idea best and would be happy with just |
a24a3e0
to
1968292
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this PR has changes from your other PR (#28). Can you rebase it onto that branch and make that branch the base for this PR? It's hard to review the changes that are relevant to just this PR.
1968292
to
7ec0635
Compare
Now that I've merged #28, I've just rebased this on top of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
The
ProxyHandler.handleConnect()
andconnectViaProxy()
functions have a lot of early returns due to error handling, and each of these error handling blocks need to callnet.Conn.Close()
on various connections. Unfortunately, they can't use the standard defer idiom, because in the success case these functions will pass theirnet.Conn
s to other functions. As a result, there are a lot of manual calls tonet.Conn.Close()
, and it's easy to forget to do this (and I'm pretty sure I did in a few places).This pull request introduces a new type called
cancelable.Closer
, which is modelled on C++11'sstd::unique_ptr
. It holds "ownership" of anio.Closer
, and allows users to use the defer idiom to call theClose()
function. But once we've passed all the error handlers, a user can callCancel()
, which will release ownership of theio.Closer
, so that it can be returned or passed to another function/goroutine. It is safe to callcancelable.Closer.Close()
multiple times.A typical usage might look like this: