Ejemplo n.º 1
0
        /// <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);
        }
Ejemplo n.º 2
0
        /// <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);
        }
Ejemplo n.º 3
0
        public async Task CommitAsync(ScheduledActions actions, ITransitionCarrier transitionCarrier, TransitionCommitOptions options, CancellationToken ct)
        {
            if (actions.ExecuteRoutineIntents?.Count > 0)
            {
                foreach (var intent in actions.ExecuteRoutineIntents)
                {
                    var serviceDefinition = GetServiceDefinition(intent.ServiceId);

                    if (serviceDefinition.Type == ServiceType.Local)
                    {
#pragma warning disable CS4014
                        Task.Run(() => RunRoutineInBackground(serviceDefinition, intent));
#pragma warning restore CS4014
                    }
                    else
                    {
                        var platformHttpClient = _platformHttpClientProvider.GetClient(serviceDefinition);
                        var routineInfo        = await platformHttpClient.ScheduleRoutineAsync(intent, _transitionUserContext.Current, ct);

                        if (routineInfo.Result != null)
                        {
                            _routineCompletionSink.OnRoutineCompleted(intent.Id, routineInfo.Result);
                        }
                    }
                }
            }

            if (actions.RaiseEventIntents?.Count > 0)
            {
                foreach (var intent in actions.RaiseEventIntents)
                {
                    await _eventDispatcher.PublishEvent(intent);
                }
            }
        }
Ejemplo n.º 4
0
 public async void RaiseEventInBackground(RaiseEventIntent intent)
 {
     var actions = new ScheduledActions
     {
         RaiseEventIntents = new List <RaiseEventIntent>
         {
             intent
         }
     };
     await _transitionCommitter.CommitAsync(actions, transitionCarrier : null, ct : default(CancellationToken));
 }
Ejemplo n.º 5
0
        public async void RaiseEventInBackground(RaiseEventIntent intent)
        {
            var actions = new ScheduledActions
            {
                RaiseEventIntents = new List <RaiseEventIntent>
                {
                    intent
                }
            };

            var options = new TransitionCommitOptions();

            await _transitionCommitter.CommitAsync(actions, transitionCarrier : null, options : options, ct : default);
        }
Ejemplo n.º 6
0
        public async Task CommitAsync(
            ITransitionCarrier transitionCarrier,
            ScheduledActions actions,
            CancellationToken ct)
        {
#warning This need deep thinking on how to achieve transictionality

            if (actions.SaveStateIntent != null)
            {
#warning Make sure that saving service and routine state is transactional - you don't want to re-run routine on failure after service state was saved only.
                var intent = actions.SaveStateIntent;
                await transitionCarrier.SaveStateAsync(intent, ct);
            }

            if (actions.ExecuteRoutineIntents?.Count > 0)
            {
                foreach (var intent in actions.ExecuteRoutineIntents)
                {
                    var connector = _fabricConnectorSelector.Select(intent.ServiceId);
#warning TODO: try to pre-generate routine ID - needed for transactionality.
#warning TODO: check if target fabric can route back the continuation. If not, come up with another strategy, e.g. polling, or gateway?
                    var info = await connector.ScheduleRoutineAsync(intent, ct);

#warning TODO: check if routine is already done - it's possible on retry to run the transition, or under some special circumstances.
#warning TODO: save scheduled routine info into current routine's state - needed for dynamic subscription.
                }
            }

            if (actions.ResumeRoutineIntent != null)
            {
#warning need ability to overwrite existing message instead of creating a new one (if supported)
                var intent    = actions.ResumeRoutineIntent;
                var connector = _fabricConnectorSelector.Select(intent.Continuation.ServiceId);
                var info      = await connector.ScheduleContinuationAsync(intent, ct);
            }

            if (actions.ContinuationIntents?.Count > 0)
            {
                foreach (var intent in actions.ContinuationIntents)
                {
                    var connector = _fabricConnectorSelector.Select(intent.Continuation.ServiceId);
                    var info      = await connector.ScheduleContinuationAsync(intent, ct);
                }
            }
        }
