f99aq8ove's blog

golang の net.http で http keepalive を一定時間で切る方法(transport を使い回す)

tag: golang and http
25 March 2019

このエントリは 2019-03-25 に書かれました。 内容が古くなっていたり、もはや正しくないこともありますので、十分検証を行ってください。

golang の net.http で http keepalive を一定時間で切る方法(transport を使い回す)

golang の net.http で http keepalive を一定時間で切る方法 で Transport を一定期間で作り変える実装を書いてみましたが、メモリリークする可能性があるというという話もあり、一定期間で2つの transport を入れ替えて使うようにしてみました。

package main

import (
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"runtime"
	"sync"
	"time"
)

type clientHolder struct {
	client          *http.Client
	clientPool      chan *http.Client
	sync            sync.Mutex
	refreshDeadline time.Time
}

var ch = clientHolder{
	clientPool: make(chan *http.Client, 1),
}

func (ch *clientHolder) getClient() *http.Client {
	ch.sync.Lock()
	defer ch.sync.Unlock()

	if ch.client == nil || time.Now().After(ch.refreshDeadline) {
		var newClient *http.Client
		if len(ch.clientPool) > 0 {
			newClient = <-ch.clientPool
		} else {
			// https://golang.org/src/net/http/transport.go
			newTransport := &http.Transport{
				Proxy: http.ProxyFromEnvironment,
				DialContext: (&net.Dialer{
					Timeout:   30 * time.Second,
					KeepAlive: 30 * time.Second,
					DualStack: true,
				}).DialContext,
				MaxIdleConns: 100,
				//IdleConnTimeout:       90 * time.Second,
				IdleConnTimeout:       9 * time.Second,
				TLSHandshakeTimeout:   10 * time.Second,
				ExpectContinueTimeout: 1 * time.Second,
			}
			newClient = &http.Client{
				Transport: newTransport,
			}

			println("new client")
		}

		if ch.client != nil {
			ch.clientPool <- ch.client
		}

		ch.client = newClient
		ch.refreshDeadline = time.Now().Add(10 * time.Second)
	}

	return ch.client
}

func main() {
	go func() {
		ticker := time.NewTicker(1 * time.Second)
		for range ticker.C {
			printMemStats()
		}
	}()

	for {
		access(ch.getClient())

		time.Sleep(10 * time.Millisecond)
	}
}

func access(client *http.Client) {
	resp, err := client.Get("http://10.0.1.1:40080/test")
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	// println(resp.Status)

	/*body*/
	_, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		panic(err)
	}
	// println(string(body))
}

func printMemStats() {
	runtime.GC()
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	fmt.Printf("Alloc: %v bytes, TotalAlloc: %v bytes, Sys: %v bytes, NumGC: %v\n", m.Alloc, m.TotalAlloc, m.Sys, m.NumGC)
}

Related Posts

Get new posts by email: