Non-determinstic poor performance with nested If conditions #3717
Description
CloudFormation Lint Version
cfn-lint 1.15.1
What operating system are you using?
Linux (Fedora 40 x86_64)
Describe the bug
When nested Fn:If conditions are used (or possibly just large numbers of Fn:If conditions) cfn-lint performance can degrade severely.
Processing the attached template can range from seconds, to over a minute for the attached example, the original full template, which has multiple nested Fn:If cases has been observed to take up to 8 minutes to process in some cases.
I have figured out that I am able to achieve repeatable processing times by forcing specific python hash seeds, in the following examples the hash seeds are random test values that I found that trigger both fast and slow processing, but the overall time can vary widely depending on the hash seed, ie there's not just one "slow" and one "fast" case.
Based on a few stack traces taken from "slow" examples I suspect that the issue may be related to sympy2's DPLL implementation, but that's just a guess based on a handful of stack traces, and the issue could also be higher up in the call chain.
Removing the nesting of the If condition from the template does appear to significantly improve both the absolute performance, and the variability, but I'm not sure whether that's specifically related to the nesting of the If conditions, or just the total number of possible condition combination branches.
This issue may be somewhat related to the previously addressed #2597
Expected behavior
Template processing should ideally be single, or low double digits seconds at most, but regardless of absolute performance the time required to process a given template shouldn't change by orders of magnitude between runs based on random factors like the python hash seed.
Reproduction template
# To reproduce "fast" cfn-lint evaluation use PYTHONHASHSEED=2821703311
# $ time env PYTHONHASHSEED=2821703311 cfn-lint --ignore-checks W1001 E3003 -- nested-if-conditions.yml
#
# To reproduce "slow" cfn-lint evaluation use PYTHONHASHSEED=1356847902
# $ time env PYTHONHASHSEED=1356847902 cfn-lint --ignore-checks W1001 E3003 -- nested-if-conditions.yml
#
Parameters:
WebACLId:
Type: String
DeploymentRegion1:
Type: String
Default: ''
DeploymentName1:
Type: String
Default: ''
ServiceDomain1:
Type: String
Default: ''
S3WebAssetsBucket1:
Type: String
Default: ''
S3WebTemplatesBucket1:
Type: String
Default: ''
S3ExtAssetsBucket1:
Type: String
Default: ''
S3ExtraTemplatesBucket1:
Type: String
Default: ''
DeploymentRegion2:
Type: String
Default: ''
DeploymentName2:
Type: String
Default: ''
ServiceDomain2:
Type: String
Default: ''
S3WebAssetsBucket2:
Type: String
Default: ''
S3WebTemplatesBucket2:
Type: String
Default: ''
S3ExtAssetsBucket2:
Type: String
Default: ''
S3ExtraTemplatesBucket2:
Type: String
Default: ''
DeploymentRegion3:
Type: String
Default: ''
DeploymentName3:
Type: String
Default: ''
ServiceDomain3:
Type: String
Default: ''
S3WebAssetsBucket3:
Type: String
Default: ''
S3WebTemplatesBucket3:
Type: String
Default: ''
S3ExtAssetsBucket3:
Type: String
Default: ''
S3ExtraTemplatesBucket3:
Type: String
Default: ''
DeploymentRegion4:
Type: String
Default: ''
DeploymentName4:
Type: String
Default: ''
ServiceDomain4:
Type: String
Default: ''
S3WebAssetsBucket4:
Type: String
Default: ''
S3WebTemplatesBucket4:
Type: String
Default: ''
S3ExtAssetsBucket4:
Type: String
Default: ''
S3ExtraTemplatesBucket4:
Type: String
Default: ''
DeploymentRegion5:
Type: String
Default: ''
DeploymentName5:
Type: String
Default: ''
ServiceDomain5:
Type: String
Default: ''
S3WebAssetsBucket5:
Type: String
Default: ''
S3WebTemplatesBucket5:
Type: String
Default: ''
S3ExtAssetsBucket5:
Type: String
Default: ''
S3ExtraTemplatesBucket5:
Type: String
Default: ''
Conditions:
HaveWebTarget1: !And
- !Not [ !Equals [ !Ref S3WebTemplatesBucket1, '' ] ]
- !Not [ !Equals [ !Ref S3WebAssetsBucket1, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName1, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion1, '' ] ]
HaveWebTarget2: !And
- !Not [ !Equals [ !Ref S3WebTemplatesBucket2, '' ] ]
- !Not [ !Equals [ !Ref S3WebAssetsBucket2, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName2, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion2, '' ] ]
HaveWebTarget3: !And
- !Not [ !Equals [ !Ref S3WebTemplatesBucket3, '' ] ]
- !Not [ !Equals [ !Ref S3WebAssetsBucket3, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName3, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion3, '' ] ]
HaveWebTarget4: !And
- !Not [ !Equals [ !Ref S3WebTemplatesBucket4, '' ] ]
- !Not [ !Equals [ !Ref S3WebAssetsBucket4, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName4, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion4, '' ] ]
HaveWebTarget5: !And
- !Not [ !Equals [ !Ref S3WebTemplatesBucket5, '' ] ]
- !Not [ !Equals [ !Ref S3WebAssetsBucket5, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName5, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion5, '' ] ]
HaveExtTarget1: !And
- !Not [ !Equals [ !Ref S3ExtraTemplatesBucket1, '' ] ]
- !Not [ !Equals [ !Ref S3ExtAssetsBucket1, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName1, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion1, '' ] ]
HaveExtTarget2: !And
- !Not [ !Equals [ !Ref S3ExtraTemplatesBucket2, '' ] ]
- !Not [ !Equals [ !Ref S3ExtAssetsBucket2, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName2, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion2, '' ] ]
HaveExtTarget3: !And
- !Not [ !Equals [ !Ref S3ExtraTemplatesBucket3, '' ] ]
- !Not [ !Equals [ !Ref S3ExtAssetsBucket3, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName3, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion3, '' ] ]
HaveExtTarget4: !And
- !Not [ !Equals [ !Ref S3ExtraTemplatesBucket4, '' ] ]
- !Not [ !Equals [ !Ref S3ExtAssetsBucket4, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName4, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion4, '' ] ]
HaveExtTarget5: !And
- !Not [ !Equals [ !Ref S3ExtraTemplatesBucket5, '' ] ]
- !Not [ !Equals [ !Ref S3ExtAssetsBucket5, '' ] ]
- !Not [ !Equals [ !Ref DeploymentName5, '' ] ]
- !Not [ !Equals [ !Ref DeploymentRegion5, '' ] ]
HasAnyTarget: !And
- !Or
- !Condition HaveWebTarget1
- !Condition HaveWebTarget2
- !Condition HaveWebTarget3
- !Condition HaveWebTarget4
- !Condition HaveWebTarget5
- !Or
- !Condition HaveExtTarget1
- !Condition HaveExtTarget2
- !Condition HaveExtTarget3
- !Condition HaveExtTarget4
- !Condition HaveExtTarget5
Resources:
Distribution1:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
WebACLId: !Ref WebACLId
DefaultCacheBehavior:
TargetOriginId: TestWebOrigin
ViewerProtocolPolicy: redirect-to-https
AllowedMethods: [ 'GET', 'HEAD' ]
CacheBehaviors:
- !If
- HasAnyTarget
- TargetOriginId: TestWebAssetsOrigin
PathPattern: '/assets/*'
ViewerProtocolPolicy: redirect-to-https
- !Ref AWS::NoValue
- !If
- HasAnyTarget
- TargetOriginId: ExtAssetsOrigin
PathPattern: '/example/assets/*'
ViewerProtocolPolicy: redirect-to-https
- !Ref AWS::NoValue
- !If
- HasAnyTarget
- TargetOriginId: TestWebAssetsOrigin
PathPattern: '/.well-known/*'
ViewerProtocolPolicy: redirect-to-https
- !Ref AWS::NoValue
Origins:
- Id: TestWebOrigin
DomainName: 'subdomain.example.com'
CustomOriginConfig:
HTTPSPort: 443
OriginProtocolPolicy: https-only
OriginSSLProtocols: ["TLSv1.2"]
OriginKeepaliveTimeout: 5
OriginReadTimeout: 5
OriginCustomHeaders:
- HeaderName: add-csp-headers
HeaderValue: 1
- !If
- HaveWebTarget1
- HeaderName: !Sub
- 'web-template-bucket${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain1 ] ]
HeaderValue: !Sub '${DeploymentRegion1}/${S3WebTemplatesBucket1}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget2
- HeaderName: !Sub
- 'web-template-bucket${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain2 ] ]
HeaderValue: !Sub '${DeploymentRegion2}/${S3WebTemplatesBucket2}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget3
- HeaderName: !Sub
- 'web-template-bucket${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain3 ] ]
HeaderValue: !Sub '${DeploymentRegion3}/${S3WebTemplatesBucket3}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget4
- HeaderName: !Sub
- 'web-template-bucket${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain4 ] ]
HeaderValue: !Sub '${DeploymentRegion4}/${S3WebTemplatesBucket4}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget5
- HeaderName: !Sub
- 'web-template-bucket${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain5 ] ]
HeaderValue: !Sub '${DeploymentRegion5}/${S3WebTemplatesBucket5}'
- !Ref AWS::NoValue
- !If
- HaveExtTarget1
- HeaderName: !Sub
- 'extra-template-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain1 ] ]
HeaderValue: !Sub '${DeploymentRegion1}/${S3ExtraTemplatesBucket1}'
- !Ref AWS::NoValue
- !If
- HaveExtTarget2
- HeaderName: !Sub
- 'extra-template-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain2 ] ]
HeaderValue: !Sub '${DeploymentRegion2}/${S3ExtraTemplatesBucket2}'
- !Ref AWS::NoValue
- !If
- HaveExtTarget3
- HeaderName: !Sub
- 'extra-template-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain3 ] ]
HeaderValue: !Sub '${DeploymentRegion3}/${S3ExtraTemplatesBucket3}'
- !Ref AWS::NoValue
- !If
- HaveExtTarget4
- HeaderName: !Sub
- 'extra-template-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain4 ] ]
HeaderValue: !Sub '${DeploymentRegion4}/${S3ExtraTemplatesBucket4}'
- !Ref AWS::NoValue
- !If
- HaveExtTarget5
- HeaderName: !Sub
- 'extra-template-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain5 ] ]
HeaderValue: !Sub '${DeploymentRegion5}/${S3ExtraTemplatesBucket5}'
- !Ref AWS::NoValue
- !If
- HasAnyTarget
- Id: TestWebAssetsOrigin
DomainName: dummy-origin.example.com
CustomOriginConfig:
OriginProtocolPolicy: https-only
OriginCustomHeaders:
- HeaderName: web-domain
HeaderValue: 'dev.dev.example.com'
- !If
- HaveWebTarget1
- HeaderName: !Sub
- 'web-template-bucket${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain1 ] ]
HeaderValue: !Sub '${DeploymentRegion1}/${S3WebTemplatesBucket1}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget1
- HeaderName: !Sub
- 'web-asset-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain1 ] ]
HeaderValue: !Sub '${DeploymentRegion1}/${S3WebAssetsBucket1}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget2
- HeaderName: !Sub
- 'web-asset-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain2 ] ]
HeaderValue: !Sub '${DeploymentRegion2}/${S3WebAssetsBucket2}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget3
- HeaderName: !Sub
- 'web-asset-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain3 ] ]
HeaderValue: !Sub '${DeploymentRegion3}/${S3WebAssetsBucket3}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget4
- HeaderName: !Sub
- 'web-asset-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain4 ] ]
HeaderValue: !Sub '${DeploymentRegion4}/${S3WebAssetsBucket4}'
- !Ref AWS::NoValue
- !If
- HaveWebTarget5
- HeaderName: !Sub
- 'web-asset-bucket-${ServicePrefix}'
- ServicePrefix: !Select [ 0, !Split [ '.', !Ref ServiceDomain5 ] ]
HeaderValue: !Sub '${DeploymentRegion5}/${S3WebAssetsBucket5}'
- !Ref AWS::NoValue
- !Ref AWS::NoValue