Ejemplo n.º 7
0
        public AutomatedGame(string hostname, int port, string username, string password, int realmId, int character)
        {
            this.RealmID = realmId;
            this.Character = character;
            scheduledActions = new ScheduledActions();
            updateObjectHandler = new UpdateObjectHandler(this);
            Triggers = new List<Trigger>();
            World = new GameWorld();
            Player = new Player();
            Player.OnFieldUpdated += OnFieldUpdate;
            Objects = new Dictionary<ulong, WorldObject>();

            this.Hostname = hostname;
            this.Port = port;
            this.Username = username;
            this.Password = password;

            socket = new AuthSocket(this, Hostname, Port, Username, Password);
            socket.InitHandlers();
        }
Ejemplo n.º 8
0
        public AutomatedGame(string hostname, int port, string username, string password, int realmId, int character)
        {
            this.RealmID = realmId;
            this.Character = character;
            scheduledActions = new ScheduledActions();
            updateObjectHandler = new UpdateObjectHandler(this);
            Triggers = new IteratedList<Trigger>();
            World = new GameWorld();
            Player = new Player();
            Player.OnFieldUpdated += OnFieldUpdate;
            Objects = new Dictionary<ulong, WorldObject>();
            CompletedAchievements = new HashSet<uint>();
            AchievementCriterias = new Dictionary<uint, ulong>();
            AIs = new Stack<IGameAI>();
            PushAI(new EmptyAI());

            this.Hostname = hostname;
            this.Port = port;
            this.Username = username;
            this.Password = password;

            socket = new AuthSocket(this, Hostname, Port, Username, Password);
            socket.InitHandlers();
        }
Ejemplo n.º 9
0
        public async Task CommitAsync(
            ScheduledActions actions,
            // Carrier is NULL when call is made outside of a transition scope, e.g. from entry point of a console app.
            ITransitionCarrier transitionCarrier,
            TransitionCommitOptions options,
            CancellationToken ct)
        {
#warning This need deep thinking on how to achieve consistency

            if (actions.SaveStateIntent != null)
            {
#warning Make sure that saving service and routine state is transactional - you don't want to re-run routine on failure after service state was saved only.
                var intent = actions.SaveStateIntent;
                await((ITransitionStateSaver)transitionCarrier).SaveStateAsync(intent, ct);
            }

            if (actions.ExecuteRoutineIntents?.Count > 0)
            {
                foreach (var intent in actions.ExecuteRoutineIntents)
                {
                    var connector = _fabricConnectorSelector.Select(intent.ServiceId);
#warning TODO: try to pre-generate routine ID - needed for transactionality.
#warning TODO: check if target fabric can route back the continuation. If not, come up with another strategy, e.g. polling, or gateway?
                    var info = await connector.ScheduleRoutineAsync(intent, ct);

#warning TODO: check if routine is already done - it's possible on retry to run the transition, or under some special circumstances.
#warning TODO: save scheduled routine info into current routine's state - needed for dynamic subscription.
                    if (options.NotifyOnRoutineCompletion)
                    {
                        ((IInternalRoutineCompletionNotifier)_routineCompletionNotifier).RegisterComittedRoutine(intent.Id, connector, info);
                    }
                }
            }

            if (actions.ResumeRoutineIntent != null)
            {
#warning need ability to overwrite existing message instead of creating a new one (if supported)
                var intent    = actions.ResumeRoutineIntent;
                var connector = _fabricConnectorSelector.Select(intent.Continuation.ServiceId);
                var info      = await connector.ScheduleContinuationAsync(intent, ct);
            }

            if (actions.ContinuationIntents?.Count > 0)
            {
                foreach (var intent in actions.ContinuationIntents)
                {
                    var connector = _fabricConnectorSelector.Select(intent.Continuation.ServiceId);
                    var info      = await connector.ScheduleContinuationAsync(intent, ct);
                }
            }

            if (actions.RaiseEventIntents?.Count > 0)
            {
                foreach (var intent in actions.RaiseEventIntents)
                {
                    if (intent.ServiceId == null)
                    {
                        throw new NotSupportedException();
                    }

                    var connector = _fabricConnectorSelector.Select(intent.ServiceId);
                    await connector.PublishEventAsync(intent, ct);
                }
            }

            if (actions.RegisterTriggerIntents?.Count > 0)
            {
                var connector = ((ICurrentConnectorProvider)transitionCarrier).Connector;
                foreach (var intent in actions.RegisterTriggerIntents)
                {
                    await connector.RegisterTriggerAsync(intent, ct);
                }
            }

            if (actions.ActivateTriggerIntents?.Count > 0)
            {
                var connector = ((ICurrentConnectorProvider)transitionCarrier).Connector;
                foreach (var intent in actions.ActivateTriggerIntents)
                {
                    await connector.ActivateTriggerAsync(intent, ct);
                }
            }

            if (actions.SubscribeToTriggerIntents?.Count > 0)
            {
                var connector = ((ICurrentConnectorProvider)transitionCarrier).Connector;
                foreach (var intent in actions.SubscribeToTriggerIntents)
                {
                    await connector.SubscribeToTriggerAsync(intent, ct);
                }
            }
        }
