@@ -73,7 +73,9 @@ internal class DatabricksConnection : SparkHttpConnection
7373 private const long DefaultMaxBytesPerFetchRequest = 400 * 1024 * 1024 ; // 400MB
7474 private long _maxBytesPerFetchRequest = DefaultMaxBytesPerFetchRequest ;
7575 private const bool DefaultRetryOnUnavailable = true ;
76+ private const bool DefaultRateLimitRetry = true ;
7677 private const int DefaultTemporarilyUnavailableRetryTimeout = 900 ;
78+ private const int DefaultRateLimitRetryTimeout = 120 ;
7779 private bool _useDescTableExtended = false ;
7880
7981 // Trace propagation configuration
@@ -540,15 +542,25 @@ protected internal override bool TrySetGetDirectResults(IRequest request)
540542 public bool RunAsyncInThrift => _runAsyncInThrift ;
541543
542544 /// <summary>
543- /// Gets a value indicating whether to retry requests that receive a 503 response with a Retry-After header .
545+ /// Gets a value indicating whether to retry requests that receive retryable responses (408, 502, 503, 504) .
544546 /// </summary>
545547 protected bool TemporarilyUnavailableRetry { get ; private set ; } = DefaultRetryOnUnavailable ;
546548
547549 /// <summary>
548- /// Gets the maximum total time in seconds to retry 503 responses before failing.
550+ /// Gets the maximum total time in seconds to retry retryable responses (408, 502, 503, 504) before failing.
549551 /// </summary>
550552 protected int TemporarilyUnavailableRetryTimeout { get ; private set ; } = DefaultTemporarilyUnavailableRetryTimeout ;
551553
554+ /// <summary>
555+ /// Gets a value indicating whether to retry requests that receive HTTP 429 responses.
556+ /// </summary>
557+ protected bool RateLimitRetry { get ; private set ; } = DefaultRateLimitRetry ;
558+
559+ /// <summary>
560+ /// Gets the number of seconds to wait before stopping an attempt to retry HTTP 429 responses.
561+ /// </summary>
562+ protected int RateLimitRetryTimeout { get ; private set ; } = DefaultRateLimitRetryTimeout ;
563+
552564 protected override HttpMessageHandler CreateHttpHandler ( )
553565 {
554566 HttpMessageHandler baseHandler = base . CreateHttpHandler ( ) ;
@@ -565,7 +577,7 @@ protected override HttpMessageHandler CreateHttpHandler()
565577 // Current chain order (outermost to innermost):
566578 // 1. OAuth handlers (OAuthDelegatingHandler, etc.) - only on baseHandler for API requests
567579 // 2. ThriftErrorMessageHandler - extracts x-thriftserver-error-message and throws descriptive exceptions
568- // 3. RetryHttpHandler - retries 408, 502, 503, 504 with Retry-After support
580+ // 3. RetryHttpHandler - retries 408, 429, 502, 503, 504 with Retry-After support
569581 // 4. TracingDelegatingHandler - propagates W3C trace context
570582 // 5. Base HTTP handler - actual network communication
571583 //
@@ -586,16 +598,16 @@ protected override HttpMessageHandler CreateHttpHandler()
586598 baseAuthHandler = new TracingDelegatingHandler ( baseAuthHandler , this , _traceParentHeaderName , _traceStateEnabled ) ;
587599 }
588600
589- if ( TemporarilyUnavailableRetry )
601+ if ( TemporarilyUnavailableRetry || RateLimitRetry )
590602 {
591- // Add retry handler for 408, 502, 503, 504 responses with Retry-After support
603+ // Add retry handler for 408, 429, 502, 503, 504 responses with Retry-After support
592604 // This must be INSIDE ThriftErrorMessageHandler so retries happen before exceptions are thrown
593- baseHandler = new RetryHttpHandler ( baseHandler , TemporarilyUnavailableRetryTimeout ) ;
594- baseAuthHandler = new RetryHttpHandler ( baseAuthHandler , TemporarilyUnavailableRetryTimeout ) ;
605+ baseHandler = new RetryHttpHandler ( baseHandler , TemporarilyUnavailableRetryTimeout , RateLimitRetryTimeout , TemporarilyUnavailableRetry , RateLimitRetry ) ;
606+ baseAuthHandler = new RetryHttpHandler ( baseAuthHandler , TemporarilyUnavailableRetryTimeout , RateLimitRetryTimeout , TemporarilyUnavailableRetry , RateLimitRetry ) ;
595607 }
596608
597609 // Add Thrift error message handler AFTER retry handler (OUTSIDE in the chain)
598- // This ensures retryable status codes (408, 502, 503, 504) are retried by RetryHttpHandler
610+ // This ensures retryable status codes (408, 429, 502, 503, 504) are retried by RetryHttpHandler
599611 // before ThriftErrorMessageHandler throws exceptions with Thrift error messages
600612 baseHandler = new ThriftErrorMessageHandler ( baseHandler ) ;
601613 baseAuthHandler = new ThriftErrorMessageHandler ( baseAuthHandler ) ;
@@ -900,6 +912,16 @@ protected override void ValidateOptions()
900912 TemporarilyUnavailableRetry = tempUnavailableRetryValue ;
901913 }
902914
915+ if ( Properties . TryGetValue ( DatabricksParameters . RateLimitRetry , out string ? rateLimitRetryStr ) )
916+ {
917+ if ( ! bool . TryParse ( rateLimitRetryStr , out bool rateLimitRetryValue ) )
918+ {
919+ throw new ArgumentOutOfRangeException ( DatabricksParameters . RateLimitRetry , rateLimitRetryStr ,
920+ $ "must be a value of false (disabled) or true (enabled). Default is true.") ;
921+ }
922+
923+ RateLimitRetry = rateLimitRetryValue ;
924+ }
903925
904926 if ( Properties . TryGetValue ( DatabricksParameters . TemporarilyUnavailableRetryTimeout , out string ? tempUnavailableRetryTimeoutStr ) )
905927 {
@@ -912,6 +934,17 @@ protected override void ValidateOptions()
912934 TemporarilyUnavailableRetryTimeout = tempUnavailableRetryTimeoutValue ;
913935 }
914936
937+ if ( Properties . TryGetValue ( DatabricksParameters . RateLimitRetryTimeout , out string ? rateLimitRetryTimeoutStr ) )
938+ {
939+ if ( ! int . TryParse ( rateLimitRetryTimeoutStr , out int rateLimitRetryTimeoutValue ) ||
940+ rateLimitRetryTimeoutValue < 0 )
941+ {
942+ throw new ArgumentOutOfRangeException ( DatabricksParameters . RateLimitRetryTimeout , rateLimitRetryTimeoutStr ,
943+ $ "must be a value of 0 (retry indefinitely) or a positive integer representing seconds. Default is 120 seconds (2 minutes).") ;
944+ }
945+ RateLimitRetryTimeout = rateLimitRetryTimeoutValue ;
946+ }
947+
915948 // When TemporarilyUnavailableRetry is enabled, we need to make sure connection timeout (which is used to cancel the HttpConnection) is equal
916949 // or greater than TemporarilyUnavailableRetryTimeout so that it won't timeout before server startup timeout (TemporarilyUnavailableRetryTimeout)
917950 if ( TemporarilyUnavailableRetry && TemporarilyUnavailableRetryTimeout * 1000 > ConnectTimeoutMilliseconds )
0 commit comments