Browse Source

avoid hanging uploads

pull/7490/head
ljluestc 2 weeks ago
parent
commit
8773ca32fd
  1. 11
      weed/operation/upload_content.go
  2. 61
      weed/operation/upload_content_test.go

11
weed/operation/upload_content.go

@ -371,7 +371,16 @@ func (uploader *Uploader) upload_content(ctx context.Context, fillBufferFunction
} else {
reqReader = bytes.NewReader(option.BytesBuffer.Bytes())
}
req, postErr := http.NewRequest(http.MethodPost, option.UploadUrl, reqReader)
// Ensure the request will not hang indefinitely: if no deadline is set on ctx,
// apply a conservative default timeout.
ctxWithTimeout := ctx
if _, hasDeadline := ctx.Deadline(); !hasDeadline {
var cancel context.CancelFunc
// Default timeout chosen to be generous for large uploads but finite to avoid hangs.
ctxWithTimeout, cancel = context.WithTimeout(ctx, 10*time.Minute)
defer cancel()
}
req, postErr := http.NewRequestWithContext(ctxWithTimeout, http.MethodPost, option.UploadUrl, reqReader)
if postErr != nil {
glog.V(1).InfofCtx(ctx, "create upload request %s: %v", option.UploadUrl, postErr)
return nil, fmt.Errorf("create upload request %s: %v", option.UploadUrl, postErr)

61
weed/operation/upload_content_test.go

@ -0,0 +1,61 @@
package operation
import (
"bytes"
"context"
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
)
// Test that Upload respects request context timeout and does not hang indefinitely
func TestUploadTimesOutOnStalledConnectionViaContext(t *testing.T) {
// Create a test server that stalls and never writes a response
ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do not read the body and do not write a response; just stall
select {
case <-time.After(10 * time.Second):
case <-r.Context().Done():
// Proactively close the underlying connection to avoid server Close delay
if hj, ok := w.(http.Hijacker); ok {
if conn, _, err := hj.Hijack(); err == nil {
_ = conn.Close()
}
}
}
}))
// Make the server's own timeouts aggressive so Close does not block long
ts.Config.ReadTimeout = 200 * time.Millisecond
ts.Config.WriteTimeout = 200 * time.Millisecond
ts.Config.IdleTimeout = 200 * time.Millisecond
ts.Start()
defer ts.Close()
u, err := NewUploader()
if err != nil {
t.Fatalf("failed to create uploader: %v", err)
}
// Short timeout to make the test fast
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
defer cancel()
data := bytes.Repeat([]byte("a"), 1024)
// Call the lower-level upload to avoid internal retries and keep test fast
_, err = u.upload_content(ctx, func(w io.Writer) error {
_, writeErr := w.Write(data)
return writeErr
}, len(data), &UploadOption{
UploadUrl: ts.URL + "/upload",
Filename: "test.bin",
MimeType: "application/octet-stream",
})
if err == nil {
t.Fatalf("expected timeout error, got nil")
}
}
Loading…
Cancel
Save