Skip to content

Race Condition Risk in BytesCounter.Read Method in Multi-goroutine EnvironmentΒ #98

@Sky-Bridge

Description

@Sky-Bridge

My English is not good, using a translation software, if there are any issues with the sentences, please understand.

When I tested locally, I found that the results with --no-pre-allocate and without it are completely different.
If you do not use --no-pre-allocate, the result is very low.

[2025-04-21 19:11:40] speedtest-cli/dist git:(master) 
$ ./librespeed-cli --no-download --local-json lo.json
Using local JSON server list: lo.json
Selecting the fastest server based on ping
Selected server: Go Backend [127.0.0.1]
You're testing from: 127.0.0.1 - localhost IPv4 access
Ping: 0.00 ms   Jitter: 0.00 ms
Download test is disabled
Upload rate:    1703.53 Mbps

[2025-04-21 19:12:12] speedtest-cli/dist git:(master) 
$ ./librespeed-cli --no-download --local-json lo.json --no-pre-allocate
Using local JSON server list: lo.json
Selecting the fastest server based on ping
Selected server: Go Backend [127.0.0.1]
You're testing from: 127.0.0.1 - localhost IPv4 access
Ping: 0.00 ms   Jitter: 0.00 ms
Download test is disabled
Pre-allocation is disabled, performance might be lower!
Upload rate:    13657.71 Mbps

or change Concurrent HTTP requests being made

[2025-04-21 19:40:21] speedtest-cli/dist git:(master) 
$ ./librespeed-cli --no-download --local-json lo.json --concurrent 1   
Using local JSON server list: lo.json
Selecting the fastest server based on ping
Selected server: Go Backend [127.0.0.1]
You're testing from: 127.0.0.1 - localhost IPv4 access
Ping: 0.00 ms   Jitter: 0.00 ms
Download test is disabled
Upload rate:    32043.91 Mbps
[2025-04-21 19:41:12] speedtest-cli/dist git:(master) 
$ ./librespeed-cli --no-download --local-json lo.json --concurrent 3
Using local JSON server list: lo.json
Selecting the fastest server based on ping
Selected server: Go Backend [127.0.0.1]
You're testing from: 127.0.0.1 - localhost IPv4 access
Ping: 0.00 ms   Jitter: 0.00 ms
Download test is disabled
Upload rate:    1042.12 Mbps

After investigation, I found that c.reader.Read(p) is not locked.

// Read implements io.Reader
func (c *BytesCounter) Read(p []byte) (int, error) {
	n, err := c.reader.Read(p)
	c.lock.Lock()
	c.total += uint64(n)
	c.pos += n
	if c.pos == c.uploadSize {
		c.resetReader()
	}
	c.lock.Unlock()

	return n, err
}

If I lock it, I find that it is the expected result.

// Read implements io.Reader
func (c *BytesCounter) Read(p []byte) (int, error) {
	c.lock.Lock()
	n, err := c.reader.Read(p)
	c.total += uint64(n)
	c.pos += n
	if c.pos == c.uploadSize {
		c.resetReader()
	}
	c.lock.Unlock()

	return n, err
}
[2025-04-21 19:17:24] speedtest-cli/dist/client/speedtest-cli git:(master*) 
$ go run main.go --no-download --local-json lo.json 
Using local JSON server list: lo.json
Selecting the fastest server based on ping
Selected server: Go Backend [127.0.0.1]
You're testing from: 127.0.0.1 - localhost IPv4 access
Ping: 0.00 ms   Jitter: 0.00 ms
Download test is disabled
Upload rate:    45348.68 Mbps

[2025-04-21 19:28:26] speedtest-cli/dist/client/speedtest-cli git:(master*) 
$ go run main.go --no-download --local-json lo.json --no-pre-allocate
Using local JSON server list: lo.json
Selecting the fastest server based on ping
Selected server: Go Backend [127.0.0.1]
You're testing from: 127.0.0.1 - localhost IPv4 access
Ping: 0.00 ms   Jitter: 0.00 ms
Download test is disabled
Pre-allocation is disabled, performance might be lower!
Upload rate:    13639.77 Mbps

Problem reproduction method

Start a server

cd server/speedtest-go
go run main.go

use local json

[
  {
    "id": 1,
    "name": "Go Backend",
    "server": "http://127.0.0.1:8989",
    "dlURL": "garbage",
    "ulURL": "empty",
    "pingURL": "empty",
    "getIpURL": "getIP"
  }
]

run cli

./librespeed-cli --no-download --local-json lo.json --no-pre-allocate
./librespeed-cli --no-download --local-json lo.json

Why don't we lock it, there are other reasons, or this is a bug

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions