use a finalizer to close the h2quic.RoundTripper

The finalizer is executed when the RoundTripper is garbage collected.
This is not a perfect solution, since there are situations when an
unneeded RoundTripper is not garbage collected, e.g. when the program
exits before the GC ran. In those cases, the server will run into the
idle timeout and eventually close the connection  on its side.
This commit is contained in:
Marten Seemann
2017-07-12 19:20:21 +07:00
parent bf6030a855
commit 65cea185bd
2 changed files with 19 additions and 0 deletions

View File

@@ -11,6 +11,8 @@ import (
quic "github.com/lucas-clemente/quic-go"
"runtime"
"golang.org/x/net/lex/httplex"
)
@@ -91,6 +93,7 @@ func (r *RoundTripper) getClient(hostname string) http.RoundTripper {
defer r.mutex.Unlock()
if r.clients == nil {
runtime.SetFinalizer(r, finalizer)
r.clients = make(map[string]roundTripCloser)
}
@@ -102,6 +105,10 @@ func (r *RoundTripper) getClient(hostname string) http.RoundTripper {
return client
}
func finalizer(r *RoundTripper) {
_ = r.Close()
}
// Close closes the QUIC connections that this RoundTripper has used
func (r *RoundTripper) Close() error {
r.mutex.Lock()

View File

@@ -6,6 +6,7 @@ import (
"errors"
"io"
"net/http"
"runtime"
"time"
quic "github.com/lucas-clemente/quic-go"
@@ -196,5 +197,16 @@ var _ = Describe("RoundTripper", func() {
Expect(err).ToNot(HaveOccurred())
Expect(len(rt.clients)).To(BeZero())
})
It("runs Close when the RoundTripper is garbage collected", func() {
// this is set by getClient, but we can't do that while at the same time injecting the mockClient
runtime.SetFinalizer(rt, finalizer)
rt.clients = make(map[string]roundTripCloser)
cl := &mockClient{}
rt.clients["foo.bar"] = cl
rt = nil // lose the references to the RoundTripper, such that it can be garbage collected
runtime.GC()
Eventually(func() bool { return cl.closed }).Should(BeTrue())
})
})
})