Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thread safety #688

Merged
merged 66 commits into from
Oct 20, 2018
Merged

Thread safety #688

merged 66 commits into from
Oct 20, 2018

Conversation

marcplouhinec
Copy link
Contributor

The goal of this pull request is to protect alicloud provider from the "thread-unsafety" of the underlying Go SDK. Because Terraform is creating resources in parallel (when possible), it randomly crashes with a panic error when a data race issue is occurring inside the Go SDK, because the later is NOT thread safe.

This pull request contains the following modifications:

  • Create wrappers inside AliyunClient that forces each call to the Go SDK to be protected via a mutex (only one call can be done at a time). A wrapper is written in the following format:
    func (client *AliyunClient) RunSafelyWithEcsClient(do func(*ecs.Client) (interface{}, error)) (interface{}, error) {
        // Ensure that only one thread can call the Go SDK with the mutex "goSdkMutex"
        // Initialize the ecs.Client if not already initialized
        // Call do() and return its results
    }
    
    When some code needs to make a call to the Go SDK, it needs to use this wrapper. For example:
    // client is an instance of AliyunClient
    raw, err := client.RunSafelyWithEcsClient(func(ecsClient *ecs.Client) (interface{}, error) {
        return ecsClient.DescribeAvailableResource(args)
    })
    if err != nil {
        return "", nil, fmt.Errorf("Error DescribeAvailableResource: %#v", err)
    }
    resources, _ := raw.(*ecs.DescribeAvailableResourceResponse)
    
  • Move AliyunClient into a new sub-package aliyunclient. The goal is to prevent the rest of the code (services, data sources, resources) to directly access Go SDK functions without the wrappers.
  • Because functions located in service_*.go cannot be defined for AliyunClient anymore because they are now located in a different package than AliyunClient, a new struct has been created for each of them. For example calling the functionDescribeAvailableResources located in alicloud/service_alicloud_ecs.go become:
    ecsService := EcsService{client}
    zoneId, validZones, err := ecsService.DescribeAvailableResources(d, meta, InstanceTypeResource)
    
    This modification also makes the code easier to write and read, because related functions are now isolated into the same service structs.
  • Modify each service, data source and resource in order to use the AliyunClient wrappers.
  • Move constants and functions that were only used by the AliyunClient into the aliyunclient package.

All the acceptance tests have been executed in Frankfurt (eu-central-1). All of them successfully pass beside:

  • TestAccAlicloudCSKubernetes_import
  • TestAccAlicloudOtsInstance_import
  • TestAccAlicloudOtsTable_importBasic
  • TestAccAlicloudCSApplication_swarm
  • TestAccAlicloudCSApplication_update
  • TestAccAlicloudCSKubernetes_basic
  • TestAccAlicloudCSSwarm_update
  • TestAccAlicloudDBConnection_basic
  • TestAccAlicloudOtsInstanceAttachment_Basic
  • TestAccAlicloudOtsInstance_Basic
  • TestAccAlicloudOtsInstance_Tags
  • TestAccAlicloudOtsTable_Basic
  • TestAccAlicloudVpnGateway_update
  • TestAccAlicloudCSApplication_import

These tests were already failing for the same reasons before the changes included in this pull request.

The following tests have also been executed successfully in Beijing (cn-beijing):

  • TestAccAlicloudSlb_spec
  • TestAccAlicloudSlb_paybybandwidth
  • TestAccAlicloudPvtzZoneRecordsDataSource_basic
  • TestAccAlicloudPvtzZonesDataSource_basic
  • TestAccAlicloudPvtzZoneAttachment_importBasic
  • TestAccAlicloudPvtzZoneRecord_importBasic
  • TestAccAlicloudPvtzZone_importBasic
  • TestAccAlicloudPvtzZoneAttachment_Basic
  • TestAccAlicloudPvtzZoneAttachment_multi
  • TestAccAlicloudPvtzZoneAttachment_update
  • TestAccAlicloudPvtzZoneRecord_Basic
  • TestAccAlicloudPvtzZoneRecord_multi
  • TestAccAlicloudPvtzZoneRecord_update
  • TestAccAlicloudPvtzZone_Basic
  • TestAccAlicloudPvtzZone_multi
  • TestAccAlicloudPvtzZone_update
  • TestAccAlicloudInstance_basic

… of AliyunClients could lead to data race issues.

Migrate datasources to the new AliyunClient.
…cloud_db_instance resources to the new AliyunClient.
…ess_lifecyclehook resources to the new AliyunClient.
…oup, alicloud_ess_scalingrule and alicloud_ess_schedule resources to the new AliyunClient.
…d_log_store and alicloud_log_store_index resource to the new AliyunClient.
…nd alicloud_ots_table resource to the new AliyunClient.
…cloud_pvtz_zone_record resource to the new AliyunClient.
…_ram_group, alicloud_ram_group_membership and alicloud_ram_group_policy_attachment to the thread-safe AliyunClient.
…icloud_vpn_gateway to the thread-safe AliyunClient.
…the NewClient methods into aliyunclient to do that).

