Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ go 1.23.7
require (
github.com/containerd/containerd/v2 v2.0.4
github.com/containerd/platforms v1.0.0-rc.1
github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57
github.com/docker/model-distribution v0.0.0-20250605145514-b377026db94a
github.com/google/go-containerregistry v0.20.3
github.com/jaypipes/ghw v0.16.0
github.com/mattn/go-shellwords v1.0.12
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/sirupsen/logrus v1.9.3
Expand Down Expand Up @@ -35,7 +36,6 @@ require (
github.com/jaypipes/pcidb v1.0.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57 h1:ZqfKknb+0/uJid8XLFwSl/osjE+WuS6o6I3dh3ZqO4U=
github.com/docker/model-distribution v0.0.0-20250512190053-b3792c042d57/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c=
github.com/docker/model-distribution v0.0.0-20250605145514-b377026db94a h1:rOxhNMoVwCchu79efuujmpL7kCbKKvzFFwvuWmJKbI4=
github.com/docker/model-distribution v0.0.0-20250605145514-b377026db94a/go.mod h1:dThpO9JoG5Px3i+rTluAeZcqLGw8C0qepuEL4gL2o/c=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down
40 changes: 37 additions & 3 deletions pkg/inference/models/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"testing"

"github.com/google/go-containerregistry/pkg/registry"
Expand All @@ -20,6 +21,39 @@ import (
"github.com/sirupsen/logrus"
)

// mutexResponseRecorder wraps httptest.ResponseRecorder with a mutex for thread-safe access
type mutexResponseRecorder struct {
*httptest.ResponseRecorder
mu sync.Mutex
}

func newMutexResponseRecorder() *mutexResponseRecorder {
return &mutexResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
}

// WriteHeader wraps the underlying WriteHeader with mutex protection
func (m *mutexResponseRecorder) WriteHeader(code int) {
m.mu.Lock()
defer m.mu.Unlock()
m.ResponseRecorder.WriteHeader(code)
}

// Write wraps the underlying Write with mutex protection
func (m *mutexResponseRecorder) Write(b []byte) (int, error) {
m.mu.Lock()
defer m.mu.Unlock()
return m.ResponseRecorder.Write(b)
}

// Header wraps the underlying Header with mutex protection
func (m *mutexResponseRecorder) Header() http.Header {
m.mu.Lock()
defer m.mu.Unlock()
return m.ResponseRecorder.Header()
}

// getProjectRoot returns the absolute path to the project root directory
func getProjectRoot(t *testing.T) string {
// Start from the current test file's directory
Expand Down Expand Up @@ -119,7 +153,7 @@ func TestPullModel(t *testing.T) {
r.Header.Set("Accept", tt.acceptHeader)
}

w := httptest.NewRecorder()
w := newMutexResponseRecorder()
Copy link
Contributor

@ekcasey ekcasey Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we can assume the real http.ResponseWriter is thread safe and we might be papering over real issues with this change to use a thread-safe version in the tests. It looks to me like the concurrent writes are happening within the pull implementation.

My best guess is this is that this is appearing now b/c with the per-layer progress reporting we are no longer waiting for the progress writing from one layer to complete before we start writing progress for the next layer.

err = m.PullModel(tag, r, w)
if err != nil {
t.Fatalf("Failed to pull model: %v", err)
Expand Down Expand Up @@ -229,7 +263,7 @@ func TestHandleGetModel(t *testing.T) {
// First pull the model if we're testing local access
if !tt.remote && !strings.Contains(tt.modelName, "nonexistent") {
r := httptest.NewRequest("POST", "/models/create", strings.NewReader(`{"from": "`+tt.modelName+`"}`))
w := httptest.NewRecorder()
w := newMutexResponseRecorder()
err = m.PullModel(tt.modelName, r, w)
if err != nil {
t.Fatalf("Failed to pull model: %v", err)
Expand All @@ -242,7 +276,7 @@ func TestHandleGetModel(t *testing.T) {
path += "?remote=true"
}
r := httptest.NewRequest("GET", path, nil)
w := httptest.NewRecorder()
w := newMutexResponseRecorder()

// Set the path value for {name} so r.PathValue("name") works
r.SetPathValue("name", tt.modelName)
Expand Down