Fixing race condition in LeaserCheckpointer where it can fail with a ContainerAlreadyExists error #253

There's a slight race condition between checking the store exists and the container being created.
    
We can handle it easily if we just allow ContainerAlreadyExists to be considered successful. Also, since the storage tests were failing (unrelated to my change) in race detection I also fixed that as well.

Fixes #252
Fixes #225
This commit is contained in:
Richard Park 2022-04-28 10:12:14 -07:00 коммит произвёл GitHub
Родитель 73b7c0f7b2
Коммит e473b7e4f6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 78 добавлений и 25 удалений

Просмотреть файл

@ -1,5 +1,9 @@
# Change Log
## `v3.3.17`
- Fixing issue where the LeaserCheckpointer could fail with a "ContainerAlreadyExists" error. (#253)
## `v3.3.16`
- Exporting a subset of AMQP message properties for the Dapr project.

Просмотреть файл

@ -41,7 +41,7 @@ jobs:
go get github.com/AlekSi/gocov-xml
go get -u github.com/matm/gocov-html
go get -u golang.org/x/lint/golint
go get github.com/fzipp/gocyclo/cmd/gocyclo
go get github.com/fzipp/gocyclo/cmd/gocyclo@v0.3.1
workingDirectory: '$(sdkPath)'
displayName: 'Install Dependencies'
- script: |

Просмотреть файл

@ -29,7 +29,7 @@ steps:
go get github.com/axw/gocov/gocov
go get github.com/AlekSi/gocov-xml
go get -u github.com/matm/gocov-html
go get github.com/fzipp/gocyclo/cmd/gocyclo
go get github.com/fzipp/gocyclo/cmd/gocyclo@v0.3.1
go get golang.org/x/lint/golint
displayName: 'Install Dependencies'
- script: |
@ -47,10 +47,10 @@ steps:
gocov-html < coverage.json > coverage.html
displayName: 'Run Integration Tests'
env:
ARM_SUBSCRIPTION_ID: $(go-live-azure-subscription-id)
ARM_CLIENT_ID: $(go-live-eh-azure-client-id)
ARM_CLIENT_SECRET: $(go-live-eh-azure-client-secret)
ARM_TENANT_ID: $(go-live-tenant-id)
ARM_SUBSCRIPTION_ID: $(azure-subscription-id)
ARM_CLIENT_ID: $(aad-azure-sdk-test-client-id)
ARM_CLIENT_SECRET: $(aad-azure-sdk-test-client-secret)
ARM_TENANT_ID: $(aad-azure-sdk-test-tenant-id)
- task: PublishTestResults@2
inputs:

Просмотреть файл

@ -31,7 +31,7 @@ import (
"github.com/devigned/tab"
"github.com/Azure/azure-event-hubs-go/v3"
eventhub "github.com/Azure/azure-event-hubs-go/v3"
)
type (
@ -58,11 +58,10 @@ func (lr *leasedReceiver) Run(ctx context.Context) error {
epoch := lr.lease.GetEpoch()
lr.dlog(ctx, "running...")
go func() {
ctx, done := context.WithCancel(context.Background())
lr.done = done
lr.periodicallyRenewLease(ctx)
}()
renewLeaseCtx, cancelRenewLease := context.WithCancel(context.Background())
lr.done = cancelRenewLease
go lr.periodicallyRenewLease(renewLeaseCtx)
opts := []eventhub.ReceiveOption{eventhub.ReceiveWithEpoch(epoch)}
if lr.processor.consumerGroup != "" {

Просмотреть файл

@ -30,6 +30,7 @@ import (
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"sync"
"time"
@ -37,7 +38,7 @@ import (
"github.com/Azure/azure-amqp-common-go/v3/uuid"
"github.com/devigned/tab"
"github.com/Azure/azure-event-hubs-go/v3"
eventhub "github.com/Azure/azure-event-hubs-go/v3"
"github.com/Azure/azure-event-hubs-go/v3/eph"
"github.com/Azure/azure-event-hubs-go/v3/persist"
@ -144,20 +145,22 @@ func (sl *LeaserCheckpointer) StoreExists(ctx context.Context) (bool, error) {
span, ctx := startConsumerSpanFromContext(ctx, "storage.LeaserCheckpointer.StoreExists")
defer span.End()
opts := azblob.ListContainersSegmentOptions{
Prefix: sl.containerName,
}
res, err := sl.serviceURL.ListContainersSegment(ctx, azblob.Marker{}, opts)
if err != nil {
return false, err
containerURL := sl.serviceURL.NewContainerURL(sl.containerName)
_, err := containerURL.GetProperties(ctx, azblob.LeaseAccessConditions{})
if err == nil {
return true, nil
}
for _, container := range res.ContainerItems {
if container.Name == sl.containerName {
return true, nil
var respErr azblob.ResponseError
if errors.As(err, &respErr) {
if respErr.Response().StatusCode == http.StatusNotFound {
return false, nil
}
}
return false, nil
return false, err
}
// EnsureStore creates the container if it does not exist
@ -175,9 +178,21 @@ func (sl *LeaserCheckpointer) EnsureStore(ctx context.Context) error {
if !ok {
containerURL := sl.serviceURL.NewContainerURL(sl.containerName)
_, err := containerURL.Create(ctx, azblob.Metadata{}, azblob.PublicAccessNone)
if err != nil {
return err
var storageErr azblob.StorageError
if errors.As(err, &storageErr) {
// we're okay if the container has been created - we're basically racing against
// other LeaserCheckpointers.
if storageErr.ServiceCode() != azblob.ServiceCodeContainerAlreadyExists {
return err
}
} else {
return err
}
}
sl.containerURL = &containerURL
}
return nil

Просмотреть файл

@ -25,6 +25,7 @@ package storage
import (
"context"
"strings"
"sync"
"time"
"github.com/Azure/azure-amqp-common-go/v3/aad"
@ -32,6 +33,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/Azure/azure-event-hubs-go/v3/eph"
"github.com/Azure/azure-event-hubs-go/v3/internal/test"
)
const (
@ -64,6 +66,35 @@ func (ts *testSuite) TestLeaserStoreCreation() {
ts.True(exists)
}
func (ts *testSuite) TestLeaserStoreCreationConcurrent() {
wg := sync.WaitGroup{}
containerName := test.RandomString("concurrent-container", 4)
// do a simple test that ensures we don't die just because we raced with
// other leasers to create the storage container.
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
leaser, _ := ts.newLeaserWithContainerName(containerName)
err := leaser.EnsureStore(context.Background())
ts.Require().NoError(err)
}(i)
}
wg.Wait()
leaser, del := ts.newLeaserWithContainerName(containerName)
defer del()
exists, err := leaser.StoreExists(context.Background())
ts.NoError(err)
ts.True(exists)
}
func (ts *testSuite) TestLeaserLeaseEnsure() {
leaser, del := ts.leaserWithEPH()
defer del()
@ -189,6 +220,10 @@ func (ts *testSuite) leaserWithEPH() (*LeaserCheckpointer, func()) {
func (ts *testSuite) newLeaser() (*LeaserCheckpointer, func()) {
containerName := strings.ToLower(ts.RandomName("stortest", 4))
return ts.newLeaserWithContainerName(containerName)
}
func (ts *testSuite) newLeaserWithContainerName(containerName string) (*LeaserCheckpointer, func()) {
cred, err := NewAADSASCredential(ts.SubscriptionID, ts.ResourceGroupName, ts.AccountName, containerName, AADSASCredentialWithEnvironmentVars())
ts.Require().NoError(err)
leaser, err := NewStorageLeaserCheckpointer(cred, ts.AccountName, containerName, ts.Env)

Просмотреть файл

@ -2,5 +2,5 @@ package eventhub
const (
// Version is the semantic version number
Version = "3.3.13"
Version = "3.3.17"
)