Load test program runs out of ports in Windows

I wrote a load test program to stress a web service, which runs fine on Linux.

However, on Windows it runs out of ports in net.Dial with this error:

Post http://localhost:9090: dial tcp [::1]:9090: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.

I have tried changing the registry to increase the available port count and reducing the WAIT_TIME as described in this post: Success Center

Does anyone know if there is a way to re-use ports so that Windows doesn’t run out of ports (or some other way to achieve this load test)?

My code is below:

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	"time"
)

type args struct {
	url     string
	body    []byte
	threads int
}

func main() {
	// 06-hammer url body threads
	if len(os.Args) != 4 {
		showUsage()
		return
	}

	var arg args
	arg.url = os.Args[1]
	body := os.Args[2]
	threads, err := strconv.Atoi(os.Args[3])
	if err != nil || len(arg.url) < 1 || len(body) < 1 {
		showUsage()
		return
	}
	arg.threads = threads
	arg.body, err = os.ReadFile(body)
	if err != nil {
		fmt.Printf("error reading '%v': %v \n", body, err)
		return
	}
	call(arg)
}

func showUsage() {
	fmt.Println("Usage:")
	fmt.Println("06-hammer url body threads")
	fmt.Println("Example:")
	fmt.Println(`06-hammer http://localhost:9090 "./body.json" 10`)
}

func call(arg args) {
	start := time.Now()
	var succ int
	var fail int
	var attempt int

	for {
		c := parallel(arg)

		attempt++
		for i := 0; i < arg.threads; i++ {
			result := <-c
			if result {
				succ++
			} else {
				fail++
			}
		}

		if time.Since(start) >= time.Duration(time.Second*5) {
			log.Printf("success: %v fail: %v elapsed: %v, avg: %v \n", succ, fail, time.Since(start), time.Duration(int64(time.Since(start))/int64(attempt)))
			start = time.Now()
			succ = 0
			fail = 0
			attempt = 0
		}
	}
}

func parallel(arg args) chan bool {
	out := make(chan bool)

	for i := 0; i < arg.threads; i++ {
		go caller(arg, out)
	}

	return out
}

func caller(arg args, c chan bool) {
	var defaultTransport http.RoundTripper = &http.Transport{Proxy: nil, DisableKeepAlives: true}
	client := &http.Client{Transport: defaultTransport}

	r, err := client.Post(arg.url, "application/json", bytes.NewReader(arg.body))

	if err != nil {
		c <- false
		return
	}
	defer r.Body.Close()

	_, err = ioutil.ReadAll(r.Body)
	if err != nil {
		c <- false
		return
	}

	c <- true
}

This GitHub issue seems to answer your question:

The answer seems to be to set a MaxUserPort registry key to 65535 and a TcpTimedWaitDelay key to 30: Settings that can be Modified to Improve Network Performance - BizTalk Server | Microsoft Docs

Hi Sean,

Thanks for replying!

I’ve already tried those registry settings as mentioned in the original question (to no avail).

Do you think it makes sense to pool the connections somehow for the net.Dial function?

I have an idea: It could be because you’re creating new *http.Clients ever time caller is called. *http.Clients cache their connections so that new requests don’t always require opening new connections.

Depending on what you’re stress-testing, you either want to:

  1. Create the *http.Clients ahead of time and re-use them (this will mostly stress the API endpoint and not stress the rest of the network stack as much)

  2. Call (*http.Client).CloseIdleConnections before returning from caller (this will include the performance of opening and closing connections in your stress test which may or may not be what you want).