55using DurableTask . Core . Entities . OperationFormat ;
66using Google . Protobuf ;
77using Microsoft . DurableTask . Entities ;
8- using Microsoft . DurableTask . Worker . Shims ;
8+ using Microsoft . DurableTask . Worker . Shims ;
9+ using Microsoft . Extensions . Caching . Memory ;
910using Microsoft . Extensions . DependencyInjection ;
1011using P = Microsoft . DurableTask . Protobuf ;
1112
@@ -25,7 +26,7 @@ namespace Microsoft.DurableTask.Worker.Grpc;
2526/// </para>
2627/// </remarks>
2728public static class GrpcEntityRunner
28- {
29+ {
2930 /// <summary>
3031 /// Deserializes entity batch request from <paramref name="encodedEntityRequest"/> and uses it to invoke the
3132 /// requested operations implemented by <paramref name="implementation"/>.
@@ -51,24 +52,100 @@ public static class GrpcEntityRunner
5152 /// </exception>
5253 public static async Task < string > LoadAndRunAsync (
5354 string encodedEntityRequest , ITaskEntity implementation , IServiceProvider ? services = null )
55+ {
56+ return await LoadAndRunAsync ( encodedEntityRequest , implementation , extendedSessionsCache : null , services : services ) ;
57+ }
58+
59+ /// <summary>
60+ /// Deserializes entity batch request from <paramref name="encodedEntityRequest"/> and uses it to invoke the
61+ /// requested operations implemented by <paramref name="implementation"/>.
62+ /// </summary>
63+ /// <param name="encodedEntityRequest">
64+ /// The encoded protobuf payload representing an entity batch request. This is a base64-encoded string.
65+ /// </param>
66+ /// <param name="implementation">
67+ /// An <see cref="ITaskEntity"/> implementation that defines the entity logic.
68+ /// </param>
69+ /// <param name="extendedSessionsCache">
70+ /// The cache of entity states which can be used to retrieve the entity state if this request is from within an extended session.
71+ /// </param>
72+ /// <param name="services">
73+ /// Optional <see cref="IServiceProvider"/> from which injected dependencies can be retrieved.
74+ /// </param>
75+ /// <returns>
76+ /// Returns a serialized result of the entity batch that should be used as the return value of the entity function
77+ /// trigger.
78+ /// </returns>
79+ /// <exception cref="ArgumentNullException">
80+ /// Thrown if <paramref name="encodedEntityRequest"/> or <paramref name="implementation"/> is <c>null</c>.
81+ /// </exception>
82+ /// <exception cref="ArgumentException">
83+ /// Thrown if <paramref name="encodedEntityRequest"/> contains invalid data.
84+ /// </exception>
85+ public static async Task < string > LoadAndRunAsync (
86+ string encodedEntityRequest , ITaskEntity implementation , ExtendedSessionsCache ? extendedSessionsCache , IServiceProvider ? services = null )
5487 {
5588 Check . NotNullOrEmpty ( encodedEntityRequest ) ;
5689 Check . NotNull ( implementation ) ;
5790
5891 P . EntityBatchRequest request = P . EntityBatchRequest . Parser . Base64Decode < P . EntityBatchRequest > (
59- encodedEntityRequest ) ;
92+ encodedEntityRequest ) ;
93+ Dictionary < string , object ? > properties = request . Properties . ToDictionary (
94+ pair => pair . Key ,
95+ pair => ProtoUtils . ConvertValueToObject ( pair . Value ) ) ;
6096
6197 EntityBatchRequest batch = request . ToEntityBatchRequest ( ) ;
6298 EntityId id = EntityId . FromString ( batch . InstanceId ! ) ;
63- TaskName entityName = new ( id . Name ) ;
64-
99+ TaskName entityName = new ( id . Name ) ;
100+
101+ bool addToExtendedSessions = false ;
102+ bool stateCached = false ;
103+ GrpcInstanceRunnerUtils . ParseRequestPropertiesAndInitializeCache (
104+ properties ,
105+ extendedSessionsCache ,
106+ out double extendedSessionIdleTimeoutInSeconds ,
107+ out bool isExtendedSession ,
108+ out bool entityStateIncluded ,
109+ out MemoryCache ? extendedSessions ) ;
110+
111+ if ( isExtendedSession && extendedSessions != null )
112+ {
113+ addToExtendedSessions = true ;
114+
115+ // If an entity state was provided, even if we already have one stored, we always want to use the provided state.
116+ if ( ! entityStateIncluded && extendedSessions . TryGetValue ( request . InstanceId , out string ? entityState ) )
117+ {
118+ batch . EntityState = entityState ;
119+ stateCached = true ;
120+ }
121+ }
122+
123+ if ( ! stateCached && ! entityStateIncluded )
124+ {
125+ // No state was provided, and we do not have one cached, so we cannot execute the batch request.
126+ return Convert . ToBase64String ( new P . EntityBatchResult { RequiresState = true } . ToByteArray ( ) ) ;
127+ }
128+
65129 DurableTaskShimFactory factory = services is null
66130 ? DurableTaskShimFactory . Default
67- : ActivatorUtilities . GetServiceOrCreateInstance < DurableTaskShimFactory > ( services ) ;
68-
69- TaskEntity entity = factory . CreateEntity ( entityName , implementation , id ) ;
70- EntityBatchResult result = await entity . ExecuteOperationBatchAsync ( batch ) ;
71-
131+ : ActivatorUtilities . GetServiceOrCreateInstance < DurableTaskShimFactory > ( services ) ;
132+
133+ TaskEntity entity = factory . CreateEntity ( entityName , implementation , id ) ;
134+ EntityBatchResult result = await entity . ExecuteOperationBatchAsync ( batch ) ;
135+
136+ if ( addToExtendedSessions )
137+ {
138+ // addToExtendedSessions can only be set to true if extendedSessions is not null
139+ extendedSessions ! . Set (
140+ request . InstanceId ,
141+ result . EntityState ,
142+ new MemoryCacheEntryOptions { SlidingExpiration = TimeSpan . FromSeconds ( extendedSessionIdleTimeoutInSeconds ) } ) ;
143+ }
144+ else
145+ {
146+ extendedSessions ? . Remove ( request . InstanceId ) ;
147+ }
148+
72149 P . EntityBatchResult response = result . ToEntityBatchResult ( ) ;
73150 byte [ ] responseBytes = response . ToByteArray ( ) ;
74151 return Convert . ToBase64String ( responseBytes ) ;
0 commit comments