/// <remarks> /// Fire and forget mode (async void). /// </remarks> public async void ExecuteAndAwaitInBackground(ExecuteRoutineIntent intent, Task proxyTask) { // Tell platform to track the completion. // Do it before commit as a routine may complete immediately. var tcs = new TaskCompletionSource <TaskResult>(); _routineCompletionNotifier.NotifyCompletion(intent.ServiceId, intent.MethodId, intent.Id, tcs, default); // Commit intent var options = new TransitionCommitOptions { // Set the hint about the synchronous call mode. NotifyOnRoutineCompletion = true }; var actions = new ScheduledActions { ExecuteRoutineIntents = new List <ExecuteRoutineIntent> { intent } }; await _transitionCommitter.CommitAsync(actions, transitionCarrier : null, options : options, ct : default); // Await for completion and set the result. var routineResult = await tcs.Task; proxyTask.TrySetResult(routineResult); }
/// <remarks> /// Fire and forget mode (async void). /// </remarks> public async void ExecuteAndAwaitInBackground(ExecuteRoutineIntent intent, Task proxyTask) { // Commit intent // Set the hint about the synchronous call mode. intent.NotifyOnCompletion = true; var actions = new ScheduledActions { ExecuteRoutineIntents = new List <ExecuteRoutineIntent> { intent } }; await _transitionCommitter.CommitAsync(actions, transitionCarrier : null, ct : default(CancellationToken)); // Tell platform to track the completion. var tcs = new TaskCompletionSource <TaskResult>(); _routineCompletionNotifier.NotifyCompletion(intent.Id, tcs); // Await for completion and set the result. var routineResult = await tcs.Task; proxyTask.TrySetResult(routineResult); }
public async Task <ActiveRoutineInfo> ScheduleRoutineAsync( ExecuteRoutineIntent intent, CancellationToken ct) { var pregeneratedRoutineId = intent.Id.ToString(); var routineDescriptor = new RoutineDescriptor { IntentId = intent.Id, MethodId = intent.MethodId, RoutineId = pregeneratedRoutineId }; var eventData = new RoutineEventData { ServiceId = intent.ServiceId, Routine = routineDescriptor, Caller = intent.Caller, Continuation = intent.Continuation, Parameters = _serializer.SerializeToString(intent.Parameters) }; var eventEnvelope = new RoutineEventEnvelope { CloudEventsVersion = CloudEventsEnvelope.Version, EventType = DasyncCloudEventsTypes.InvokeRoutine.Name, EventTypeVersion = DasyncCloudEventsTypes.InvokeRoutine.Version, Source = "/" + (intent.Caller?.ServiceId.ServiceName ?? ""), EventID = intent.Id.ToString(), EventTime = DateTimeOffset.Now, ContentType = "application/json", Data = CloudEventsSerialization.Serialize(eventData) }; var message = new CloudQueueMessage( JsonConvert.SerializeObject(eventEnvelope, CloudEventsSerialization.JsonSerializerSettings)); while (true) { try { await _transitionsQueue.AddMessageAsync(message, null, null, ct); break; } catch (QueueDoesNotExistException) { await _transitionsQueue.CreateAsync(ct); } } return(new ActiveRoutineInfo { RoutineId = pregeneratedRoutineId }); }
public void RegisterIntent(ExecuteRoutineIntent intent, Task proxyTask) { var transitionContext = Context; var actions = transitionContext.ScheduledActions; if (actions.ExecuteRoutineIntents == null) { actions.ExecuteRoutineIntents = new List <ExecuteRoutineIntent>(); } actions.ExecuteRoutineIntents.Add(intent); _taskContinuationTracker.StartTracking(proxyTask, _onRoutineContinuationSetCallback, intent); }
public Task <ActiveRoutineInfo> ScheduleRoutineAsync( ExecuteRoutineIntent intent, CancellationToken ct) { #warning Need to send message first, then create routine record. var routineId = Interlocked.Increment(ref _dataStore.RoutineCounter); var routineRecord = new RoutineStateRecord { ETag = DateTime.UtcNow.Ticks.ToString("X16"), Id = routineId.ToString(), Completion = new TaskCompletionSource <string>(), Continuation = intent.Continuation == null ? null : _serializer.SerializeToString(intent.Continuation) }; lock (_dataStore.Routines) { _dataStore.Routines.Add(routineRecord.Id, routineRecord); } var transitionDescriptor = new TransitionDescriptor { Type = TransitionType.InvokeRoutine, ETag = routineRecord.ETag }; var routineDescriptor = new RoutineDescriptor { MethodId = intent.MethodId, IntentId = intent.Id, RoutineId = routineRecord.Id, ETag = routineRecord.ETag }; var message = new Message { //["IntentId"] = _serializer.Serialize(intent.Id), [nameof(TransitionDescriptor)] = _serializer.SerializeToString(transitionDescriptor), [nameof(ServiceId)] = _serializer.SerializeToString(intent.ServiceId), [nameof(RoutineDescriptor)] = _serializer.SerializeToString(routineDescriptor), ["Parameters"] = _serializer.SerializeToString(intent.Parameters) }; _dataStore.ScheduleMessage(message); var info = new ActiveRoutineInfo { RoutineId = routineRecord.Id }; return(Task.FromResult(info)); }
public static MethodInvocationData CreateMethodInvocationData(ExecuteRoutineIntent intent, ITransitionContext context) { return(new MethodInvocationData { IntentId = intent.Id, Service = intent.Service, Method = intent.Method, Parameters = intent.Parameters, Continuation = intent.Continuation, Caller = context?.CurrentAsCaller(), FlowContext = context?.FlowContext }); }
/// <remarks> /// Fire and forget mode (async void). /// </remarks> public async void ExecuteAndAwaitInBackground(ExecuteRoutineIntent intent, Task proxyTask) { #warning Add infrastructure exception handling try { #warning need to associate a routine with a cancellaion token, and abandon polling when canceled var ct = CancellationToken.None; var fabricConnector = _fabricConnectorSelector.Select(intent.ServiceId); var routineInfo = await fabricConnector.ScheduleRoutineAsync(intent, ct); for (var i = 0; ; i++) { #warning Ideally need to handle 'fire an forget' cases - the continuation is never set? //if (!proxyTask.continuation == null after 1 min?) // return; routineInfo = await fabricConnector.PollRoutineResultAsync(routineInfo, ct); if (routineInfo.Result != null) { break; } TimeSpan delayInterval; if (routineInfo is IRoutinePollInterval routinePollInterval) { delayInterval = routinePollInterval.Suggest(i); } else { delayInterval = TimeSpan.FromSeconds(0.5); } await Task.Delay(delayInterval); } if (!proxyTask.TrySetResult(routineInfo.Result)) { throw new InvalidOperationException("Critical error - cannot set result to the proxy task."); } } catch (Exception ex) { //Console.WriteLine(ex.ToString()); throw; } }
public Task <ActiveRoutineInfo> ScheduleRoutineAsync( ExecuteRoutineIntent intent, CancellationToken ct) { var pregeneratedRoutineId = intent.Id.ToString(); var routineDescriptor = new RoutineDescriptor { IntentId = intent.Id, MethodId = intent.MethodId, RoutineId = pregeneratedRoutineId }; var eventData = new RoutineEventData { ServiceId = intent.ServiceId, Routine = routineDescriptor, Caller = intent.Caller, Continuation = intent.Continuation, Parameters = _serializer.SerializeToString(intent.Parameters) }; var eventEnvelope = new RoutineEventEnvelope { CloudEventsVersion = CloudEventsEnvelope.Version, EventType = DasyncCloudEventsTypes.InvokeRoutine.Name, EventTypeVersion = DasyncCloudEventsTypes.InvokeRoutine.Version, Source = "/" + (intent.Caller?.ServiceId.ServiceName ?? ""), EventID = intent.Id.ToString(), EventTime = DateTimeOffset.Now, ContentType = "application/json", Data = CloudEventsSerialization.Serialize(eventData) }; var fileName = intent.Id.ToString() + ".json"; var filePath = Path.Combine(_transitionsDirectory, fileName); var content = CloudEventsSerialization.Serialize(eventEnvelope); File.WriteAllText(filePath, content, Encoding.UTF8); var info = new ActiveRoutineInfo { RoutineId = pregeneratedRoutineId }; return(Task.FromResult(info)); }
/// <remarks> /// Fire and forget mode (async void). /// </remarks> public async void ExecuteAndAwaitInBackground(ExecuteRoutineIntent intent, Task proxyTask) { // TODO: exception handling var result = await _singleMethodInvoker.InvokeAsync(intent); if (result.Outcome == InvocationOutcome.Complete) { proxyTask.TrySetResult(result.Result); } else { var tcs = new TaskCompletionSource <ITaskResult>(); _routineCompletionNotifier.NotifyOnCompletion(intent.Service, intent.Method, intent.Id, tcs, default); var taskResult = await tcs.Task; proxyTask.TrySetResult(taskResult); } }
public Task Execute <TParameters>(IProxy proxy, MethodInfo methodInfo, ref TParameters parameters) where TParameters : IValueContainer { var serviceProxyContext = (ServiceProxyContext)proxy.Context; var intent = new ExecuteRoutineIntent { Id = _numericIdGenerator.NewId(), ServiceId = serviceProxyContext.Descriptor.Id, MethodId = _routineMethodIdProvider.GetId(methodInfo), Parameters = parameters }; var taskResultType = // Dispose() does not return a task, and is the only exception. #warning check if it's really IDisposable.Dispose methodInfo.ReturnType == typeof(void) ? TaskAccessor.VoidTaskResultType : TaskAccessor.GetTaskResultType(methodInfo.ReturnType); var taskState = new RoutineReference { IntentId = intent.Id #warning must have id of actual routine for dynamic subscription (subscribe after a routine already scheduled). }; var proxyTask = TaskAccessor.CreateTask(taskState, taskResultType); bool executeInline = !_transitionScope.IsActive || !IsCalledByRoutine( _transitionScope.CurrentMonitor.Context, // Skip 2 stack frames: current method and dynamically-generated proxy. // WARNING! DO NOT PUT 'new StackFrame()' into a helper method! new StackFrame(skipFrames: 2, fNeedFileInfo: false)); if (executeInline) { ExecuteAndAwaitInBackground(intent, proxyTask); } else { _transitionScope.CurrentMonitor.RegisterIntent(intent, proxyTask); } return(proxyTask); }
public async Task <RoutineInfo> ScheduleRoutineAsync(ExecuteRoutineIntent intent, NameValueCollection context, CancellationToken ct) { var uri = string.Concat(_serviceHttpConfigurator.GetUrl(_serviceDefinition), "/", intent.MethodId.MethodName); var json = _dasyncJsonSerializer.SerializeToString(intent); while (true) { try { var content = new StringContent(json); content.Headers.ContentType = new MediaTypeHeaderValue("application/dasync+json"); if (context?.Count > 0) { content.Headers.TryAddWithoutValidation(DasyncHttpHeaders.Context, context.Serialize()); } var response = await _httpClient.PutAsync(uri, content, ct); var statusCode = (int)response.StatusCode; if (statusCode == DasyncHttpCodes.Succeeded || statusCode == DasyncHttpCodes.Faulted || statusCode == DasyncHttpCodes.Canceled) { TaskResult taskResult; using (var stream = await response.Content.ReadAsStreamAsync()) { taskResult = _dasyncJsonSerializer.Deserialize <TaskResult>(stream); } return(new RoutineInfo { Result = taskResult }); } else { throw new InvalidOperationException($"Unexpected HTTP {statusCode} response:\r\n{await response.Content.ReadAsStringAsync()}"); } } catch (Exception) { await Task.Delay(5_000); } } }
public Task Execute <TParameters>(IProxy proxy, MethodInfo methodInfo, ref TParameters parameters) where TParameters : IValueContainer { var serviceProxyContext = (ServiceProxyContext)proxy.Context; var intent = new ExecuteRoutineIntent { Id = _numericIdGenerator.NewId(), ServiceId = serviceProxyContext.Service.Id, MethodId = _routineMethodIdProvider.GetId(methodInfo), Parameters = parameters }; var taskResultType = // Dispose() does not return a task, and is the only exception. #warning check if it's really IDisposable.Dispose methodInfo.ReturnType == typeof(void) ? TaskAccessor.VoidTaskResultType : TaskAccessor.GetTaskResultType(methodInfo.ReturnType); var taskState = new RoutineReference { IntentId = intent.Id #warning must have id of actual routine for dynamic subscription (subscribe after a routine already scheduled). }; var proxyTask = TaskAccessor.CreateTask(taskState, taskResultType); if (_transitionScope.IsActive) { _transitionScope.CurrentMonitor.RegisterIntent(intent, proxyTask); } else { ExecuteAndAwaitInBackground(intent, proxyTask); } return(proxyTask); }
public async Task HandleAsync(PathString basePath, HttpContext context, CancellationToken ct) { var headers = context.Response.Headers; headers.Add(DasyncHttpHeaders.PoweredBy, "D-ASYNC"); var basePathSegmentCount = basePath.Value.Split('/', StringSplitOptions.RemoveEmptyEntries).Length; var pathSegments = context.Request.Path.Value.Split('/', StringSplitOptions.RemoveEmptyEntries).Skip(basePathSegmentCount).ToArray(); if (pathSegments.Length == 0 && context.Request.Query.ContainsKey("react")) { await HandleEventReactionAsync(null, context, ct); return; } if (pathSegments.Length == 0) { context.Response.StatusCode = 404; context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("Empty request URL")); return; } var serviceName = pathSegments[0]; var serviceDefinition = _communicationModelProvider.Model.FindServiceByName(serviceName); if (serviceDefinition == null) { context.Response.StatusCode = 404; context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"Service '{serviceName}' is not registered")); return; } if (serviceDefinition.Type == ServiceType.External) { context.Response.StatusCode = 403; // Forbidden context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"Cannot invoke external service '{serviceName}' on your behalf")); return; } if (_eventDispatcher != null && pathSegments.Length == 1 && context.Request.Query.ContainsKey("react")) { await HandleEventReactionAsync(serviceDefinition, context, ct); return; } if (pathSegments.Length == 1) { context.Response.StatusCode = 404; context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request URL does not contain a service method")); return; } if (_eventDispatcher != null && context.Request.Query.ContainsKey("subscribe")) { await HandleEventSubsciptionAsync(serviceDefinition, pathSegments[1], context, ct); return; } var methodName = pathSegments[1]; if (pathSegments.Length == 3) { await HandleResultPoll(context, serviceName, methodName, intentId : pathSegments[2]); return; } if (pathSegments.Length > 3) { context.Response.StatusCode = 404; context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request URL contains extra segments")); return; } var isQueryRequest = context.Request.Method == "GET"; var isCommandRequest = context.Request.Method == "PUT" || context.Request.Method == "POST"; var contentType = context.Request.GetContentType(); var isJsonRequest = string.Equals(contentType.MediaType, "application/json", StringComparison.OrdinalIgnoreCase); var isDasyncJsonRequest = string.Equals(contentType.MediaType, "application/dasync+json", StringComparison.OrdinalIgnoreCase); if (!isQueryRequest && !isJsonRequest && !isDasyncJsonRequest) { context.Response.StatusCode = 406; // Not Acceptable context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request content type is either not specified or not supported")); return; } if (!isQueryRequest && !isCommandRequest) { context.Response.StatusCode = 405; // Method Not Allowed context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The service method invocation must use one of these HTTP verbs: GET, POST, PUT")); return; } var routineMethodId = new RoutineMethodId { MethodName = methodName }; #warning Use model for method resolution instead MethodInfo routineMethod; try { routineMethod = _routineMethodResolver.Resolve(serviceDefinition, routineMethodId); } catch { context.Response.StatusCode = 404; context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"The service '{serviceDefinition.Name}' does not have method '{methodName}'")); return; } #warning Use model to determine if the method is command or query var isQueryMethod = GetWordAndSynonyms.Contains(GetFirstWord(methodName)); if (isQueryRequest && !isQueryMethod) { context.Response.StatusCode = 404; context.Response.ContentType = "text/plain"; await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"The method '{methodName}' of service '{serviceDefinition.Name}' is a command, but not a query, thus must be invoked with POST or PUT verb")); return; } string parametersJson = null; ContinuationDescriptor continuationDescriptor = null; if (isQueryRequest) { var inputObject = new JObject(); foreach (var kvPair in context.Request.Query) { if (kvPair.Value.Count == 0) { continue; } var parameterName = kvPair.Key; if (kvPair.Value.Count == 1) { var parameterValue = kvPair.Value[0]; inputObject.Add(parameterName, parameterValue); } else { var values = new JArray(); foreach (var value in kvPair.Value) { values.Add(value); } inputObject.Add(parameterName, values); } } parametersJson = inputObject.ToString(); } else if (isJsonRequest) { parametersJson = await context.Request.ReadBodyAsStringAsync(contentType); } else { var inputJson = await context.Request.ReadBodyAsStringAsync(contentType); var envelope = JsonConvert.DeserializeObject <CommandEnvelope>(inputJson, JsonSettings); parametersJson = envelope.Parameters; continuationDescriptor = envelope.Continuation; } var methodInvoker = _methodInvokerFactory.Create(routineMethod); var parameterContainer = methodInvoker.CreateParametersContainer(); if (!string.IsNullOrWhiteSpace(parametersJson)) { if (isDasyncJsonRequest) { _dasyncJsonSerializer.Populate(parametersJson, parameterContainer); } else { JsonConvert.PopulateObject(parametersJson, parameterContainer, JsonSettings); } } var externalRequestId = context.Request.Headers.TryGetValue(DasyncHttpHeaders.RequestId, out var requestIdValues) && requestIdValues.Count > 0 ? requestIdValues[0] : null; var externalCorrelationId = context.Request.Headers.TryGetValue(DasyncHttpHeaders.CorrelationId, out var correlationIdValues) && correlationIdValues.Count > 0 ? correlationIdValues[0] : null; var rfc7240Preferences = GetPreferences(context.Request.Headers); var isHttpRequestBlockingExecution = !(rfc7240Preferences.RespondAsync == true); _transitionUserContext.Current = GetUserContext(context); _intentPreprocessor.PrepareContext(context); if (await _intentPreprocessor.PreprocessAsync(context, serviceDefinition, routineMethodId, parameterContainer).ConfigureAwait(false)) { return; } var serviceId = new ServiceId { ServiceName = serviceDefinition.Name }; var intentId = _idGenerator.NewId(); if (isQueryMethod) { var serviceInstance = _domainServiceProvider.GetService(serviceDefinition.Implementation); foreach (var postAction in _transitionActions) { await postAction.OnRoutineStartAsync(serviceDefinition, serviceId, routineMethodId, intentId); } Task task; try { task = methodInvoker.Invoke(serviceInstance, parameterContainer); if (routineMethod.ReturnType != typeof(void)) { try { await task; } catch { } } } catch (Exception ex) { if (ex is TargetInvocationException) { ex = ex.InnerException; } task = Task.FromException(ex); } var taskResult = task?.ToTaskResult() ?? new TaskResult(); foreach (var postAction in _transitionActions) { await postAction.OnRoutineCompleteAsync(serviceDefinition, serviceId, routineMethodId, intentId, taskResult); } await RespondWithRoutineResult(context, taskResult, isDasyncJsonRequest); } else { var intent = new ExecuteRoutineIntent { Id = intentId, ServiceId = new ServiceId { ServiceName = serviceDefinition.Name }, MethodId = routineMethodId, Parameters = parameterContainer, Continuation = continuationDescriptor }; var actions = new ScheduledActions { ExecuteRoutineIntents = new List <ExecuteRoutineIntent> { intent } }; var options = new TransitionCommitOptions { NotifyOnRoutineCompletion = isHttpRequestBlockingExecution, RequestId = externalRequestId, CorrelationId = externalCorrelationId ?? externalRequestId }; await _transitionCommitter.CommitAsync(actions, null, options, default); var waitTime = rfc7240Preferences.Wait; if (isHttpRequestBlockingExecution || waitTime > MaxLongPollTime) { waitTime = MaxLongPollTime; } if (waitTime > TimeSpan.Zero) { var cts = new CancellationTokenSource(); var completionSink = new TaskCompletionSource <TaskResult>(); _routineCompletionNotifier.NotifyCompletion(intent.ServiceId, intent.MethodId, intent.Id, completionSink, cts.Token); try { var taskResult = await completionSink.Task.WithTimeout(waitTime); await RespondWithRoutineResult(context, taskResult, isDasyncJsonRequest); return; } catch (TaskCanceledException) { cts.Cancel(); } } var location = context.Request.Path.ToString() + "/" + intent.Id; context.Response.Headers.Add("Location", location); context.Response.StatusCode = DasyncHttpCodes.Scheduled; return; } }
public Task Execute <TParameters>(IProxy proxy, MethodInfo methodInfo, ref TParameters parameters) where TParameters : IValueContainer { var serviceProxyContext = (ServiceProxyContext)proxy.Context; var methodDefinition = serviceProxyContext.Definition.FindMethod(methodInfo); if (methodDefinition == null || methodDefinition.IsIgnored) { var invoker = _methodInvokerFactory.Create(methodInfo); return(invoker.Invoke(proxy, parameters)); } var intent = new ExecuteRoutineIntent { Id = _idGenerator.NewId(), Service = serviceProxyContext.Descriptor.Id, Method = _routineMethodIdProvider.GetId(methodInfo), Parameters = parameters }; Type taskResultType = methodInfo.ReturnType == typeof(void) ? TaskAccessor.VoidTaskResultType : TaskAccessor.GetTaskResultType(methodInfo.ReturnType); var taskState = new RoutineReference { ServiceId = intent.Service, MethodId = intent.Method, IntentId = intent.Id #warning must have id of actual routine for dynamic subscription (subscribe after a routine already scheduled). }; var proxyTask = TaskAccessor.CreateTask(taskState, taskResultType); bool invokedByRunningMethod = _transitionScope.IsActive && IsCalledByRoutine( _transitionScope.CurrentMonitor.Context, // Skip 2 stack frames: current method and dynamically-generated proxy. // WARNING! DO NOT PUT 'new StackFrame()' into a helper method! new StackFrame(skipFrames: 2, fNeedFileInfo: false)); bool ignoreTransaction = !invokedByRunningMethod; if (!ignoreTransaction && _transitionScope.IsActive) { var runningMethodSettings = _communicationSettingsProvider.GetMethodSettings( _transitionScope.CurrentMonitor.Context.MethodRef.Definition); if (!runningMethodSettings.Transactional) { ignoreTransaction = true; } } if (!ignoreTransaction) { var methodSettings = _communicationSettingsProvider.GetMethodSettings(methodDefinition); if (methodSettings.IgnoreTransaction) { ignoreTransaction = true; } } if (ignoreTransaction) { ExecuteAndAwaitInBackground(intent, proxyTask); } else { _transitionScope.CurrentMonitor.RegisterIntent(intent, proxyTask); } return(proxyTask); }
public void HandleWhenAllAsDetectedContinuation( Task whenAllTask, Task[] awaitedTasks, ExecuteRoutineIntent awaitedRoutineIntent, ITransitionMonitor monitor) { ExecuteRoutineIntent executeWhenAllIntent; lock (whenAllTask) { var state = whenAllTask.AsyncState as WhenAllProxyTaskState; if (state == null) { var whenAllIntentId = _numericIdGenerator.NewId(); Type itemType = null; var resultType = whenAllTask.GetResultType(); if (resultType.IsArray) { itemType = resultType.GetElementType(); } executeWhenAllIntent = new ExecuteRoutineIntent { Id = whenAllIntentId, ServiceId = new ServiceId { ServiceName = awaitedRoutineIntent.ServiceId.ServiceName, ProxyName = nameof(IntrinsicRoutines) }, MethodId = _routineMethodIdProvider.GetId( IntrinsicRoutines.WhenAllMethodInfo), Parameters = new WhenAllInputParameters { tasks = awaitedTasks, intents = new ExecuteRoutineIntent[awaitedTasks.Length], // The task result must be an Array, e.g. string[] itemType = itemType } }; state = new WhenAllProxyTaskState { IntentId = whenAllIntentId, ExecuteWhenAllIntent = executeWhenAllIntent }; whenAllTask.SetAsyncState(state); monitor.RegisterIntent(executeWhenAllIntent, whenAllTask); } else { executeWhenAllIntent = state.ExecuteWhenAllIntent; } } var index = -1; for (var i = 0; i < awaitedTasks.Length; i++) { if (awaitedTasks[i].AsyncState is ProxyTaskState state && state.IntentId == awaitedRoutineIntent.Id) { index = i; break; } } var parameters = (WhenAllInputParameters)executeWhenAllIntent.Parameters; parameters.intents[index] = awaitedRoutineIntent; monitor.Context.ScheduledActions.ExecuteRoutineIntents.Remove(awaitedRoutineIntent); if (parameters.intents.All(i => i != null)) { whenAllTask.SetAsyncState(new ProxyTaskState { IntentId = executeWhenAllIntent.Id }); } }
private async void RunRoutineInBackground(IServiceDefinition serviceDefinition, ExecuteRoutineIntent intent) { try { var serviceInstance = _domainServiceProvider.GetService(serviceDefinition.Implementation); var routineMethod = _routineMethodResolver.Resolve(serviceDefinition, intent.MethodId); var methodInvoker = _methodInvokerFactory.Create(routineMethod); foreach (var postAction in _transitionActions) { await postAction.OnRoutineStartAsync(serviceDefinition, intent.ServiceId, intent.MethodId, intent.Id); } Task task; try { task = methodInvoker.Invoke(serviceInstance, intent.Parameters); if (routineMethod.ReturnType != typeof(void)) { try { await task; } catch { } } } catch (Exception ex) { if (ex is TargetInvocationException) { ex = ex.InnerException; } task = Task.FromException(ex); } var taskResult = task?.ToTaskResult() ?? new TaskResult(); foreach (var postAction in _transitionActions) { await postAction.OnRoutineCompleteAsync(serviceDefinition, intent.ServiceId, intent.MethodId, intent.Id, taskResult); } _routineCompletionSink.OnRoutineCompleted(intent.Id, taskResult); } catch { } }
public async Task <HttpResponseMessage> ProcessRequestAsync( HttpRequestMessage request, FunctionExecutionContext context, DateTimeOffset requestStartTime, ILogger logger, CancellationToken ct) { _currentFunction.Value = new ExecutingFuncionInfo { FunctionName = context.FunctionName }; try { #warning Create an HTTP connector if (request.Method == HttpMethod.Post) { string serviceName = null; string routineName = null; long? intentId = null; bool @volatile = false; TimeSpan pollTime = TimeSpan.Zero; foreach (var parameter in request.GetQueryNameValuePairs()) { switch (parameter.Key) { case "service": serviceName = parameter.Value; break; case "routine": routineName = parameter.Value; break; case "intent": if (long.TryParse(parameter.Value, out var parsedIntentId)) { intentId = parsedIntentId; } break; } } foreach (var header in request.Headers) { var firstValue = header.Value.FirstOrDefault(); switch (header.Key) { case "Volatile": if (string.IsNullOrEmpty(firstValue) || !bool.TryParse(firstValue, out @volatile)) { @volatile = true; } break; case "Poll-Time": if (!string.IsNullOrEmpty(firstValue)) { if (double.TryParse(firstValue, out var pollTimeInSeconds)) { pollTime = TimeSpan.FromSeconds(pollTimeInSeconds); } else if (TimeSpan.TryParse(firstValue, out var pollTimeTimeSpan)) { pollTime = pollTimeTimeSpan; } } break; } } var serviceId = new ServiceId { ServiceName = serviceName }; var routineMethodId = new RoutineMethodId { MethodName = routineName }; var registration = _serviceRegistry.AllRegistrations .SingleOrDefault(r => r.ServiceName == serviceId.ServiceName); if (registration == null || registration.IsExternal) { return new HttpResponseMessage(HttpStatusCode.BadRequest) { Content = new StringContent( registration == null ? "Service not found" : "Cannot invoke external service") } } ; IValueContainer parameterContainer = null; if (request.Content != null) { var routineMethod = _routineMethodResolver.Resolve(registration.ServiceType, routineMethodId); var methodInvoker = _methodInvokerFactory.Create(routineMethod); parameterContainer = methodInvoker.CreateParametersContainer(); #warning change to use a serializer based on the format var paramsJson = await request.Content.ReadAsStringAsync(); if (!string.IsNullOrWhiteSpace(paramsJson)) { JsonConvert.PopulateObject(paramsJson, parameterContainer); } } var intent = new ExecuteRoutineIntent { #warning Generate intent ID from function request ID? any benefit (like on re-try)? Id = intentId ?? _idGenerator.NewId(), ServiceId = serviceId, MethodId = routineMethodId, Parameters = parameterContainer, Continuation = null }; var connector = GetConnector(serviceId); var routineInfo = await connector.ScheduleRoutineAsync(intent, ct); if (pollTime > TimeSpan.Zero) { routineInfo = await PollRoutineAsync(routineInfo, connector, requestStartTime, pollTime, ct); } if (routineInfo.Result != null) { return(CreateResponseFromRoutineResult(routineInfo.Result)); } var response = new HttpResponseMessage(HttpStatusCode.Accepted); var location = $"{request.RequestUri.AbsolutePath}?service={serviceId.ServiceName}&routineId={routineInfo.RoutineId}"; response.Headers.Add("Location", location); #warning ETag must be in certain format //if (!string.IsNullOrEmpty(routineInfo.ETag)) // response.Headers.ETag = new EntityTagHeaderValue(routineInfo.ETag); return(response); } else if (request.Method == HttpMethod.Get) { string serviceName = null; string routineId = null; string routineETag = null; TimeSpan pollTime = TimeSpan.Zero; foreach (var parameter in request.GetQueryNameValuePairs()) { switch (parameter.Key) { case "service": serviceName = parameter.Value; break; case "routineId": routineId = parameter.Value; break; case "routineTag": routineETag = parameter.Value; break; } } foreach (var header in request.Headers) { var firstValue = header.Value.FirstOrDefault(); switch (header.Key) { case "Poll-Time": if (!string.IsNullOrEmpty(firstValue)) { if (double.TryParse(firstValue, out var pollTimeInSeconds)) { pollTime = TimeSpan.FromSeconds(pollTimeInSeconds); } else if (TimeSpan.TryParse(firstValue, out var pollTimeTimeSpan)) { pollTime = pollTimeTimeSpan; } } break; } } var serviceId = new ServiceId { ServiceName = serviceName }; var routineInfo = new ActiveRoutineInfo { RoutineId = routineId, ETag = routineETag }; var connector = GetConnector(serviceId); routineInfo = await PollRoutineAsync(routineInfo, connector, requestStartTime, pollTime, ct); if (routineInfo.Result != null) { return(CreateResponseFromRoutineResult(routineInfo.Result)); } var response = new HttpResponseMessage(HttpStatusCode.NoContent); #warning ETag must be in certain format //if (!string.IsNullOrEmpty(routineInfo.ETag)) // response.Headers.ETag = new EntityTagHeaderValue(routineInfo.ETag); return(response); } } catch (Exception ex) { return(CreateInfrastructureErrorResponse(ex)); } finally { _currentFunction.Value = default; } return(new HttpResponseMessage(HttpStatusCode.NotFound)); }
public async Task <InvokeRoutineResult> InvokeAsync(ExecuteRoutineIntent intent) { var serviceRef = _serviceResolver.Resolve(intent.Service); var methodRef = _methodResolver.Resolve(serviceRef.Definition, intent.Method); var behaviorSettings = _communicationSettingsProvider.GetMethodSettings(methodRef.Definition); ICommunicator communicator = _communicatorProvider.GetCommunicator(serviceRef.Id, methodRef.Id); bool preferToRunInPlace = behaviorSettings.RunInPlace && serviceRef.Definition.Type != ServiceType.External; bool runInPlace = preferToRunInPlace && (!behaviorSettings.Persistent || communicator.Traits.HasFlag(CommunicationTraits.MessageLockOnPublish)); var invocationData = InvocationDataUtils.CreateMethodInvocationData(intent, _transitionScope.IsActive ? _transitionScope.CurrentMonitor.Context : null); var resultValueType = methodRef.Definition.MethodInfo.ReturnType; if (resultValueType != typeof(void)) { resultValueType = TaskAccessor.GetTaskResultType(resultValueType); if (resultValueType == TaskAccessor.VoidTaskResultType) { resultValueType = typeof(void); } } if (runInPlace) { IMessageHandle messageHandle = null; if (behaviorSettings.Persistent) { var preferences = new InvocationPreferences { LockMessage = true, ResultValueType = resultValueType }; var invocationResult = await communicator.InvokeAsync(invocationData, preferences); if (invocationResult.Outcome == InvocationOutcome.Complete) { return(invocationResult); } messageHandle = invocationResult.MessageHandle; } try { var result = await _localMethodRunner.RunAsync( invocationData, RuntimeCommunicatorMessage.Instance); if (messageHandle != null) { await messageHandle.Complete(); } return(result); } catch (Exception ex) // TODO: infra exceptions? should not be there, right? { if (messageHandle != null) { messageHandle.ReleaseLock(); } return(new InvokeRoutineResult { Outcome = InvocationOutcome.Scheduled, MessageHandle = messageHandle }); } } else { var preferences = new InvocationPreferences { // TODO: check this option //LockMessage = behaviorSettings.Resilient && communicator.Traits.HasFlag(CommunicationTraits.MessageLockOnPublish), Synchronous = communicator.Traits.HasFlag(CommunicationTraits.SyncReplies), ResultValueType = resultValueType }; return(await communicator.InvokeAsync(invocationData, preferences)); } }