public async Task <ActiveRoutineInfo> PollRoutineResultAsync( ActiveRoutineInfo info, CancellationToken ct) { RoutineRecord routineRecord; try { routineRecord = await _routinesTable.TryRetrieveAsync <RoutineRecord>( _serviceId.ServiceName, info.RoutineId, RoutineRecordPropertiesForPolling, ct); } catch (TableDoesNotExistException) { routineRecord = null; } if (routineRecord != null) { TaskResult routineResult = null; if (routineRecord.Status == (int)RoutineStatus.Complete && !string.IsNullOrEmpty(routineRecord.Result)) { routineResult = _serializer.Deserialize <TaskResult>(routineRecord.Result); } info = new ActiveRoutineInfo { ETag = routineRecord.ETag, RoutineId = info.RoutineId, Result = routineResult }; } return(info); }
public Task <ActiveRoutineInfo> ScheduleContinuationAsync( ContinueRoutineIntent intent, CancellationToken ct) { var transitionDescriptor = new TransitionDescriptor { Type = TransitionType.ContinueRoutine, ETag = intent.Continuation.Routine.ETag }; var message = new Message { //["IntentId"] = _serializer.Serialize(intent.Id), [nameof(TransitionDescriptor)] = _serializer.SerializeToString(transitionDescriptor), [nameof(ServiceId)] = _serializer.SerializeToString(intent.Continuation.ServiceId), [nameof(RoutineDescriptor)] = _serializer.SerializeToString(intent.Continuation.Routine), [nameof(RoutineResultDescriptor)] = _serializer.SerializeToString(intent.Result), DeliverAt = intent.Continuation.ContinueAt?.ToUniversalTime() }; _dataStore.ScheduleMessage(message); var info = new ActiveRoutineInfo { RoutineId = intent.Continuation.Routine.RoutineId }; return(Task.FromResult(info)); }
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 async Task <ActiveRoutineInfo> PollRoutineResultAsync( ActiveRoutineInfo info, CancellationToken ct) { var routineRecord = _dataStore.GetRoutineRecord(info.RoutineId); var resultData = await routineRecord.Completion.Task; var result = _serializer.Deserialize <TaskResult>(resultData); return(new ActiveRoutineInfo { RoutineId = routineRecord.Id, Result = result }); }
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)); }
private async Task <ActiveRoutineInfo> PollRoutineAsync( ActiveRoutineInfo routineInfo, IFabricConnector connector, DateTimeOffset requestStartTime, TimeSpan maxPollTime, CancellationToken ct) { var stopPollingAt = requestStartTime + maxPollTime; for (var i = 0; ; i++) { routineInfo = await connector.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); } var resumeAt = DateTimeOffset.Now + delayInterval; if (resumeAt > stopPollingAt) { delayInterval = stopPollingAt - DateTimeOffset.Now; } if (delayInterval <= TimeSpan.Zero) { break; } await Task.Delay(delayInterval); } return(routineInfo); }
public async Task <ActiveRoutineInfo> PollRoutineResultAsync( ActiveRoutineInfo info, CancellationToken ct) { TaskResult routineResult = null; if (TryReadRoutineData(_routinesDirectory, info.RoutineId, out var dataEnvelope, out var eTag)) { if (dataEnvelope.Status == RoutineStatus.Complete) { routineResult = _serializer.Deserialize <TaskResult>(dataEnvelope.Result); } } return(new ActiveRoutineInfo { RoutineId = info.RoutineId, Result = routineResult, ETag = eTag }); }
public Task <ActiveRoutineInfo> ScheduleContinuationAsync( ContinueRoutineIntent intent, CancellationToken ct) { var eventData = new RoutineEventData { ServiceId = intent.Continuation.ServiceId, Routine = intent.Continuation.Routine, Callee = intent.Callee, Result = _serializer.SerializeToString(intent.Result) }; var eventEnvelope = new RoutineEventEnvelope { CloudEventsVersion = CloudEventsEnvelope.Version, EventType = DasyncCloudEventsTypes.ContinueRoutine.Name, EventTypeVersion = DasyncCloudEventsTypes.ContinueRoutine.Version, Source = "/" + (intent.Callee?.ServiceId.ServiceName ?? ""), EventID = intent.Id.ToString(), EventTime = DateTimeOffset.Now, EventDeliveryTime = intent.Continuation.ContinueAt?.ToUniversalTime(), ETag = intent.Continuation.Routine.ETag, 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 = intent.Continuation.Routine.RoutineId }; return(Task.FromResult(info)); }
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)); }