public ExecuteRoutineAsyncResult(object apiResponse, RoutineExecution execMetric, CommonReturnValue mayAccess, Dictionary <string, string> responseHeaders) { this.ApiResponse = apiResponse; this.RoutineExecutionMetric = execMetric; this.MayAccess = mayAccess; this.ResponseHeaders = responseHeaders; }
/// <summary> /// Create a routine definition that will only run once /// </summary> public RoutineDefinition(RoutineExecution execution, Action method) : this() { IntervalInMs = 0; Execution = execution; Method = method; RunOnce = true; }
/// <summary> /// Create a routine definition. Set the interval to 0 if you want to execute it on every update/fixed update /// Set the runOnce to true if you want to run the routine only 1 time /// </summary> public RoutineDefinition(int intervalInMs, RoutineExecution execution, Action method) : this() { IntervalInMs = intervalInMs; Execution = execution; Method = method; MethodName = method.Method.Name; }
/// <summary> /// Changes the routine execution interval on the fly /// </summary> protected void ChangeRoutineExecutionInterval(RoutineExecution execution, string routineName, int newIntervalInMs) { RoutineDefinition routine; switch (execution) { case RoutineExecution.FixedUpdate: routine = FixedUpdateRoutines.FirstOrDefault(r => r.Name == routineName); break; case RoutineExecution.Update: routine = UpdateRoutines.FirstOrDefault(r => r.Name == routineName); break; case RoutineExecution.LateUpdate: routine = LateUpdateRoutines.FirstOrDefault(r => r.Name == routineName); break; default: throw new ArgumentOutOfRangeException(nameof(execution), execution, null); } if (routine != null) { routine.IntervalInMs = newIntervalInMs; } else { LunaLog.LogError($"[LMP]: Routine {execution}/{routineName} not defined"); } }
//(object/*ApiResponse | IActionResult*/, RoutineExecution, CommonReturnValue) public static async Task <ExecuteRoutineAsyncResult> ExecuteRoutineAsync(ExecOptions execOptions, Dictionary <string, string> requestHeaders, string referer, string remoteIpAddress, string appTitle, string appVersion) { string debugInfo = null; Project project = null; Application app = null; Endpoint endpoint = null; Dictionary <string, string> responseHeaders = null; List <ExecutionPlugin> pluginList = null; RoutineExecution routineExecutionMetric = null; responseHeaders = new Dictionary <string, string>(); try { if (!ControllerHelper.GetProjectAndAppAndEndpoint(execOptions.project, execOptions.application, execOptions.endpoint, out project, out app, out endpoint, out var resp)) { return(new ExecuteRoutineAsyncResult(resp, null, null, responseHeaders)); } routineExecutionMetric = new RoutineExecution(endpoint, execOptions.schema, execOptions.routine); RealtimeTrackerThread.Instance.Enqueue(routineExecutionMetric); // debugInfo += $"[{execOptions.schema}].[{execOptions.routine}]"; string jsDALApiKey = null; if (requestHeaders.ContainsKey("api-key")) { jsDALApiKey = requestHeaders["api-key"]; } // make sure the source domain/IP is allowed access var mayAccess = app.MayAccessDbSource(referer, jsDALApiKey); if (!mayAccess.IsSuccess) { return(new ExecuteRoutineAsyncResult(null, null, mayAccess, responseHeaders)); } Dictionary <string, dynamic> outputParameters; int commandTimeOutInSeconds = 60; // PLUGINS var pluginsInitMetric = routineExecutionMetric.BeginChildStage("Init plugins"); pluginList = InitPlugins(app, execOptions.inputParameters, requestHeaders); pluginsInitMetric.End(); //////////////////// // Auth stage /////////////////// { // ask all ExecPlugins to authenticate foreach (var plugin in pluginList) { if (!plugin.IsAuthenticated(execOptions.schema, execOptions.routine, out var error)) { responseHeaders.Add("Plugin-AuthFailed", plugin.Name); return(new ExecuteRoutineAsyncResult(new UnauthorizedObjectResult(error), null, null, responseHeaders)); } } } var execRoutineQueryMetric = routineExecutionMetric.BeginChildStage("execRoutineQuery"); string dataCollectorEntryShortId = DataCollectorThread.Enqueue(endpoint, execOptions); /////////////////// // Database call /////////////////// OrmDAL.ExecutionResult executionResult = null; try { executionResult = await OrmDAL.ExecRoutineQueryAsync( execOptions.CancellationToken, execOptions.type, execOptions.schema, execOptions.routine, endpoint, execOptions.inputParameters, requestHeaders, remoteIpAddress, pluginList, commandTimeOutInSeconds, execRoutineQueryMetric, responseHeaders ); outputParameters = executionResult.OutputParameterDictionary; responseHeaders = executionResult.ResponseHeaders; execRoutineQueryMetric.End(); ulong?rows = null; if (executionResult?.RowsAffected.HasValue ?? false) { rows = (ulong)executionResult.RowsAffected.Value; } DataCollectorThread.End(dataCollectorEntryShortId, rowsAffected: rows, durationInMS: execRoutineQueryMetric.DurationInMS, bytesReceived: executionResult.BytesReceived, networkServerTimeMS: executionResult.NetworkServerTimeInMS); } catch (Exception execEx) { DataCollectorThread.End(dataCollectorEntryShortId, ex: execEx); // if (execOptions != null && endpoint != null) // { // var wrapEx = new Exception($"Failed to execute {endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}", execEx); // // create a fake frame to include the exec detail - this way any logger logging the StackTrace will always include the relevant execution detail // StackFrame sf = new($"{execOptions.type} {endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}", 0); // StackTrace st = new(sf); // //?wrapEx.SetStackTrace(st); // var allFields = wrapEx.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance); // var fffff =string.Join("\r\n", allFields.Select(f=>f.Name).ToArray()); // var fi = wrapEx.GetType().GetField("_stackTraceString", BindingFlags.NonPublic | BindingFlags.Instance); // fi.SetValue(wrapEx, $"{endpoint?.Pedigree}/{execOptions?.schema}/{execOptions?.routine}"); // throw wrapEx; // } // else throw; throw; // rethrow } var prepareResultsMetric = routineExecutionMetric.BeginChildStage("Prepare results"); if (!string.IsNullOrEmpty(executionResult.userError)) { return(new ExecuteRoutineAsyncResult(ApiResponse.ExclamationModal(executionResult.userError), routineExecutionMetric, mayAccess, responseHeaders)); } var retVal = (IDictionary <string, object>) new System.Dynamic.ExpandoObject(); var ret = ApiResponse.Payload(retVal); retVal.Add("OutputParms", outputParameters); if (outputParameters != null) { // TODO: Consider making this a plugin var possibleUEParmNames = (new string[] { "usererrormsg", "usrerrmsg", "usererrormessage", "usererror", "usererrmsg" }).ToList(); var ueKey = outputParameters.Keys.FirstOrDefault(k => possibleUEParmNames.Contains(k.ToLower())); // if a user error msg is defined. if (!string.IsNullOrWhiteSpace(ueKey) && !string.IsNullOrWhiteSpace(outputParameters[ueKey])) { ret.Message = outputParameters[ueKey]; ret.Title = "Action failed"; ret.Type = ApiResponseType.ExclamationModal; } } if (execOptions.type == ExecType.Query) { if (executionResult.ReaderResults != null) { var keys = executionResult.ReaderResults.Keys.ToList(); for (var i = 0; i < keys.Count; i++) { retVal.Add(keys[i], executionResult.ReaderResults[keys[i]]); } retVal.Add("HasResultSets", keys.Count > 0); retVal.Add("ResultSetKeys", keys.ToArray()); } else { var dataSet = executionResult.DataSet; var dataContainers = dataSet.ToJsonDS(); var keys = dataContainers.Keys.ToList(); for (var i = 0; i < keys.Count; i++) { retVal.Add(keys[i], dataContainers[keys[i]]); } retVal.Add("HasResultSets", keys.Count > 0); retVal.Add("ResultSetKeys", keys.ToArray()); } } else if (execOptions.type == ExecType.NonQuery) { // nothing to do } else if (execOptions.type == ExecType.Scalar) { if (executionResult.ScalarValue is DateTime) { var dt = (DateTime)executionResult.ScalarValue; // convert to Javascript Date ticks var ticks = dt.ToUniversalTime().Subtract(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; ret = ApiResponseScalar.Payload(ticks, true); } else { ret = ApiResponse.Payload(executionResult.ScalarValue); } } prepareResultsMetric.End(); routineExecutionMetric.End(executionResult.RowsAffected ?? 0); // enqueue a second time as we now have an End date and rowsAffected RealtimeTrackerThread.Instance.Enqueue(routineExecutionMetric); return(new ExecuteRoutineAsyncResult(ret, routineExecutionMetric, mayAccess, responseHeaders)); } catch (SqlException ex) when(execOptions.CancellationToken.IsCancellationRequested && ex.Number == 0 && ex.State == 0 && ex.Class == 11) { // if we ended up here with a SqlException and a Cancel has been requested, we are very likely here because of the exception "Operation cancelled by user." // since MS does not provide an easy way (like a specific error code) to detect this scenario we have to guess routineExecutionMetric?.Exception(ex); throw new OperationCancelledByUserException(ex); } catch (Exception ex) { routineExecutionMetric?.Exception(ex); Connection dbConn = null; if (endpoint != null) { // TODO: Fix! dbConn = endpoint.GetSqlConnection(); // if (debugInfo == null) debugInfo = ""; // if (dbConn != null) // { // debugInfo = $"{ endpoint.Pedigree } - { dbConn.InitialCatalog } - { debugInfo }"; // } // else // { // debugInfo = $"{ endpoint.Pedigree } - (no connection) - { debugInfo }"; // } } var exceptionResponse = ApiResponse.ExecException(ex, execOptions, out var exceptionId, debugInfo, appTitle, appVersion); if (debugInfo == null) { debugInfo = ""; } debugInfo = exceptionId + " " + debugInfo; // TODO: Get Execution plugin list specifically if (pluginList != null) { string externalRef; if (dbConn != null) { using (var con = new SqlConnection(dbConn.ConnectionStringDecrypted)) { try { string additionalInfo = debugInfo; if (execOptions?.inputParameters != null) { additionalInfo += " "; additionalInfo += string.Join(",", execOptions.inputParameters.Select(kv => $"{kv.Key}={kv.Value}").ToArray()); } con.Open(); ProcessPluginExecutionExceptionHandlers(pluginList, con, ex, additionalInfo, appTitle, appVersion, out externalRef); ((dynamic)exceptionResponse.Data).ExternalRef = externalRef; } catch (Exception e) { ExceptionLogger.LogException(e, "ProcessPluginExecutionExceptionHandlers", "jsdal-server"); } } } // else: TODO: Log fact that we dont have a proper connection string.. or should plugins handle that? } // return it as "200 (Ok)" because the exception has been handled return(new ExecuteRoutineAsyncResult(exceptionResponse, routineExecutionMetric, null, responseHeaders)); } }
public RealtimeInfo(RoutineExecution re) { this.createdEpoch = DateTime.Now.ToEpochMS(); this.RoutineExecution = re; }