Ejemplo n.º 10
0
        public async Task CommitAsync(
            ScheduledActions actions,
            ITransitionCarrier transitionCarrier,
            TransitionCommitOptions options,
            CancellationToken ct)
        {
            ITransitionContext context = _transitionScope.CurrentMonitor.Context;
            SerializedMethodContinuationState continuationState = null;

            if (actions.SaveStateIntent != null)
            {
                var intent           = actions.SaveStateIntent;
                var serviceRef       = _serviceResolver.Resolve(context.Service);
                var methodRef        = _methodResolver.Resolve(serviceRef.Definition, context.Method);
                var behaviorSettings = _communicationSettingsProvider.GetMethodSettings(methodRef.Definition);

                IMethodStateStorage stateStorage = null;

                if (intent.RoutineState != null && intent.RoutineResult == null)
                {
                    var roamState = behaviorSettings.RoamingState;

                    if (!roamState)
                    {
                        stateStorage = _methodStateStorageProvider.GetStorage(context.Service, context.Method, returnNullIfNotFound: true);
                        if (stateStorage == null)
                        {
                            // Fallback: try to roam the state if possible
                            roamState = true;
                        }
                    }

                    if (roamState)
                    {
                        // Aggregate methods (WhenAll, WhenAny) must have a concurrency control
                        // using a persistence mechanism to aggregate multiple results.
                        roamState = methodRef.Definition.FindProperty("aggregate")?.Value != (object)true;
                    }

                    if (!roamState && stateStorage == null)
                    {
                        throw new InvalidOperationException($"Invoking method '{methodRef.Id}' on '{serviceRef.Id}' requires persistence for aggregating result.");
                    }

                    if (roamState)
                    {
                        continuationState = EncodeContinuationState(intent, transitionCarrier, context);
                    }
                    else
                    {
                        try
                        {
                            var executionState = GetMethodExecutionState(
                                actions.SaveStateIntent, transitionCarrier, context);

                            var etag = await stateStorage.WriteStateAsync(
                                actions.SaveStateIntent.Service,
                                actions.SaveStateIntent.Method,
                                executionState);

                            // NOTE: assume that ContinuationDescriptor instances refer to this instance of
                            // PersistedMethodId and the ETag gets automatically propagated to the invoke intents.
                            actions.SaveStateIntent.Method.ETag = etag;
                        }
                        catch (ETagMismatchException ex)
                        {
                            // The record in the storage has changed since the beginning of the transition.
                            // There must have been a concurrent transition.
                            // Discard current results and try again.
                            throw new ConcurrentTransitionException(ex);
                        }
                    }
                }
                else if (intent.RoutineResult != null)
                {
                    var expectsSyncReply = (transitionCarrier as TransitionCarrier)?.Message.CommunicatorTraits.HasFlag(CommunicationTraits.SyncReplies) == true;

                    // TODO: make this behavior optional if a continuation is present (no polling expected).
                    // TODO: for the event sourcing style, the result must be written by the receiver of the response.
                    var writeResult = !expectsSyncReply;

                    if (writeResult && context.Caller?.Event != null)
                    {
                        writeResult = false;
                    }

                    if (writeResult && stateStorage == null)
                    {
                        stateStorage = _methodStateStorageProvider.GetStorage(context.Service, context.Method, returnNullIfNotFound: true);
                    }

                    if (stateStorage == null)
                    {
                        // Fallback (A): if the method has a continuation, assume that no polling is expected,
                        // so there is no need to write the result into a persisted storage.
                        // This does not cover 'fire and forget' scenarios.
                        // Fallback (B): This method must be an event handler - no need
                        // to write result because nothing should poll for the result.
                        if (expectsSyncReply ||
                            transitionCarrier.GetContinuationsAsync(default).Result?.Count > 0 ||
Ejemplo n.º 11
0
        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;
            }
        }