/// <summary> /// Internal constructor. /// </summary> /// <param name="parentWorkflow">The associated parent workflow.</param> /// <param name="methodName"> /// Optionally identifies the target activity method by the name specified in /// the <c>[ActivityMethod]</c> attribute tagging the method. Pass a <c>null</c> /// or empty string to specify the default method. /// </param> /// <param name="options">The activity options or <c>null</c>.</param> internal LocalActivityFutureStub(Workflow parentWorkflow, string methodName = null, LocalActivityOptions options = null) { Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); var activityInterface = typeof(TActivityInterface); TemporalHelper.ValidateActivityInterface(activityInterface); this.parentWorkflow = parentWorkflow; this.hasStarted = false; this.targetMethod = TemporalHelper.GetActivityTarget(activityInterface, methodName).TargetMethod; this.options = LocalActivityOptions.Normalize(parentWorkflow.Client, options); }
/// <summary> /// Internal constructor. /// </summary> /// <param name="parentWorkflow">The associated parent workflow.</param> /// <param name="methodName"> /// Optionally identifies the target activity method by the name specified in /// the <c>[ActivityMethod]</c> attribute tagging the method. Pass a <c>null</c> /// or empty string to target the default method. /// </param> /// <param name="options">The activity options or <c>null</c>.</param> internal ActivityFutureStub(Workflow parentWorkflow, string methodName = null, ActivityOptions options = null) { Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); var activityInterface = typeof(TActivityInterface); TemporalHelper.ValidateActivityInterface(activityInterface); this.parentWorkflow = parentWorkflow; this.client = parentWorkflow.Client; this.hasStarted = false; var activityTarget = TemporalHelper.GetActivityTarget(activityInterface, methodName); var methodAttribute = activityTarget.MethodAttribute; this.activityTypeName = activityTarget.ActivityTypeName; this.targetMethod = activityTarget.TargetMethod; this.options = ActivityOptions.Normalize(client, options, typeof(TActivityInterface), activityTarget.TargetMethod); }
//--------------------------------------------------------------------- // Static members /// <summary> /// Normalizes the options passed by creating or cloning a new instance as /// required and filling unset properties using default client settings. /// </summary> /// <param name="client">The associated Temporal client.</param> /// <param name="options">The input options or <c>null</c>.</param> /// <param name="activityInterface">Optionally specifies the activity interface definition.</param> /// /// <param name="method">Optionally specifies the target workflow method.</param> /// <returns>The normalized options.</returns> /// <exception cref="ArgumentNullException">Thrown if a valid task queue is not specified.</exception> internal static ActivityOptions Normalize(TemporalClient client, ActivityOptions options, Type activityInterface = null, MethodInfo method = null) { Covenant.Requires <ArgumentNullException>(client != null, nameof(client)); options = options ?? new ActivityOptions(); ActivityInterfaceAttribute interfaceAttribute = null; ActivityMethodAttribute methodAttribute = null; if (activityInterface != null) { TemporalHelper.ValidateActivityInterface(activityInterface); interfaceAttribute = activityInterface.GetCustomAttribute <ActivityInterfaceAttribute>(); } if (method != null) { methodAttribute = method.GetCustomAttribute <ActivityMethodAttribute>(); } if (string.IsNullOrEmpty(options.Namespace)) { if (!string.IsNullOrEmpty(methodAttribute?.Namespace)) { options.Namespace = methodAttribute.Namespace; } if (string.IsNullOrEmpty(options.Namespace) && !string.IsNullOrEmpty(interfaceAttribute?.Namespace)) { options.Namespace = interfaceAttribute.Namespace; } } if (string.IsNullOrEmpty(options.TaskQueue)) { if (!string.IsNullOrEmpty(methodAttribute?.TaskQueue)) { options.TaskQueue = methodAttribute.TaskQueue; } if (string.IsNullOrEmpty(options.TaskQueue) && !string.IsNullOrEmpty(interfaceAttribute?.TaskQueue)) { options.TaskQueue = interfaceAttribute.TaskQueue; } } if (options.ScheduleToCloseTimeout <= TimeSpan.Zero) { if (methodAttribute != null && methodAttribute.ScheduleToCloseTimeoutSeconds > 0) { options.ScheduleToCloseTimeout = TimeSpan.FromSeconds(methodAttribute.ScheduleToCloseTimeoutSeconds); } if (options.ScheduleToCloseTimeout <= TimeSpan.Zero) { options.ScheduleToCloseTimeout = client.Settings.ActivityScheduleToCloseTimeout; } } if (options.ScheduleToStartTimeout <= TimeSpan.Zero) { if (methodAttribute != null && methodAttribute.ScheduleToStartTimeoutSeconds > 0) { options.ScheduleToStartTimeout = TimeSpan.FromSeconds(methodAttribute.ScheduleToStartTimeoutSeconds); } if (options.ScheduleToStartTimeout <= TimeSpan.Zero) { options.ScheduleToStartTimeout = client.Settings.ActivityScheduleToStartTimeout; } } if (options.StartToCloseTimeout <= TimeSpan.Zero) { if (methodAttribute != null && methodAttribute.StartToCloseTimeoutSeconds > 0) { options.StartToCloseTimeout = TimeSpan.FromSeconds(methodAttribute.StartToCloseTimeoutSeconds); } if (options.StartToCloseTimeout <= TimeSpan.Zero) { options.StartToCloseTimeout = client.Settings.ActivityStartToCloseTimeout; } } return(options); }
/// <summary> /// Registers an activity implementation with the temporal-proxy. /// </summary> /// <param name="activityType">The activity implementation type.</param> /// <exception cref="RegistrationException">Thrown when there's a problem with the registration.</exception> private async Task RegisterActivityImplementationAsync(Type activityType) { await SyncContext.Clear; TemporalHelper.ValidateActivityImplementation(activityType); var interfaceType = TemporalHelper.GetActivityInterface(activityType); TemporalHelper.ValidateActivityInterface(interfaceType); // We need to register each activity method that implements an activity interface method // with the same signature that that was tagged by [ActivityMethod]. // // First, we'll create a dictionary that maps method signatures from any inherited // interfaces that are tagged by [ActivityMethod] to the attribute. var methodSignatureToAttribute = new Dictionary <string, ActivityMethodAttribute>(); foreach (var method in interfaceType.GetMethods(BindingFlags.Public | BindingFlags.Instance)) { var activityMethodAttribute = method.GetCustomAttribute <ActivityMethodAttribute>(); if (activityMethodAttribute == null) { continue; } var signature = method.ToString(); if (methodSignatureToAttribute.ContainsKey(signature)) { throw new NotSupportedException($"Activity type [{activityType.FullName}] cannot implement the [{signature}] method from two different interfaces."); } methodSignatureToAttribute.Add(signature, activityMethodAttribute); } // Next, we need to register the activity methods that implement the // activity interface. foreach (var method in activityType.GetMethods()) { if (!methodSignatureToAttribute.TryGetValue(method.ToString(), out var activityMethodAttribute)) { continue; } var activityTarget = TemporalHelper.GetActivityTarget(interfaceType, activityMethodAttribute.Name); lock (nameToActivityRegistration) { if (nameToActivityRegistration.TryGetValue(activityTarget.ActivityTypeName, out var registration)) { if (!object.ReferenceEquals(registration.ActivityType, registration.ActivityType)) { throw new InvalidOperationException($"Conflicting activity type registration: Activity type [{activityType.FullName}] is already registered for activity type name [{activityTarget.ActivityTypeName}]."); } } else { nameToActivityRegistration[activityTarget.ActivityTypeName] = new ActivityRegistration() { ActivityType = activityType, ActivityMethod = method, ActivityMethodParameterTypes = method.GetParameterTypes() }; } } var reply = (ActivityRegisterReply)await Client.CallProxyAsync( new ActivityRegisterRequest() { WorkerId = WorkerId, Name = activityTarget.ActivityTypeName, }); reply.ThrowOnError(); } }