Move map to array (#3339)

* Move map to array

The map was not needed move to an array, see #1941 for the original
idea. That of course didn't apply anymore; make a super minimal change
to implements the idea from #1941

Signed-off-by: Miek Gieben <miek@miek.nl>

* Add total count

Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
Miek Gieben 2019-10-01 20:45:52 +01:00 committed by GitHub
parent 2d98d520b5
commit 575cea4496
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 53 additions and 30 deletions

View file

@ -2,7 +2,6 @@ package forward
import ( import (
"crypto/tls" "crypto/tls"
"net"
"sort" "sort"
"time" "time"
@ -17,9 +16,9 @@ type persistConn struct {
// Transport hold the persistent cache. // Transport hold the persistent cache.
type Transport struct { type Transport struct {
avgDialTime int64 // kind of average time of dial time avgDialTime int64 // kind of average time of dial time
conns map[string][]*persistConn // Buckets for udp, tcp and tcp-tls. conns [typeTotalCount][]*persistConn // Buckets for udp, tcp and tcp-tls.
expire time.Duration // After this duration a connection is expired. expire time.Duration // After this duration a connection is expired.
addr string addr string
tlsConfig *tls.Config tlsConfig *tls.Config
@ -32,7 +31,7 @@ type Transport struct {
func newTransport(addr string) *Transport { func newTransport(addr string) *Transport {
t := &Transport{ t := &Transport{
avgDialTime: int64(maxDialTimeout / 2), avgDialTime: int64(maxDialTimeout / 2),
conns: make(map[string][]*persistConn), conns: [typeTotalCount][]*persistConn{},
expire: defaultExpire, expire: defaultExpire,
addr: addr, addr: addr,
dial: make(chan string), dial: make(chan string),
@ -50,17 +49,18 @@ Wait:
for { for {
select { select {
case proto := <-t.dial: case proto := <-t.dial:
transtype := stringToTransportType(proto)
// take the last used conn - complexity O(1) // take the last used conn - complexity O(1)
if stack := t.conns[proto]; len(stack) > 0 { if stack := t.conns[transtype]; len(stack) > 0 {
pc := stack[len(stack)-1] pc := stack[len(stack)-1]
if time.Since(pc.used) < t.expire { if time.Since(pc.used) < t.expire {
// Found one, remove from pool and return this conn. // Found one, remove from pool and return this conn.
t.conns[proto] = stack[:len(stack)-1] t.conns[transtype] = stack[:len(stack)-1]
t.ret <- pc t.ret <- pc
continue Wait continue Wait
} }
// clear entire cache if the last conn is expired // clear entire cache if the last conn is expired
t.conns[proto] = nil t.conns[transtype] = nil
// now, the connections being passed to closeConns() are not reachable from // now, the connections being passed to closeConns() are not reachable from
// transport methods anymore. So, it's safe to close them in a separate goroutine // transport methods anymore. So, it's safe to close them in a separate goroutine
go closeConns(stack) go closeConns(stack)
@ -68,18 +68,8 @@ Wait:
t.ret <- nil t.ret <- nil
case pc := <-t.yield: case pc := <-t.yield:
// no proto here, infer from config and conn transtype := t.transportTypeFromConn(pc)
if _, ok := pc.c.Conn.(*net.UDPConn); ok { t.conns[transtype] = append(t.conns[transtype], pc)
t.conns["udp"] = append(t.conns["udp"], pc)
continue Wait
}
if t.tlsConfig == nil {
t.conns["tcp"] = append(t.conns["tcp"], pc)
continue Wait
}
t.conns["tcp-tls"] = append(t.conns["tcp-tls"], pc)
case <-ticker.C: case <-ticker.C:
t.cleanup(false) t.cleanup(false)
@ -102,12 +92,12 @@ func closeConns(conns []*persistConn) {
// cleanup removes connections from cache. // cleanup removes connections from cache.
func (t *Transport) cleanup(all bool) { func (t *Transport) cleanup(all bool) {
staleTime := time.Now().Add(-t.expire) staleTime := time.Now().Add(-t.expire)
for proto, stack := range t.conns { for transtype, stack := range t.conns {
if len(stack) == 0 { if len(stack) == 0 {
continue continue
} }
if all { if all {
t.conns[proto] = nil t.conns[transtype] = nil
// now, the connections being passed to closeConns() are not reachable from // now, the connections being passed to closeConns() are not reachable from
// transport methods anymore. So, it's safe to close them in a separate goroutine // transport methods anymore. So, it's safe to close them in a separate goroutine
go closeConns(stack) go closeConns(stack)
@ -121,7 +111,7 @@ func (t *Transport) cleanup(all bool) {
good := sort.Search(len(stack), func(i int) bool { good := sort.Search(len(stack), func(i int) bool {
return stack[i].used.After(staleTime) return stack[i].used.After(staleTime)
}) })
t.conns[proto] = stack[good:] t.conns[transtype] = stack[good:]
// now, the connections being passed to closeConns() are not reachable from // now, the connections being passed to closeConns() are not reachable from
// transport methods anymore. So, it's safe to close them in a separate goroutine // transport methods anymore. So, it's safe to close them in a separate goroutine
go closeConns(stack[:good]) go closeConns(stack[:good])

View file

@ -96,18 +96,14 @@ func TestCleanupAll(t *testing.T) {
c2, _ := dns.DialTimeout("udp", tr.addr, maxDialTimeout) c2, _ := dns.DialTimeout("udp", tr.addr, maxDialTimeout)
c3, _ := dns.DialTimeout("udp", tr.addr, maxDialTimeout) c3, _ := dns.DialTimeout("udp", tr.addr, maxDialTimeout)
tr.conns["udp"] = []*persistConn{ tr.conns[typeUdp] = []*persistConn{{c1, time.Now()}, {c2, time.Now()}, {c3, time.Now()}}
{c1, time.Now()},
{c2, time.Now()},
{c3, time.Now()},
}
if len(tr.conns["udp"]) != 3 { if len(tr.conns[typeUdp]) != 3 {
t.Error("Expected 3 connections") t.Error("Expected 3 connections")
} }
tr.cleanup(true) tr.cleanup(true)
if len(tr.conns["udp"]) > 0 { if len(tr.conns[typeUdp]) > 0 {
t.Error("Expected no cached connections") t.Error("Expected no cached connections")
} }
} }

37
plugin/forward/type.go Normal file
View file

@ -0,0 +1,37 @@
package forward
import "net"
type transportType int
const (
typeUdp transportType = iota
typeTcp
typeTls
typeTotalCount // keep this last
)
func stringToTransportType(s string) transportType {
switch s {
case "udp":
return typeUdp
case "tcp":
return typeTcp
case "tcp-tls":
return typeTls
}
return typeUdp
}
func (t *Transport) transportTypeFromConn(pc *persistConn) transportType {
if _, ok := pc.c.Conn.(*net.UDPConn); ok {
return typeUdp
}
if t.tlsConfig == nil {
return typeTcp
}
return typeTls
}