Clean some code.
# Conflicts:
#	alicloud/common.go
#	alicloud/config.go
#	alicloud/data_source_alicloud_zones.go
#	alicloud/extension_ess.go
#	alicloud/resource_alicloud_cs_kubernetes.go
#	alicloud/resource_alicloud_db_connection.go
#	alicloud/resource_alicloud_db_connection_test.go
#	alicloud/resource_alicloud_kvstore_instance.go
#	alicloud/service_alicloud_cen.go
# Conflicts:
#	alicloud/common.go
#	alicloud/config.go
#	alicloud/data_source_alicloud_zones.go
#	alicloud/extension_ess.go
#	alicloud/resource_alicloud_cs_kubernetes.go
#	alicloud/resource_alicloud_db_connection.go
#	alicloud/resource_alicloud_db_connection_test.go
#	alicloud/resource_alicloud_kvstore_instance.go
#	alicloud/service_alicloud_cen.go
@@ -0,0 +1,726 @@
package aliyunclient
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think package name client is not better because this package does not just contain client.

In addition, I think just use client.go is ok.

"regexp"

"sync"

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please run make fmt before next submitting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange, I do it again but it doesn't change any of my files. What happen if you do it on your computer?

if identity.AccountId == "" {
return "", fmt.Errorf("caller identity doesn't contain any AccountId")
}
log.Printf("[DEBUG] account_id retrieved with success.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think line 629 can be removed and if it is not success, it will return an error.


func (client *AliyunClient) getCallerIdentity() (*sts.GetCallerIdentityResponse, error) {
args := sts.CreateGetCallerIdentityRequest()
args.Scheme = "https"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think args.Scheme = string(Https) will be better.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately not possible because the Https constant is defined outside of the package.


args := rds.CreateDescribeDBInstancesRequest()

args.RegionId = getRegionId(d, meta)
args.RegionId = meta.(*aliyunclient.AliyunClient).RegionId
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think using args.RegionId = client.RegionId will be better.

@@ -145,14 +147,17 @@ func resourceAlicloudCSApplicationCreate(d *schema.ResourceData, meta interface{
}
invoker := NewInvoker()
if err := invoker.Run(func() error {
return conn.CreateProject(args)
_, err := csService.RunSafelyWithCsProjectClientByClusterName(clusterName, func(csProjectClient *cs.ProjectClient) (interface{}, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why line 150 is csService.RunSafelyWithCsProjectClientByClusterName not client.RunSafelyWithCsProjectClientByClusterName

client.RunSafelyWithOssClient(func(ossClient *oss.Client) (interface{}, error) {
log.Printf("[DEBUG] OSS bucket create: %#v, using endpoint: %#v", bucket, ossClient.Config.Endpoint)
return nil, nil
})

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove line 226-229.

"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
"github.com/aliyun/alibaba-cloud-sdk-go/services/cbn"
)

type CenService struct {
client *aliyunclient.AliyunClient
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think line 14 just using *aliyunclient.AliyunClient is ok.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, the semantic changes when you remove "client": in the current implementation a service uses the client, but in what you propose the service extends the client.

Although it is slightly more verbose to write "myService.client.SomeFunction" instead of "myService.SomeFunction", it is easier to read because the reader can immediately understand that the function come from the client, not the service.

It's like the Composition over inheritance principle.


args := vpc.CreateDescribeEipAddressesRequest()
args.RegionId = string(getRegion(d, meta))
args.RegionId = string(meta.(*aliyunclient.AliyunClient).Region)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think line 90 uses args.RegionId =client.RegionId will be better.

}, nil
}

func (client *AliyunClient) RunSafelyWithEcsClient(do func(*ecs.Client) (interface{}, error)) (interface{}, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR aims to solve GO SDK thread-unsafe problem. I think the prefix RunSafelyWith is redundant and it can be removed to simplify the client definition. If you want to stress thread-safe, adding some annotations is good choice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I propose to still keep "With" like "WithSomeClient", if not it would look like a weird getter.

…vice.GetContainerClusterAndCertsByName(clusterName) followed by client.RunSafelyWithCsProjectClient().
# Conflicts:
#	alicloud/common.go
#	alicloud/config.go
#	alicloud/resource_alicloud_instance.go
#	alicloud/resource_alicloud_key_pair.go
#	alicloud/resource_alicloud_ram_user_policy_attachment.go
# Conflicts:
#	alicloud/common.go
#	alicloud/config.go
#	alicloud/resource_alicloud_instance.go
#	alicloud/resource_alicloud_key_pair.go
#	alicloud/resource_alicloud_ram_user_policy_attachment.go
# Conflicts:
#	alicloud/resource_alicloud_cen_instance.go
#	alicloud/resource_alicloud_vroute_entry.go
#	alicloud/service_alicloud_cen.go
# Conflicts:
#	alicloud/resource_alicloud_cen_instance.go
#	alicloud/resource_alicloud_vroute_entry.go
#	alicloud/service_alicloud_cen.go
# Conflicts:
#	alicloud/resource_alicloud_ess_scalingconfiguration.go
#	alicloud/resource_alicloud_router_interface.go
#	alicloud/resource_alicloud_security_group.go
#	alicloud/service_alicloud_vpc.go
# Conflicts:
#	alicloud/resource_alicloud_ess_scalingconfiguration.go
#	alicloud/resource_alicloud_router_interface.go
#	alicloud/resource_alicloud_security_group.go
#	alicloud/service_alicloud_vpc.go
@xiaozhu36 xiaozhu36 merged commit 2fdc31a into alibaba:master Oct 20, 2018
@marcplouhinec marcplouhinec deleted the thread_safety branch October 22, 2018 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants