diff --git a/agent/docker.go b/agent/docker.go index f35f7fc8..3ac3fd6d 100644 --- a/agent/docker.go +++ b/agent/docker.go @@ -65,7 +65,6 @@ type dockerManager struct { dockerVersionChecked bool // Whether a version probe has completed successfully isWindows bool // Whether the Docker Engine API is running on Windows buf *bytes.Buffer // Buffer to store and read response bodies - decoder *json.Decoder // Reusable JSON decoder that reads from buf apiStats *container.ApiStats // Reusable API stats object excludeContainers []string // Patterns to exclude containers by name usingPodman bool // Whether the Docker Engine API is running on Podman @@ -747,20 +746,18 @@ func (dm *dockerManager) applyDockerVersionInfo(serverHeader string, versionInfo } } -// Decodes Docker API JSON response using a reusable buffer and decoder. Not thread safe. +// Decodes a Docker API JSON response using a reusable buffer. Not thread safe. func (dm *dockerManager) decode(resp *http.Response, d any) error { if dm.buf == nil { // initialize buffer with 256kb starting size dm.buf = bytes.NewBuffer(make([]byte, 0, 1024*256)) - dm.decoder = json.NewDecoder(dm.buf) } defer resp.Body.Close() defer dm.buf.Reset() - _, err := dm.buf.ReadFrom(resp.Body) - if err != nil { + if _, err := dm.buf.ReadFrom(resp.Body); err != nil { return err } - return dm.decoder.Decode(d) + return json.Unmarshal(dm.buf.Bytes(), d) } // Test docker / podman sockets and return if one exists diff --git a/agent/docker_test.go b/agent/docker_test.go index 9186a756..d4485084 100644 --- a/agent/docker_test.go +++ b/agent/docker_test.go @@ -804,6 +804,24 @@ func TestGetDockerStatsRetriesVersionCheckUntilSuccess(t *testing.T) { assert.Equal(t, 2, requestCounts["/version"]) } +// A failed decode must not break later decodes. Previously the reused json.Decoder +// stayed desynced after one truncated response, breaking decode until restart. +func TestDecodeRecoversFromError(t *testing.T) { + dm := &dockerManager{} + + // truncated JSON: body reads fine, decode fails + var bad []container.ApiInfo + err := dm.decode(&http.Response{Body: io.NopCloser(strings.NewReader(`[{"Id":"abc`))}, &bad) + require.Error(t, err) + + // the next decode must still succeed + var good []container.ApiInfo + err = dm.decode(&http.Response{Body: io.NopCloser(strings.NewReader(`[{"Id":"abcdef012345","Names":["/ok"]}]`))}, &good) + require.NoError(t, err) + require.Len(t, good, 1) + assert.Equal(t, "abcdef012345", good[0].Id) +} + func TestCycleCpuDeltas(t *testing.T) { dm := &dockerManager{ lastCpuContainer: map[uint16]map[string]uint64{