1111// limitations under the License.
1212// ------------------------------------------------------------------------
1313
14+ using System ;
15+ using System ;
1416using System . Reflection ;
1517using System . Text . Json ;
18+ using System . Threading ;
1619using Grpc . Net . Client ;
1720using Microsoft . Extensions . Configuration ;
1821
@@ -31,6 +34,11 @@ protected DaprGenericClientBuilder(IConfiguration? configuration = null)
3134 this . GrpcEndpoint = DaprDefaults . GetDefaultGrpcEndpoint ( ) ;
3235 this . HttpEndpoint = DaprDefaults . GetDefaultHttpEndpoint ( ) ;
3336
37+ this . GrpcKeepAliveEnabled = DaprDefaults . GetDefaultGrpcKeepAliveEnable ( configuration ) ;
38+ this . GrpcKeepAliveTime = TimeSpan . FromSeconds ( DaprDefaults . GetDefaultGrpcKeepAliveTimeSeconds ( configuration ) ) ;
39+ this . GrpcKeepAliveTimeout = TimeSpan . FromSeconds ( DaprDefaults . GetDefaultGrpcKeepAliveTimeoutSeconds ( configuration ) ) ;
40+ this . GrpcKeepAlivePermitWithoutCalls = DaprDefaults . GetDefaultGrpcKeepAliveWithoutCalls ( configuration ) ;
41+
3442 this . GrpcChannelOptions = new GrpcChannelOptions ( )
3543 {
3644 // The gRPC client doesn't throw the right exception for cancellation
@@ -71,12 +79,32 @@ protected DaprGenericClientBuilder(IConfiguration? configuration = null)
7179 /// Property exposed for testing purposes.
7280 /// </summary>
7381 public string DaprApiToken { get ; private set ; }
74-
82+
7583 /// <summary>
7684 /// Property exposed for testing purposes.
7785 /// </summary>
7886 internal TimeSpan Timeout { get ; private set ; }
7987
88+ /// <summary>
89+ /// Property exposed for testing purposes.
90+ /// </summary>
91+ internal bool GrpcKeepAliveEnabled { get ; private set ; }
92+
93+ /// <summary>
94+ /// Property exposed for testing purposes.
95+ /// </summary>
96+ internal TimeSpan GrpcKeepAliveTime { get ; private set ; }
97+
98+ /// <summary>
99+ /// Property exposed for testing purposes.
100+ /// </summary>
101+ internal TimeSpan GrpcKeepAliveTimeout { get ; private set ; }
102+
103+ /// <summary>
104+ /// Property exposed for testing purposes.
105+ /// </summary>
106+ internal bool GrpcKeepAlivePermitWithoutCalls { get ; private set ; }
107+
80108 /// <summary>
81109 /// Overrides the HTTP endpoint used by the Dapr client for communicating with the Dapr runtime.
82110 /// </summary>
@@ -180,6 +208,50 @@ public DaprGenericClientBuilder<TClientBuilder> UseTimeout(TimeSpan timeout)
180208 return this ;
181209 }
182210
211+ /// <summary>
212+ /// Enables or disables gRPC keep-alive.
213+ /// </summary>
214+ /// <param name="enabled">Whether to enable gRPC keep-alive.</param>
215+ /// <returns>The <see cref="DaprGenericClientBuilder{TClientBuilder}" /> instance.</returns>
216+ public DaprGenericClientBuilder < TClientBuilder > UseGrpcKeepAlive ( bool enabled )
217+ {
218+ this . GrpcKeepAliveEnabled = enabled ;
219+ return this ;
220+ }
221+
222+ /// <summary>
223+ /// Sets the gRPC keep-alive time interval.
224+ /// </summary>
225+ /// <param name="keepAliveTime">The time interval between keep-alive pings.</param>
226+ /// <returns>The <see cref="DaprGenericClientBuilder{TClientBuilder}" /> instance.</returns>
227+ public DaprGenericClientBuilder < TClientBuilder > UseGrpcKeepAliveTime ( TimeSpan keepAliveTime )
228+ {
229+ this . GrpcKeepAliveTime = keepAliveTime ;
230+ return this ;
231+ }
232+
233+ /// <summary>
234+ /// Sets the gRPC keep-alive timeout.
235+ /// </summary>
236+ /// <param name="keepAliveTimeout">The time to wait for a keep-alive ping response before considering the connection dead.</param>
237+ /// <returns>The <see cref="DaprGenericClientBuilder{TClientBuilder}" /> instance.</returns>
238+ public DaprGenericClientBuilder < TClientBuilder > UseGrpcKeepAliveTimeout ( TimeSpan keepAliveTimeout )
239+ {
240+ this . GrpcKeepAliveTimeout = keepAliveTimeout ;
241+ return this ;
242+ }
243+
244+ /// <summary>
245+ /// Sets whether gRPC keep-alive should be sent when there are no active calls.
246+ /// </summary>
247+ /// <param name="permitWithoutCalls">Whether to send keep-alive pings even when there are no active calls.</param>
248+ /// <returns>The <see cref="DaprGenericClientBuilder{TClientBuilder}" /> instance.</returns>
249+ public DaprGenericClientBuilder < TClientBuilder > UseGrpcKeepAlivePermitWithoutCalls ( bool permitWithoutCalls )
250+ {
251+ this . GrpcKeepAlivePermitWithoutCalls = permitWithoutCalls ;
252+ return this ;
253+ }
254+
183255 /// <summary>
184256 /// Builds out the inner DaprClient that provides the core shape of the
185257 /// runtime gRPC client used by the consuming package.
@@ -209,8 +281,25 @@ protected internal (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint
209281 //Configure the HTTP client
210282 var httpClient = ConfigureHttpClient ( assembly ) ;
211283 this . GrpcChannelOptions . HttpClient = httpClient ;
284+
285+ if ( this . GrpcKeepAliveEnabled )
286+ {
287+ if ( ! ( this . GrpcChannelOptions . HttpHandler is SocketsHttpHandler ) )
288+ {
289+ var handler = new SocketsHttpHandler ( ) ;
290+ this . GrpcChannelOptions . HttpHandler = handler ;
291+ }
292+
293+ var socketsHandler = ( SocketsHttpHandler ) this . GrpcChannelOptions . HttpHandler ;
294+
295+ socketsHandler . KeepAlivePingDelay = this . GrpcKeepAliveTime ;
296+ socketsHandler . KeepAlivePingTimeout = this . GrpcKeepAliveTimeout ;
297+ socketsHandler . KeepAlivePingPolicy = this . GrpcKeepAlivePermitWithoutCalls
298+ ? HttpKeepAlivePingPolicy . Always
299+ : HttpKeepAlivePingPolicy . WithActiveRequests ;
300+ }
212301
213- var channel = GrpcChannel . ForAddress ( this . GrpcEndpoint , this . GrpcChannelOptions ) ;
302+ var channel = GrpcChannel . ForAddress ( this . GrpcEndpoint , this . GrpcChannelOptions ) ;
214303 return ( channel , httpClient , httpEndpoint , this . DaprApiToken ) ;
215304 }
216305
@@ -222,17 +311,17 @@ protected internal (GrpcChannel channel, HttpClient httpClient, Uri httpEndpoint
222311 private HttpClient ConfigureHttpClient ( Assembly assembly )
223312 {
224313 var httpClient = HttpClientFactory is not null ? HttpClientFactory ( ) : new HttpClient ( ) ;
225-
314+
226315 //Set the timeout as necessary
227316 if ( this . Timeout > TimeSpan . Zero )
228317 {
229318 httpClient . Timeout = this . Timeout ;
230319 }
231-
320+
232321 //Set the user agent
233322 var userAgent = DaprClientUtilities . GetUserAgent ( assembly ) ;
234323 httpClient . DefaultRequestHeaders . Add ( "User-Agent" , userAgent . ToString ( ) ) ;
235-
324+
236325 //Set the API token
237326 var apiTokenHeader = DaprClientUtilities . GetDaprApiTokenHeader ( this . DaprApiToken ) ;
238327 if ( apiTokenHeader is not null )
0 commit comments