Skip to content

Commit ca4b2ef

Browse files
authored
fix(bigquery): avoid double-channel-close during context cancellation (#7467)
EnableWriteRetries exposes an issue with finalizing a pending write. The appendWithRetry function incorrectly marks a pending write done in certain situations (context cancellations), when responsibility is held by it's callers. PR adds fix and a test that exercises the failure condition. Fixes: https://togithub.com/googleapis/google-cloud-go/issues/7380
1 parent 4810e8d commit ca4b2ef

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

bigquery/storage/managedwriter/managed_stream.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -392,8 +392,9 @@ func (ms *ManagedStream) appendWithRetry(pw *pendingWrite, opts ...gax.CallOptio
392392
}
393393
continue
394394
}
395-
// Mark the pending write done. This will not be returned to the user, they'll receive the returned error.
396-
pw.markDone(nil, appendErr, ms.fc)
395+
// This append cannot be retried locally. It is not the responsibility of this function to finalize the pending
396+
// write however, as that's handled by callers.
397+
// Related: https://github.com/googleapis/google-cloud-go/issues/7380
397398
return appendErr
398399
}
399400
return nil

bigquery/storage/managedwriter/managed_stream_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -735,3 +735,52 @@ func TestManagedStream_Receiver(t *testing.T) {
735735
cancel()
736736
}
737737
}
738+
739+
func TestManagedWriter_CancellationDuringRetry(t *testing.T) {
740+
// Issue: double close of pending write.
741+
// https://github.com/googleapis/google-cloud-go/issues/7380
742+
ctx, cancel := context.WithCancel(context.Background())
743+
744+
ms := &ManagedStream{
745+
ctx: ctx,
746+
open: openTestArc(&testAppendRowsClient{},
747+
func(req *storagepb.AppendRowsRequest) error {
748+
// Append doesn't error, but is slow.
749+
time.Sleep(time.Second)
750+
return nil
751+
},
752+
func() (*storagepb.AppendRowsResponse, error) {
753+
// Response is slow and always returns a retriable error.
754+
time.Sleep(2 * time.Second)
755+
return nil, io.EOF
756+
}),
757+
streamSettings: defaultStreamSettings(),
758+
fc: newFlowController(0, 0),
759+
retry: newStatelessRetryer(),
760+
schemaDescriptor: &descriptorpb.DescriptorProto{
761+
Name: proto.String("testDescriptor"),
762+
},
763+
}
764+
765+
fakeData := [][]byte{
766+
[]byte("foo"),
767+
}
768+
769+
res, err := ms.AppendRows(context.Background(), fakeData)
770+
if err != nil {
771+
t.Errorf("AppendRows send err: %v", err)
772+
}
773+
cancel()
774+
775+
select {
776+
777+
case <-res.Ready():
778+
if _, err := res.GetResult(context.Background()); err == nil {
779+
t.Errorf("expected failure, got success")
780+
}
781+
782+
case <-time.After(5 * time.Second):
783+
t.Errorf("result was not ready in expected time")
784+
785+
}
786+
}

0 commit comments

Comments
 (0)