Skip to content

Commit 7ce8d51

Browse files
feat: implement S3 upload with retry mechanism and extended timeout
1 parent 04fb226 commit 7ce8d51

1 file changed

Lines changed: 44 additions & 15 deletions

File tree

internal/telemetry/telemetry.go

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -326,26 +326,55 @@ func uploadToS3(ctx context.Context, log *progress.Logger, payload *Payload) err
326326
return fmt.Errorf("empty upload URL in response")
327327
}
328328

329-
// Upload payload to S3
330-
log.Progress("Uploading telemetry to S3...")
331-
putReq, err := http.NewRequestWithContext(ctx, http.MethodPut, urlResp.UploadURL, bytes.NewReader(payloadJSON))
332-
if err != nil {
333-
return fmt.Errorf("creating S3 PUT request: %w", err)
334-
}
335-
putReq.Header.Set("Content-Type", "application/json")
329+
// Upload payload to S3 with retry — use a longer timeout since payloads
330+
// with npm scan data and execution logs can be several MB.
331+
log.Progress("Uploading telemetry to S3 (%d bytes)...", len(payloadJSON))
332+
s3Client := &http.Client{Timeout: 60 * time.Second}
333+
const maxRetries = 3
334+
var putResp *http.Response
335+
for attempt := 1; attempt <= maxRetries; attempt++ {
336+
uploadStart := time.Now()
337+
putReq, reqErr := http.NewRequestWithContext(ctx, http.MethodPut, urlResp.UploadURL, bytes.NewReader(payloadJSON))
338+
if reqErr != nil {
339+
return fmt.Errorf("creating S3 PUT request: %w", reqErr)
340+
}
341+
putReq.Header.Set("Content-Type", "application/json")
336342

337-
putResp, err := client.Do(putReq)
338-
if err != nil {
339-
return fmt.Errorf("uploading to S3: %w", err)
343+
putResp, err = s3Client.Do(putReq)
344+
elapsed := time.Since(uploadStart)
345+
346+
if err == nil && putResp.StatusCode == http.StatusOK {
347+
log.Progress("Uploaded to S3 in %s", elapsed)
348+
break
349+
}
350+
351+
// Clean up response body before retry
352+
if putResp != nil {
353+
_, _ = io.Copy(io.Discard, putResp.Body)
354+
_ = putResp.Body.Close()
355+
}
356+
357+
if attempt == maxRetries {
358+
if err != nil {
359+
return fmt.Errorf("uploading to S3 (payload: %d bytes, elapsed: %s, attempts: %d): %w",
360+
len(payloadJSON), elapsed, maxRetries, err)
361+
}
362+
return fmt.Errorf("S3 upload failed with status %d (payload: %d bytes, attempts: %d)",
363+
putResp.StatusCode, len(payloadJSON), maxRetries)
364+
}
365+
366+
// Log retry and backoff
367+
backoff := time.Duration(attempt) * 2 * time.Second
368+
if err != nil {
369+
log.Progress("S3 upload attempt %d/%d failed (%s), retrying in %s...", attempt, maxRetries, elapsed, backoff)
370+
} else {
371+
log.Progress("S3 upload attempt %d/%d got status %d, retrying in %s...", attempt, maxRetries, putResp.StatusCode, backoff)
372+
}
373+
time.Sleep(backoff)
340374
}
341375
defer func() { _ = putResp.Body.Close() }()
342376
_, _ = io.Copy(io.Discard, putResp.Body)
343377

344-
if putResp.StatusCode != http.StatusOK {
345-
return fmt.Errorf("S3 upload failed with status %d", putResp.StatusCode)
346-
}
347-
log.Progress("Uploaded to S3")
348-
349378
// Notify backend
350379
log.Progress("Notifying backend of upload...")
351380
notifyBody, _ := json.Marshal(map[string]string{

0 commit comments

Comments
 (0)