Skip to content

Bump forked google-api-go-client to v0.277.0-patched#4826

Merged
Michal-Leszczynski merged 1 commit into
masterfrom
ml/1909-update-google-api-go-client
May 12, 2026
Merged

Bump forked google-api-go-client to v0.277.0-patched#4826
Michal-Leszczynski merged 1 commit into
masterfrom
ml/1909-update-google-api-go-client

Conversation

@Michal-Leszczynski

@Michal-Leszczynski Michal-Leszczynski commented May 12, 2026

Copy link
Copy Markdown
Collaborator

v0.277.0-patched is the proper v0.277.0 google-api-go-client tag with c427c0d9 from our fork allowing to pass pre-allocated buffer on file upload. The diff is huge due to the changes between v0.34 and v0.277.

To validate the actual changes, one can take a look at:

This commit also removes the specific version from the replace directive, as it doesn't make sense in our case, since we don't expect newer versions of google-api-go-client to include this change.

git range-diff
1:  c427c0d93a ! 1:  292c1f5934 smpatch: gensupport: Allow user to provide his own buffer used for uploading
    @@
      ## Metadata ##
    -Author: Maciej Zimnoch <maciej@scylladb.com>
    +Author: Michał Leszczyński <2000michal@wp.pl>
     
      ## Commit message ##
    -    smpatch: gensupport: Allow user to provide his own buffer used for uploading
    +    chore(gensupport): cherry-pick c427c0d9
    +
    +    c427c0d9 is the reason for our fork existence, as it makes it
    +    possible to reuse reallocated buffers for uploading files.
    +    This commit cherry-picks it on top of v0.277.0.
     
      ## googleapi/googleapi.go ##
     @@ googleapi/googleapi.go: type MediaOption interface {
    @@ googleapi/googleapi.go: type MediaOption interface {
      
      func (ct contentTypeOption) setOptions(o *MediaOptions) {
     @@ googleapi/googleapi.go: type MediaOptions struct {
    - 	ForceEmptyContentType bool
    - 
    - 	ChunkSize int
    + 	ChunkRetryDeadline    time.Duration
    + 	ChunkTransferTimeout  time.Duration
    + 	EnableAutoChecksum    bool
     +
     +	Buffer []byte
      }
    @@ internal/gensupport/buffer.go: func NewMediaBuffer(media io.Reader, chunkSize in
      	return &MediaBuffer{media: media, chunk: make([]byte, 0, chunkSize)}
      }
      
    -+// NewMediaBuffer initializes a MediaBuffer.
    ++// NewMediaBufferWithBuffer initializes a MediaBuffer.
     +func NewMediaBufferWithBuffer(media io.Reader, chunkSize int, buffer []byte) *MediaBuffer {
     +	// If buffer isn't long enough, allocate new one.
     +	if cap(buffer) < chunkSize {
    @@ internal/gensupport/media.go: func typeHeader(contentType string) textproto.MIME
      //
      // After PrepareUpload has been called, media should no longer be used: the
      // media content should be accessed via one of the return values.
    --func PrepareUpload(media io.Reader, chunkSize int) (r io.Reader, mb *MediaBuffer, singleChunk bool) {
    -+func PrepareUpload(media io.Reader, chunkSize int, buffer []byte) (r io.Reader, mb *MediaBuffer, singleChunk bool) {
    +-func PrepareUpload(media io.Reader, chunkSize int, enableAutoChecksum bool) (r io.Reader, mb *MediaBuffer, singleChunk bool) {
    ++func PrepareUpload(media io.Reader, chunkSize int, enableAutoChecksum bool, buffer []byte) (r io.Reader, mb *MediaBuffer, singleChunk bool) {
      	if chunkSize == 0 { // do not chunk
      		return media, nil, true
      	}
    @@ internal/gensupport/media.go: func typeHeader(contentType string) textproto.MIME
     +	} else {
     +		mb = NewMediaBuffer(media, chunkSize)
     +	}
    -+
    + 	mb.enableAutoChecksum = enableAutoChecksum
      	_, _, _, err := mb.Chunk()
      	// If err is io.EOF, we can upload this in a single request. Otherwise, err is
    - 	// either nil or a non-EOF error. If it is the latter, then the next call to
     @@ internal/gensupport/media.go: func NewInfoFromMedia(r io.Reader, options []googleapi.MediaOption) *MediaInfo {
    - 	if !opts.ForceEmptyContentType {
    - 		r, mi.mType = DetermineContentType(r, opts.ContentType)
      	}
    --	mi.media, mi.buffer, mi.singleChunk = PrepareUpload(r, opts.ChunkSize)
    -+	mi.media, mi.buffer, mi.singleChunk = PrepareUpload(r, opts.ChunkSize, opts.Buffer)
    + 	mi.chunkRetryDeadline = opts.ChunkRetryDeadline
    + 	mi.chunkTransferTimeout = opts.ChunkTransferTimeout
    +-	mi.media, mi.buffer, mi.singleChunk = PrepareUpload(r, opts.ChunkSize, opts.EnableAutoChecksum)
    ++	mi.media, mi.buffer, mi.singleChunk = PrepareUpload(r, opts.ChunkSize, opts.EnableAutoChecksum, opts.Buffer)
      	return mi
      }
      
     
      ## internal/gensupport/media_test.go ##
    -@@ internal/gensupport/media_test.go: func TestDetermineContentType(t *testing.T) {
    +@@ internal/gensupport/media_test.go: import (
    + 	"io"
    + 	mathrand "math/rand"
    + 	"net/http"
    ++	"reflect"
    + 	"strings"
    + 	"testing"
    + 	"time"
    +@@ internal/gensupport/media_test.go: import (
      
      func TestNewInfoFromMedia(t *testing.T) {
      	const textType = "text/plain; charset=utf-8"
    -+	chunkBuffer := make([]byte, 0)
    ++	chunkBuffer := make([]byte, 0, googleapi.DefaultUploadChunkSize)
      	for _, test := range []struct {
     -		desc                                   string
     -		r                                      io.Reader
     -		opts                                   []googleapi.MediaOption
     -		wantType                               string
     -		wantMedia, wantBuffer, wantSingleChunk bool
    -+		desc                                      string
    -+		r                                         io.Reader
    -+		opts                                      []googleapi.MediaOption
    -+		wantType                                  string
    -+		wantChunkBuffer                           []byte
    -+		wantMedia, wantAnyBuffer, wantSingleChunk bool
    +-		wantDeadline                           time.Duration
    ++		desc                                                  string
    ++		r                                                     io.Reader
    ++		opts                                                  []googleapi.MediaOption
    ++		wantType                                              string
    ++		wantMedia, wantAnyBuffer, wantBuffer, wantSingleChunk bool
    ++		wantChunkBuffer                                       []byte
    ++		wantDeadline                                          time.Duration
      	}{
      		{
      			desc:            "an empty reader results in a MediaBuffer with a single, empty chunk",
    @@ internal/gensupport/media_test.go: func TestNewInfoFromMedia(t *testing.T) {
      			wantType:        "application/octet-stream",
     -			wantBuffer:      true,
     +			wantAnyBuffer:   true,
    -+			wantSingleChunk: false,
    -+		},
    + 			wantSingleChunk: false,
    + 		},
     +		{
     +			desc:            "WithBuffer is observed",
     +			r:               new(bytes.Buffer),
     +			opts:            []googleapi.MediaOption{googleapi.WithBuffer(chunkBuffer)},
     +			wantType:        textType,
     +			wantChunkBuffer: chunkBuffer,
    - 			wantSingleChunk: false,
    - 		},
    ++			wantSingleChunk: true,
    ++		},
      	} {
    + 
    + 		mi := NewInfoFromMedia(test.r, test.opts)
     @@ internal/gensupport/media_test.go: func TestNewInfoFromMedia(t *testing.T) {
      		if got, want := (mi.media != nil), test.wantMedia; got != want {
      			t.Errorf("%s: media non-nil: got %t, want %t", test.desc, got, want)
    @@ internal/gensupport/media_test.go: func TestNewInfoFromMedia(t *testing.T) {
      			t.Errorf("%s: singleChunk: got %t, want %t", test.desc, got, want)
     
      ## internal/gensupport/resumable_test.go ##
    -@@ internal/gensupport/resumable_test.go: func TestRetry_EachChunkHasItsOwnRetryDeadline(t *testing.T) {
    +@@ internal/gensupport/resumable_test.go: func TestUploadChecksum(t *testing.T) {
      		}
      	}
      }
    @@ internal/gensupport/resumable_test.go: func TestRetry_EachChunkHasItsOwnRetryDea
     +	tr := &interruptibleTransport{
     +		buf: make([]byte, 0, mediaSize),
     +		events: []event{
    -+			{"bytes 0-255/*", 200},
    ++			{"bytes 0-255/*", 200, 0},
     +		},
     +		bodies: bodyTracker{},
     +	}

Fixes https://scylladb.atlassian.net/browse/CLOUD-1909

v0.277.0-patched is the proper v0.277.0 google-api-go-client tag with
c427c0d9 from our fork allowing to pass pre-allocated buffer on file upload.
The diff is huge due to the changes between v0.34 and v0.277.

To validate the actual changes, one can take a look at:
- how our changes were applied before:
scylladb/google-api-go-client@c427c0d
- how our changes were applied after rebase:
scylladb/google-api-go-client@292c1f5

This commit also removes the specific version from the replace directive,
as it doesn't make sense in our case, since we don't expect newer versions
of google-api-go-client to include this change.

Fixes https://scylladb.atlassian.net/browse/CLOUD-1909
@Michal-Leszczynski
Michal-Leszczynski force-pushed the ml/1909-update-google-api-go-client branch from fa667ff to 718b9ef Compare May 12, 2026 09:29
@Michal-Leszczynski
Michal-Leszczynski marked this pull request as ready for review May 12, 2026 11:13
@karol-kokoszka

Copy link
Copy Markdown
Collaborator

Can you validate with i.e. opus that the WithBuffer is included into the upload operations and Go doesn't allocate memory for every consecutive transfer but reuses the buffer ?

@Michal-Leszczynski

Copy link
Copy Markdown
Collaborator Author

@copilot validate that the changes from commit c427c0d9 were correctly applied on top of v0.277.0 of google-api-go-client and they still achieve the same goal. Focus on ensuring that the WithBuffer is included into the upload operations and Go doesn't allocate memory for every consecutive transfer but reuses the buffer.

Copilot AI commented May 12, 2026

Copy link
Copy Markdown
Contributor

@copilot validate that the changes from commit c427c0d9 were correctly applied on top of v0.277.0 of google-api-go-client and they still achieve the same goal. Focus on ensuring that t...

I've validated that the WithBuffer changes from commit c427c0d9 were correctly applied on top of v0.277.0. The full buffer-reuse chain is intact:

  1. googleapi.WithBuffer(buffer []byte) (googleapi.go:288) — creates a bufferOption that stores the pre-allocated []byte slice into MediaOptions.Buffer (googleapi.go:384).

  2. PrepareUpload (media.go:121) — accepts the buffer []byte parameter and, when non-nil, calls NewMediaBufferWithBuffer instead of NewMediaBuffer, passing the pre-allocated buffer.

  3. NewMediaBufferWithBuffer (buffer.go:42) — reuses the provided buffer as the chunk field of MediaBuffer (after resetting length to 0), so no new allocation happens. Falls back to NewMediaBuffer only if cap(buffer) < chunkSize.

  4. NewInfoFromMedia (media.go:156) — passes opts.Buffer through to PrepareUpload, connecting the option to the buffer logic.

  5. Consumer: rclone GCS backend (googlecloudstorage.go:1180-1188) — obtains a buffer from a pool (o.fs.pool.Get()), passes it via googleapi.WithBuffer(buf) as a media option, and returns it to the pool after upload (defer o.fs.pool.Put(buf)). This ensures Go reuses buffers across consecutive transfers without allocating new memory each time.

The implementation correctly achieves the same goal as the original fork: allowing a pre-allocated buffer to be passed into upload operations so that memory is reused across transfers instead of being allocated fresh for every upload.

@Michal-Leszczynski

Copy link
Copy Markdown
Collaborator Author

@karol-kokoszka see #4826 (comment) - do you want me to run some SCT against GCP and take a look at the golang memory metrics during backup execution?

@karol-kokoszka

Copy link
Copy Markdown
Collaborator

@karol-kokoszka see #4826 (comment) - do you want me to run some SCT against GCP and take a look at the golang memory metrics during backup execution?

It's ok, feel free to merge.

@Michal-Leszczynski
Michal-Leszczynski merged commit 5d3f536 into master May 12, 2026
75 of 76 checks passed
@Michal-Leszczynski
Michal-Leszczynski deleted the ml/1909-update-google-api-go-client branch May 12, 2026 11:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants