tasker-engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +443 -0
- data/Rakefile +10 -0
- data/app/controllers/tasker/analytics_controller.rb +179 -0
- data/app/controllers/tasker/application_controller.rb +45 -0
- data/app/controllers/tasker/graphql_controller.rb +193 -0
- data/app/controllers/tasker/handlers_controller.rb +217 -0
- data/app/controllers/tasker/health_controller.rb +229 -0
- data/app/controllers/tasker/metrics_controller.rb +111 -0
- data/app/controllers/tasker/page_sort.rb +97 -0
- data/app/controllers/tasker/task_diagrams_controller.rb +30 -0
- data/app/controllers/tasker/tasks_controller.rb +123 -0
- data/app/controllers/tasker/workflow_steps_controller.rb +69 -0
- data/app/graphql/examples/all_tasks.graphql +22 -0
- data/app/graphql/examples/pending_tasks.graphql +23 -0
- data/app/graphql/tasker/graph_ql_types/annotation_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_argument.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_connection.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/base_edge.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_enum.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_field.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_input_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_interface.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/base_object.rb +10 -0
- data/app/graphql/tasker/graph_ql_types/base_scalar.rb +9 -0
- data/app/graphql/tasker/graph_ql_types/base_union.rb +11 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_object_map_type.rb +18 -0
- data/app/graphql/tasker/graph_ql_types/dependent_system_type.rb +13 -0
- data/app/graphql/tasker/graph_ql_types/mutation_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_step_type.rb +16 -0
- data/app/graphql/tasker/graph_ql_types/named_task_type.rb +14 -0
- data/app/graphql/tasker/graph_ql_types/named_tasks_named_step_type.rb +19 -0
- data/app/graphql/tasker/graph_ql_types/node_type.rb +12 -0
- data/app/graphql/tasker/graph_ql_types/query_type.rb +20 -0
- data/app/graphql/tasker/graph_ql_types/task_annotation_type.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_interface.rb +17 -0
- data/app/graphql/tasker/graph_ql_types/task_type.rb +26 -0
- data/app/graphql/tasker/graph_ql_types/workflow_step_type.rb +154 -0
- data/app/graphql/tasker/graph_ql_types.rb +42 -0
- data/app/graphql/tasker/mutations/base_mutation.rb +13 -0
- data/app/graphql/tasker/mutations/cancel_step.rb +29 -0
- data/app/graphql/tasker/mutations/cancel_task.rb +29 -0
- data/app/graphql/tasker/mutations/create_task.rb +52 -0
- data/app/graphql/tasker/mutations/update_step.rb +36 -0
- data/app/graphql/tasker/mutations/update_task.rb +41 -0
- data/app/graphql/tasker/queries/all_annotation_types.rb +17 -0
- data/app/graphql/tasker/queries/all_tasks.rb +23 -0
- data/app/graphql/tasker/queries/base_query.rb +9 -0
- data/app/graphql/tasker/queries/helpers.rb +16 -0
- data/app/graphql/tasker/queries/one_step.rb +24 -0
- data/app/graphql/tasker/queries/one_task.rb +18 -0
- data/app/graphql/tasker/queries/tasks_by_annotation.rb +31 -0
- data/app/graphql/tasker/queries/tasks_by_status.rb +30 -0
- data/app/graphql/tasker/tasker_rails_schema.rb +52 -0
- data/app/jobs/tasker/application_job.rb +8 -0
- data/app/jobs/tasker/metrics_export_job.rb +252 -0
- data/app/jobs/tasker/task_runner_job.rb +224 -0
- data/app/models/tasker/annotation_type.rb +26 -0
- data/app/models/tasker/application_record.rb +70 -0
- data/app/models/tasker/dependent_system.rb +26 -0
- data/app/models/tasker/dependent_system_object_map.rb +64 -0
- data/app/models/tasker/diagram/edge.rb +106 -0
- data/app/models/tasker/diagram/flowchart.rb +137 -0
- data/app/models/tasker/diagram/node.rb +99 -0
- data/app/models/tasker/named_step.rb +41 -0
- data/app/models/tasker/named_task.rb +121 -0
- data/app/models/tasker/named_tasks_named_step.rb +82 -0
- data/app/models/tasker/step_dag_relationship.rb +65 -0
- data/app/models/tasker/step_readiness_status.rb +59 -0
- data/app/models/tasker/task.rb +424 -0
- data/app/models/tasker/task_annotation.rb +36 -0
- data/app/models/tasker/task_diagram.rb +332 -0
- data/app/models/tasker/task_execution_context.rb +29 -0
- data/app/models/tasker/task_namespace.rb +41 -0
- data/app/models/tasker/task_transition.rb +235 -0
- data/app/models/tasker/workflow_step.rb +461 -0
- data/app/models/tasker/workflow_step_edge.rb +94 -0
- data/app/models/tasker/workflow_step_transition.rb +434 -0
- data/app/serializers/tasker/annotation_type_serializer.rb +8 -0
- data/app/serializers/tasker/handler_serializer.rb +109 -0
- data/app/serializers/tasker/task_annotation_serializer.rb +32 -0
- data/app/serializers/tasker/task_serializer.rb +168 -0
- data/app/serializers/tasker/workflow_step_serializer.rb +27 -0
- data/app/services/tasker/analytics_service.rb +409 -0
- data/app/views/tasker/task/_diagram.html.erb +32 -0
- data/config/initializers/dry_struct.rb +11 -0
- data/config/initializers/statesman.rb +6 -0
- data/config/initializers/tasker_orchestration.rb +17 -0
- data/config/initializers/time_formats.rb +4 -0
- data/config/routes.rb +34 -0
- data/config/tasker/subscriptions/example_integrations.yml +67 -0
- data/config/tasker/system_events.yml +305 -0
- data/db/functions/calculate_dependency_levels_v01.sql +45 -0
- data/db/functions/get_analytics_metrics_v01.sql +137 -0
- data/db/functions/get_slowest_steps_v01.sql +82 -0
- data/db/functions/get_slowest_tasks_v01.sql +96 -0
- data/db/functions/get_step_readiness_status_batch_v01.sql +140 -0
- data/db/functions/get_step_readiness_status_v01.sql +139 -0
- data/db/functions/get_system_health_counts_v01.sql +108 -0
- data/db/functions/get_task_execution_context_v01.sql +108 -0
- data/db/functions/get_task_execution_contexts_batch_v01.sql +104 -0
- data/db/init/schema.sql +2277 -0
- data/db/migrate/20250701165431_initial_tasker_schema.rb +116 -0
- data/db/views/tasker_step_dag_relationships_v01.sql +69 -0
- data/docs/APPLICATION_GENERATOR.md +384 -0
- data/docs/AUTH.md +1780 -0
- data/docs/CIRCUIT_BREAKER.md +224 -0
- data/docs/DEVELOPER_GUIDE.md +2665 -0
- data/docs/EVENT_SYSTEM.md +637 -0
- data/docs/EXECUTION_CONFIGURATION.md +341 -0
- data/docs/FLOW_CHART.md +149 -0
- data/docs/HEALTH.md +542 -0
- data/docs/METRICS.md +731 -0
- data/docs/OPTIMIZATION_PLAN.md +1479 -0
- data/docs/OVERVIEW.md +552 -0
- data/docs/QUICK_START.md +270 -0
- data/docs/REGISTRY_SYSTEMS.md +373 -0
- data/docs/REST_API.md +632 -0
- data/docs/ROADMAP.md +221 -0
- data/docs/SQL_FUNCTIONS.md +1408 -0
- data/docs/TASK_DIAGRAM.md +252 -0
- data/docs/TASK_EXECUTION_CONTROL_FLOW.md +237 -0
- data/docs/TELEMETRY.md +795 -0
- data/docs/TROUBLESHOOTING.md +756 -0
- data/docs/TaskHandlerGenerator.html +255 -0
- data/docs/Tasker/Analysis/RuntimeGraphAnalyzer.html +907 -0
- data/docs/Tasker/Analysis/TemplateGraphAnalyzer.html +1236 -0
- data/docs/Tasker/Analysis.html +117 -0
- data/docs/Tasker/AnalyticsController.html +450 -0
- data/docs/Tasker/AnalyticsService/BottleneckAnalytics.html +816 -0
- data/docs/Tasker/AnalyticsService/PerformanceAnalytics.html +586 -0
- data/docs/Tasker/AnalyticsService.html +2221 -0
- data/docs/Tasker/AnnotationType.html +137 -0
- data/docs/Tasker/AnnotationTypeSerializer.html +124 -0
- data/docs/Tasker/ApplicationController.html +147 -0
- data/docs/Tasker/ApplicationJob.html +128 -0
- data/docs/Tasker/ApplicationRecord.html +378 -0
- data/docs/Tasker/Authentication/AuthenticationError.html +124 -0
- data/docs/Tasker/Authentication/ConfigurationError.html +124 -0
- data/docs/Tasker/Authentication/Coordinator.html +242 -0
- data/docs/Tasker/Authentication/Interface.html +560 -0
- data/docs/Tasker/Authentication/InterfaceError.html +124 -0
- data/docs/Tasker/Authentication/NoneAuthenticator.html +338 -0
- data/docs/Tasker/Authentication.html +119 -0
- data/docs/Tasker/Authorization/AuthorizationError.html +139 -0
- data/docs/Tasker/Authorization/BaseCoordinator.html +927 -0
- data/docs/Tasker/Authorization/ConfigurationError.html +153 -0
- data/docs/Tasker/Authorization/ResourceConstants/ACTIONS.html +428 -0
- data/docs/Tasker/Authorization/ResourceConstants/RESOURCES.html +365 -0
- data/docs/Tasker/Authorization/ResourceConstants.html +146 -0
- data/docs/Tasker/Authorization/ResourceRegistry.html +882 -0
- data/docs/Tasker/Authorization/UnauthorizedError.html +153 -0
- data/docs/Tasker/Authorization.html +582 -0
- data/docs/Tasker/CacheCapabilities.html +167 -0
- data/docs/Tasker/CacheStrategy.html +1297 -0
- data/docs/Tasker/Concerns/Authenticatable.html +116 -0
- data/docs/Tasker/Concerns/Authorizable/AdminStatusChecker.html +256 -0
- data/docs/Tasker/Concerns/Authorizable.html +816 -0
- data/docs/Tasker/Concerns/ControllerAuthorizable.html +157 -0
- data/docs/Tasker/Concerns/EventPublisher.html +4023 -0
- data/docs/Tasker/Concerns/IdempotentStateTransitions.html +806 -0
- data/docs/Tasker/Concerns/LifecycleEventHelpers.html +129 -0
- data/docs/Tasker/Concerns/OrchestrationPublisher.html +129 -0
- data/docs/Tasker/Concerns/StateMachineBase/ClassMethods.html +1075 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase/ClassMethods.html +191 -0
- data/docs/Tasker/Concerns/StateMachineBase/StateMachineBase.html +126 -0
- data/docs/Tasker/Concerns/StateMachineBase.html +153 -0
- data/docs/Tasker/Concerns/StructuredLogging.html +1413 -0
- data/docs/Tasker/Concerns.html +117 -0
- data/docs/Tasker/Configuration/AuthConfiguration.html +1023 -0
- data/docs/Tasker/Configuration/ConfigurationProxy.html +581 -0
- data/docs/Tasker/Configuration/DatabaseConfiguration.html +475 -0
- data/docs/Tasker/Configuration/EngineConfiguration.html +1265 -0
- data/docs/Tasker/Configuration/HealthConfiguration.html +791 -0
- data/docs/Tasker/Configuration/TelemetryConfiguration.html +1308 -0
- data/docs/Tasker/Configuration/TelemetryConfigurationProxy.html +388 -0
- data/docs/Tasker/Configuration.html +1669 -0
- data/docs/Tasker/ConfigurationError.html +143 -0
- data/docs/Tasker/ConfiguredTask.html +514 -0
- data/docs/Tasker/Constants/EventDefinitions.html +590 -0
- data/docs/Tasker/Constants/LifecycleEvents.html +137 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Step.html +152 -0
- data/docs/Tasker/Constants/ObservabilityEvents/Task.html +142 -0
- data/docs/Tasker/Constants/ObservabilityEvents.html +126 -0
- data/docs/Tasker/Constants/RegistryEvents.html +285 -0
- data/docs/Tasker/Constants/StepEvents.html +177 -0
- data/docs/Tasker/Constants/TaskEvents.html +167 -0
- data/docs/Tasker/Constants/TaskExecution/ExecutionStatus.html +207 -0
- data/docs/Tasker/Constants/TaskExecution/HealthStatus.html +191 -0
- data/docs/Tasker/Constants/TaskExecution/RecommendedAction.html +207 -0
- data/docs/Tasker/Constants/TaskExecution.html +126 -0
- data/docs/Tasker/Constants/TaskFinalization/ErrorMessages.html +132 -0
- data/docs/Tasker/Constants/TaskFinalization/PendingReasons.html +207 -0
- data/docs/Tasker/Constants/TaskFinalization/ReenqueueReasons.html +239 -0
- data/docs/Tasker/Constants/TaskFinalization.html +126 -0
- data/docs/Tasker/Constants/TaskStatuses.html +223 -0
- data/docs/Tasker/Constants/TestEvents.html +163 -0
- data/docs/Tasker/Constants/WorkflowEvents.html +222 -0
- data/docs/Tasker/Constants/WorkflowStepStatuses.html +223 -0
- data/docs/Tasker/Constants.html +561 -0
- data/docs/Tasker/DependentSystem.html +137 -0
- data/docs/Tasker/DependentSystemObjectMap.html +250 -0
- data/docs/Tasker/DetectorRegistry.html +598 -0
- data/docs/Tasker/Diagram/Edge.html +1191 -0
- data/docs/Tasker/Diagram/Flowchart.html +1539 -0
- data/docs/Tasker/Diagram/Node.html +1165 -0
- data/docs/Tasker/Diagram.html +117 -0
- data/docs/Tasker/Engine.html +215 -0
- data/docs/Tasker/Error.html +139 -0
- data/docs/Tasker/Events/Bus.html +1226 -0
- data/docs/Tasker/Events/Catalog/CatalogPrinter.html +258 -0
- data/docs/Tasker/Events/Catalog/CustomEventRegistrar.html +276 -0
- data/docs/Tasker/Events/Catalog/ExamplePayloadGenerator.html +294 -0
- data/docs/Tasker/Events/Catalog.html +1291 -0
- data/docs/Tasker/Events/CustomRegistry.html +943 -0
- data/docs/Tasker/Events/DefinitionLoader.html +575 -0
- data/docs/Tasker/Events/EventPayloadBuilder/ErrorInfoExtractor.html +286 -0
- data/docs/Tasker/Events/EventPayloadBuilder/StepPayloadBuilder.html +312 -0
- data/docs/Tasker/Events/EventPayloadBuilder.html +664 -0
- data/docs/Tasker/Events/Publisher.html +365 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer/ErrorTypeClassifier.html +1128 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/ErrorCategorizer.html +270 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber/MetricTagsExtractor.html +266 -0
- data/docs/Tasker/Events/Subscribers/BaseSubscriber.html +2556 -0
- data/docs/Tasker/Events/Subscribers/MetricsSubscriber.html +723 -0
- data/docs/Tasker/Events/Subscribers/TelemetrySubscriber.html +2251 -0
- data/docs/Tasker/Events/Subscribers.html +117 -0
- data/docs/Tasker/Events/SubscriptionLoader.html +493 -0
- data/docs/Tasker/Events.html +294 -0
- data/docs/Tasker/EventsGenerator.html +459 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics/AnalyticsMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedAnalyticsMetrics.html +412 -0
- data/docs/Tasker/Functions/FunctionBasedDependencyLevels.html +598 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps/SlowestStep.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestSteps.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks/SlowestTask.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSlowestTasks.html +453 -0
- data/docs/Tasker/Functions/FunctionBasedStepReadinessStatus.html +1457 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts/HealthMetrics.html +135 -0
- data/docs/Tasker/Functions/FunctionBasedSystemHealthCounts.html +370 -0
- data/docs/Tasker/Functions/FunctionBasedTaskExecutionContext.html +1250 -0
- data/docs/Tasker/Functions/FunctionWrapper.html +479 -0
- data/docs/Tasker/Functions.html +117 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator/UsageInstructionsFormatter.html +244 -0
- data/docs/Tasker/Generators/AuthenticatorGenerator.html +373 -0
- data/docs/Tasker/Generators/AuthorizationCoordinatorGenerator.html +430 -0
- data/docs/Tasker/Generators/SubscriberGenerator.html +377 -0
- data/docs/Tasker/Generators/TaskHandlerGenerator.html +263 -0
- data/docs/Tasker/Generators.html +117 -0
- data/docs/Tasker/GraphQLTypes/AnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/BaseArgument.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseConnection.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseEdge.html +130 -0
- data/docs/Tasker/GraphQLTypes/BaseEnum.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseField.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInputObject.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseInterface.html +116 -0
- data/docs/Tasker/GraphQLTypes/BaseObject.html +128 -0
- data/docs/Tasker/GraphQLTypes/BaseScalar.html +124 -0
- data/docs/Tasker/GraphQLTypes/BaseUnion.html +124 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemObjectMapType.html +132 -0
- data/docs/Tasker/GraphQLTypes/DependentSystemType.html +132 -0
- data/docs/Tasker/GraphQLTypes/MutationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTaskType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NamedTasksNamedStepType.html +132 -0
- data/docs/Tasker/GraphQLTypes/NodeType.html +118 -0
- data/docs/Tasker/GraphQLTypes/QueryType.html +139 -0
- data/docs/Tasker/GraphQLTypes/TaskAnnotationType.html +132 -0
- data/docs/Tasker/GraphQLTypes/TaskInterface.html +111 -0
- data/docs/Tasker/GraphQLTypes/TaskType.html +201 -0
- data/docs/Tasker/GraphQLTypes/WorkflowStepType.html +694 -0
- data/docs/Tasker/GraphQLTypes.html +130 -0
- data/docs/Tasker/GraphqlController.html +251 -0
- data/docs/Tasker/HandlerFactory.html +1518 -0
- data/docs/Tasker/HandlerSerializer.html +682 -0
- data/docs/Tasker/HandlersController.html +574 -0
- data/docs/Tasker/HashIdentityStrategy.html +278 -0
- data/docs/Tasker/Health/ReadinessChecker.html +712 -0
- data/docs/Tasker/Health/StatusChecker.html +653 -0
- data/docs/Tasker/Health.html +117 -0
- data/docs/Tasker/HealthController.html +523 -0
- data/docs/Tasker/IdentityStrategy.html +276 -0
- data/docs/Tasker/InvalidTaskHandlerConfig.html +135 -0
- data/docs/Tasker/LifecycleEvents/Events/Step.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events/Task.html +162 -0
- data/docs/Tasker/LifecycleEvents/Events.html +204 -0
- data/docs/Tasker/LifecycleEvents/Publisher.html +132 -0
- data/docs/Tasker/LifecycleEvents.html +799 -0
- data/docs/Tasker/Logging/CorrelationIdGenerator.html +688 -0
- data/docs/Tasker/Logging.html +115 -0
- data/docs/Tasker/MetricsController.html +293 -0
- data/docs/Tasker/MetricsExportJob.html +414 -0
- data/docs/Tasker/Mutations/BaseMutation.html +128 -0
- data/docs/Tasker/Mutations/CancelStep.html +219 -0
- data/docs/Tasker/Mutations/CancelTask.html +221 -0
- data/docs/Tasker/Mutations/CreateTask.html +243 -0
- data/docs/Tasker/Mutations/UpdateStep.html +243 -0
- data/docs/Tasker/Mutations/UpdateTask.html +243 -0
- data/docs/Tasker/Mutations.html +117 -0
- data/docs/Tasker/NamedStep.html +216 -0
- data/docs/Tasker/NamedTask.html +910 -0
- data/docs/Tasker/NamedTasksNamedStep.html +435 -0
- data/docs/Tasker/Orchestration/BackoffCalculator.html +404 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder/ConfigValidator.html +258 -0
- data/docs/Tasker/Orchestration/ConnectionBuilder.html +435 -0
- data/docs/Tasker/Orchestration/ConnectionPoolIntelligence.html +513 -0
- data/docs/Tasker/Orchestration/Coordinator.html +641 -0
- data/docs/Tasker/Orchestration/FutureStateAnalyzer.html +1045 -0
- data/docs/Tasker/Orchestration/Orchestrator.html +679 -0
- data/docs/Tasker/Orchestration/PluginIntegration.html +1127 -0
- data/docs/Tasker/Orchestration/ResponseProcessor.html +504 -0
- data/docs/Tasker/Orchestration/RetryHeaderParser.html +304 -0
- data/docs/Tasker/Orchestration/StepExecutor.html +995 -0
- data/docs/Tasker/Orchestration/StepSequenceFactory.html +644 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/BlockageChecker.html +264 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ContextManager.html +254 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/DelayCalculator.html +556 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationDecisionMaker.html +348 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/FinalizationProcessor.html +286 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReasonDeterminer.html +432 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/ReenqueueManager.html +296 -0
- data/docs/Tasker/Orchestration/TaskFinalizer/UnclearStateHandler.html +314 -0
- data/docs/Tasker/Orchestration/TaskFinalizer.html +1212 -0
- data/docs/Tasker/Orchestration/TaskInitializer.html +766 -0
- data/docs/Tasker/Orchestration/TaskReenqueuer.html +506 -0
- data/docs/Tasker/Orchestration/ViableStepDiscovery.html +442 -0
- data/docs/Tasker/Orchestration/WorkflowCoordinator.html +510 -0
- data/docs/Tasker/Orchestration.html +130 -0
- data/docs/Tasker/PageSort/PageSortParamsBuilder.html +296 -0
- data/docs/Tasker/PageSort.html +247 -0
- data/docs/Tasker/PermanentError.html +518 -0
- data/docs/Tasker/ProceduralError.html +147 -0
- data/docs/Tasker/Queries/AllAnnotationTypes.html +217 -0
- data/docs/Tasker/Queries/AllTasks.html +221 -0
- data/docs/Tasker/Queries/BaseQuery.html +128 -0
- data/docs/Tasker/Queries/Helpers.html +187 -0
- data/docs/Tasker/Queries/OneStep.html +225 -0
- data/docs/Tasker/Queries/OneTask.html +217 -0
- data/docs/Tasker/Queries/TasksByAnnotation.html +231 -0
- data/docs/Tasker/Queries/TasksByStatus.html +233 -0
- data/docs/Tasker/Queries.html +119 -0
- data/docs/Tasker/Railtie.html +124 -0
- data/docs/Tasker/Registry/BaseRegistry.html +1690 -0
- data/docs/Tasker/Registry/EventPublisher.html +667 -0
- data/docs/Tasker/Registry/InterfaceValidator.html +569 -0
- data/docs/Tasker/Registry/RegistrationError.html +132 -0
- data/docs/Tasker/Registry/RegistryError.html +139 -0
- data/docs/Tasker/Registry/StatisticsCollector.html +841 -0
- data/docs/Tasker/Registry/SubscriberRegistry.html +1504 -0
- data/docs/Tasker/Registry/ValidationError.html +132 -0
- data/docs/Tasker/Registry.html +119 -0
- data/docs/Tasker/RetryableError.html +515 -0
- data/docs/Tasker/StateMachine/Compatibility.html +282 -0
- data/docs/Tasker/StateMachine/InvalidStateTransition.html +135 -0
- data/docs/Tasker/StateMachine/StepStateMachine/StandardizedPayloadBuilder.html +260 -0
- data/docs/Tasker/StateMachine/StepStateMachine.html +2215 -0
- data/docs/Tasker/StateMachine/TaskStateMachine.html +734 -0
- data/docs/Tasker/StateMachine.html +602 -0
- data/docs/Tasker/StepDagRelationship.html +657 -0
- data/docs/Tasker/StepHandler/Api/Config.html +1091 -0
- data/docs/Tasker/StepHandler/Api.html +884 -0
- data/docs/Tasker/StepHandler/AutomaticEventPublishing.html +321 -0
- data/docs/Tasker/StepHandler/Base.html +970 -0
- data/docs/Tasker/StepHandler.html +119 -0
- data/docs/Tasker/StepReadinessStatus.html +836 -0
- data/docs/Tasker/Task.html +2575 -0
- data/docs/Tasker/TaskAnnotation.html +137 -0
- data/docs/Tasker/TaskAnnotationSerializer.html +124 -0
- data/docs/Tasker/TaskBuilder/StepNameValidator.html +264 -0
- data/docs/Tasker/TaskBuilder/StepTemplateDefiner.html +264 -0
- data/docs/Tasker/TaskBuilder.html +764 -0
- data/docs/Tasker/TaskDiagram/StepToStepEdgeBuilder.html +260 -0
- data/docs/Tasker/TaskDiagram/TaskToRootStepEdgeBuilder.html +290 -0
- data/docs/Tasker/TaskDiagram.html +548 -0
- data/docs/Tasker/TaskDiagramsController.html +240 -0
- data/docs/Tasker/TaskExecutionContext.html +469 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/ClassBasedEventRegistrar.html +238 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner/YamlEventRegistrar.html +254 -0
- data/docs/Tasker/TaskHandler/ClassMethods/StepTemplateDefiner.html +988 -0
- data/docs/Tasker/TaskHandler/ClassMethods.html +357 -0
- data/docs/Tasker/TaskHandler/InstanceMethods.html +1396 -0
- data/docs/Tasker/TaskHandler/StepGroup.html +1748 -0
- data/docs/Tasker/TaskHandler.html +271 -0
- data/docs/Tasker/TaskNamespace.html +312 -0
- data/docs/Tasker/TaskRunnerJob.html +406 -0
- data/docs/Tasker/TaskSerializer.html +474 -0
- data/docs/Tasker/TaskTransition.html +1517 -0
- data/docs/Tasker/TaskWorkflowSummary.html +988 -0
- data/docs/Tasker/TaskerRailsSchema/InvalidObjectTypeError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema/TypeResolutionError.html +139 -0
- data/docs/Tasker/TaskerRailsSchema/UnknownInterfaceError.html +132 -0
- data/docs/Tasker/TaskerRailsSchema.html +384 -0
- data/docs/Tasker/TasksController.html +595 -0
- data/docs/Tasker/Telemetry/EventMapping.html +1307 -0
- data/docs/Tasker/Telemetry/EventRouter.html +2178 -0
- data/docs/Tasker/Telemetry/Events/ExportEvents.html +246 -0
- data/docs/Tasker/Telemetry/Events.html +115 -0
- data/docs/Tasker/Telemetry/ExportCoordinator/DistributedLockTimeoutError.html +135 -0
- data/docs/Tasker/Telemetry/ExportCoordinator.html +2137 -0
- data/docs/Tasker/Telemetry/IntelligentCacheManager.html +1083 -0
- data/docs/Tasker/Telemetry/LogBackend.html +1088 -0
- data/docs/Tasker/Telemetry/MetricTypes/Counter.html +1054 -0
- data/docs/Tasker/Telemetry/MetricTypes/Gauge.html +1270 -0
- data/docs/Tasker/Telemetry/MetricTypes/Histogram.html +1492 -0
- data/docs/Tasker/Telemetry/MetricTypes.html +153 -0
- data/docs/Tasker/Telemetry/MetricsBackend.html +2510 -0
- data/docs/Tasker/Telemetry/MetricsExportService.html +578 -0
- data/docs/Tasker/Telemetry/PluginRegistry.html +1774 -0
- data/docs/Tasker/Telemetry/Plugins/BaseExporter.html +1835 -0
- data/docs/Tasker/Telemetry/Plugins/CsvExporter.html +768 -0
- data/docs/Tasker/Telemetry/Plugins/JsonExporter.html +747 -0
- data/docs/Tasker/Telemetry/Plugins.html +117 -0
- data/docs/Tasker/Telemetry/PrometheusExporter.html +481 -0
- data/docs/Tasker/Telemetry/TraceBackend.html +891 -0
- data/docs/Tasker/Telemetry.html +130 -0
- data/docs/Tasker/Types/AuthConfig.html +886 -0
- data/docs/Tasker/Types/BackoffConfig.html +1063 -0
- data/docs/Tasker/Types/BaseConfig.html +227 -0
- data/docs/Tasker/Types/CacheConfig.html +1731 -0
- data/docs/Tasker/Types/DatabaseConfig.html +388 -0
- data/docs/Tasker/Types/DependencyGraph.html +526 -0
- data/docs/Tasker/Types/DependencyGraphConfig.html +753 -0
- data/docs/Tasker/Types/EngineConfig.html +1181 -0
- data/docs/Tasker/Types/ExecutionConfig.html +1963 -0
- data/docs/Tasker/Types/GraphEdge.html +517 -0
- data/docs/Tasker/Types/GraphMetadata.html +781 -0
- data/docs/Tasker/Types/GraphNode.html +694 -0
- data/docs/Tasker/Types/HealthConfig.html +784 -0
- data/docs/Tasker/Types/StepSequence.html +353 -0
- data/docs/Tasker/Types/StepTemplate.html +1193 -0
- data/docs/Tasker/Types/TaskRequest.html +1179 -0
- data/docs/Tasker/Types/TelemetryConfig.html +2746 -0
- data/docs/Tasker/Types.html +154 -0
- data/docs/Tasker/WorkflowStep/StepFinder.html +282 -0
- data/docs/Tasker/WorkflowStep.html +2724 -0
- data/docs/Tasker/WorkflowStepEdge.html +304 -0
- data/docs/Tasker/WorkflowStepSerializer.html +305 -0
- data/docs/Tasker/WorkflowStepTransition/TransitionDescriptionFormatter.html +282 -0
- data/docs/Tasker/WorkflowStepTransition.html +2201 -0
- data/docs/Tasker/WorkflowStepsController.html +462 -0
- data/docs/Tasker.html +452 -0
- data/docs/VISION.md +584 -0
- data/docs/WHY.md +21 -0
- data/docs/_index.html +2375 -0
- data/docs/class_list.html +54 -0
- data/docs/css/common.css +1 -0
- data/docs/css/full_list.css +58 -0
- data/docs/css/style.css +503 -0
- data/docs/events/migration_plan_outcomes.md +80 -0
- data/docs/file.README.html +541 -0
- data/docs/file_list.html +59 -0
- data/docs/frames.html +22 -0
- data/docs/index.html +541 -0
- data/docs/js/app.js +344 -0
- data/docs/js/full_list.js +242 -0
- data/docs/js/jquery.js +4 -0
- data/docs/method_list.html +9182 -0
- data/docs/top-level-namespace.html +110 -0
- data/lib/generators/tasker/authenticator_generator.rb +301 -0
- data/lib/generators/tasker/authorization_coordinator_generator.rb +139 -0
- data/lib/generators/tasker/events_generator.rb +91 -0
- data/lib/generators/tasker/subscriber_generator.rb +107 -0
- data/lib/generators/tasker/task_handler_generator.rb +138 -0
- data/lib/generators/tasker/templates/api_token_authenticator.rb.erb +113 -0
- data/lib/generators/tasker/templates/api_token_authenticator_spec.rb.erb +144 -0
- data/lib/generators/tasker/templates/authorization_coordinator.rb.erb +95 -0
- data/lib/generators/tasker/templates/authorization_coordinator_spec.rb.erb +142 -0
- data/lib/generators/tasker/templates/custom_authenticator.rb.erb +108 -0
- data/lib/generators/tasker/templates/custom_authenticator_spec.rb.erb +162 -0
- data/lib/generators/tasker/templates/custom_events.yml.erb +62 -0
- data/lib/generators/tasker/templates/custom_subscriber.rb.erb +72 -0
- data/lib/generators/tasker/templates/devise_authenticator.rb.erb +101 -0
- data/lib/generators/tasker/templates/devise_authenticator_spec.rb.erb +126 -0
- data/lib/generators/tasker/templates/initialize.rb.erb +202 -0
- data/lib/generators/tasker/templates/jwt_authenticator.rb.erb +144 -0
- data/lib/generators/tasker/templates/jwt_authenticator_spec.rb.erb +298 -0
- data/lib/generators/tasker/templates/metrics_subscriber.rb.erb +258 -0
- data/lib/generators/tasker/templates/metrics_subscriber_spec.rb.erb +308 -0
- data/lib/generators/tasker/templates/omniauth_authenticator.rb.erb +135 -0
- data/lib/generators/tasker/templates/omniauth_authenticator_spec.rb.erb +196 -0
- data/lib/generators/tasker/templates/opentelemetry_initializer.rb +52 -0
- data/lib/generators/tasker/templates/subscriber.rb.erb +64 -0
- data/lib/generators/tasker/templates/subscriber_spec.rb.erb +80 -0
- data/lib/generators/tasker/templates/task_config.yaml.erb +117 -0
- data/lib/generators/tasker/templates/task_handler.rb.erb +59 -0
- data/lib/generators/tasker/templates/task_handler_spec.rb.erb +159 -0
- data/lib/tasker/analysis/runtime_graph_analyzer.rb +1168 -0
- data/lib/tasker/analysis/template_graph_analyzer.rb +328 -0
- data/lib/tasker/authentication/coordinator.rb +78 -0
- data/lib/tasker/authentication/errors.rb +9 -0
- data/lib/tasker/authentication/interface.rb +36 -0
- data/lib/tasker/authentication/none_authenticator.rb +26 -0
- data/lib/tasker/authorization/base_coordinator.rb +112 -0
- data/lib/tasker/authorization/errors.rb +26 -0
- data/lib/tasker/authorization/resource_constants.rb +74 -0
- data/lib/tasker/authorization/resource_registry.rb +143 -0
- data/lib/tasker/authorization.rb +75 -0
- data/lib/tasker/cache_capabilities.rb +131 -0
- data/lib/tasker/cache_strategy.rb +469 -0
- data/lib/tasker/concerns/authenticatable.rb +41 -0
- data/lib/tasker/concerns/authorizable.rb +204 -0
- data/lib/tasker/concerns/controller_authorizable.rb +124 -0
- data/lib/tasker/concerns/event_publisher.rb +716 -0
- data/lib/tasker/concerns/idempotent_state_transitions.rb +128 -0
- data/lib/tasker/concerns/state_machine_base.rb +218 -0
- data/lib/tasker/concerns/structured_logging.rb +387 -0
- data/lib/tasker/configuration.rb +325 -0
- data/lib/tasker/constants/event_definitions.rb +147 -0
- data/lib/tasker/constants/registry_events.rb +54 -0
- data/lib/tasker/constants.rb +417 -0
- data/lib/tasker/engine.rb +90 -0
- data/lib/tasker/errors.rb +90 -0
- data/lib/tasker/events/catalog.rb +432 -0
- data/lib/tasker/events/custom_registry.rb +175 -0
- data/lib/tasker/events/definition_loader.rb +199 -0
- data/lib/tasker/events/event_payload_builder.rb +461 -0
- data/lib/tasker/events/publisher.rb +149 -0
- data/lib/tasker/events/subscribers/base_subscriber.rb +601 -0
- data/lib/tasker/events/subscribers/metrics_subscriber.rb +120 -0
- data/lib/tasker/events/subscribers/telemetry_subscriber.rb +462 -0
- data/lib/tasker/events/subscription_loader.rb +161 -0
- data/lib/tasker/events.rb +37 -0
- data/lib/tasker/functions/function_based_analytics_metrics.rb +103 -0
- data/lib/tasker/functions/function_based_dependency_levels.rb +54 -0
- data/lib/tasker/functions/function_based_slowest_steps.rb +84 -0
- data/lib/tasker/functions/function_based_slowest_tasks.rb +84 -0
- data/lib/tasker/functions/function_based_step_readiness_status.rb +183 -0
- data/lib/tasker/functions/function_based_system_health_counts.rb +94 -0
- data/lib/tasker/functions/function_based_task_execution_context.rb +148 -0
- data/lib/tasker/functions/function_wrapper.rb +42 -0
- data/lib/tasker/functions.rb +12 -0
- data/lib/tasker/handler_factory.rb +322 -0
- data/lib/tasker/health/readiness_checker.rb +186 -0
- data/lib/tasker/health/status_checker.rb +203 -0
- data/lib/tasker/identity_strategy.rb +38 -0
- data/lib/tasker/logging/correlation_id_generator.rb +120 -0
- data/lib/tasker/orchestration/backoff_calculator.rb +184 -0
- data/lib/tasker/orchestration/connection_builder.rb +122 -0
- data/lib/tasker/orchestration/connection_pool_intelligence.rb +177 -0
- data/lib/tasker/orchestration/coordinator.rb +119 -0
- data/lib/tasker/orchestration/future_state_analyzer.rb +137 -0
- data/lib/tasker/orchestration/plugin_integration.rb +124 -0
- data/lib/tasker/orchestration/response_processor.rb +168 -0
- data/lib/tasker/orchestration/retry_header_parser.rb +78 -0
- data/lib/tasker/orchestration/step_executor.rb +941 -0
- data/lib/tasker/orchestration/step_sequence_factory.rb +67 -0
- data/lib/tasker/orchestration/task_finalizer.rb +564 -0
- data/lib/tasker/orchestration/task_initializer.rb +140 -0
- data/lib/tasker/orchestration/task_reenqueuer.rb +71 -0
- data/lib/tasker/orchestration/viable_step_discovery.rb +65 -0
- data/lib/tasker/orchestration/workflow_coordinator.rb +294 -0
- data/lib/tasker/orchestration.rb +45 -0
- data/lib/tasker/railtie.rb +9 -0
- data/lib/tasker/registry/base_registry.rb +177 -0
- data/lib/tasker/registry/event_publisher.rb +91 -0
- data/lib/tasker/registry/interface_validator.rb +140 -0
- data/lib/tasker/registry/statistics_collector.rb +381 -0
- data/lib/tasker/registry/subscriber_registry.rb +285 -0
- data/lib/tasker/registry.rb +22 -0
- data/lib/tasker/state_machine/step_state_machine.rb +508 -0
- data/lib/tasker/state_machine/task_state_machine.rb +192 -0
- data/lib/tasker/state_machine.rb +83 -0
- data/lib/tasker/step_handler/api.rb +410 -0
- data/lib/tasker/step_handler/base.rb +206 -0
- data/lib/tasker/task_builder.rb +432 -0
- data/lib/tasker/task_handler/class_methods.rb +324 -0
- data/lib/tasker/task_handler/instance_methods.rb +293 -0
- data/lib/tasker/task_handler/step_group.rb +182 -0
- data/lib/tasker/task_handler.rb +43 -0
- data/lib/tasker/telemetry/event_mapping.rb +126 -0
- data/lib/tasker/telemetry/event_router.rb +318 -0
- data/lib/tasker/telemetry/events/export_events.rb +38 -0
- data/lib/tasker/telemetry/export_coordinator.rb +497 -0
- data/lib/tasker/telemetry/intelligent_cache_manager.rb +508 -0
- data/lib/tasker/telemetry/log_backend.rb +224 -0
- data/lib/tasker/telemetry/metric_types.rb +368 -0
- data/lib/tasker/telemetry/metrics_backend.rb +1227 -0
- data/lib/tasker/telemetry/metrics_export_service.rb +392 -0
- data/lib/tasker/telemetry/plugin_registry.rb +333 -0
- data/lib/tasker/telemetry/plugins/base_exporter.rb +246 -0
- data/lib/tasker/telemetry/plugins/csv_exporter.rb +198 -0
- data/lib/tasker/telemetry/plugins/json_exporter.rb +141 -0
- data/lib/tasker/telemetry/prometheus_exporter.rb +249 -0
- data/lib/tasker/telemetry/trace_backend.rb +186 -0
- data/lib/tasker/telemetry.rb +59 -0
- data/lib/tasker/types/auth_config.rb +81 -0
- data/lib/tasker/types/backoff_config.rb +142 -0
- data/lib/tasker/types/cache_config.rb +257 -0
- data/lib/tasker/types/database_config.rb +39 -0
- data/lib/tasker/types/dependency_graph.rb +225 -0
- data/lib/tasker/types/dependency_graph_config.rb +149 -0
- data/lib/tasker/types/engine_config.rb +131 -0
- data/lib/tasker/types/execution_config.rb +289 -0
- data/lib/tasker/types/health_config.rb +84 -0
- data/lib/tasker/types/step_sequence.rb +24 -0
- data/lib/tasker/types/step_template.rb +63 -0
- data/lib/tasker/types/task_request.rb +60 -0
- data/lib/tasker/types/telemetry_config.rb +273 -0
- data/lib/tasker/types.rb +64 -0
- data/lib/tasker/version.rb +7 -0
- data/lib/tasker.rb +82 -0
- data/lib/tasks/tasker_tasks.rake +302 -0
- metadata +958 -0
@@ -0,0 +1,941 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'concurrent'
|
4
|
+
require_relative '../concerns/idempotent_state_transitions'
|
5
|
+
require_relative '../concerns/structured_logging'
|
6
|
+
require_relative '../concerns/event_publisher'
|
7
|
+
require_relative '../types/step_sequence'
|
8
|
+
require_relative 'future_state_analyzer'
|
9
|
+
|
10
|
+
module Tasker
|
11
|
+
module Orchestration
|
12
|
+
# StepExecutor handles the execution of workflow steps with concurrent processing
|
13
|
+
#
|
14
|
+
# This class provides the implementation for step execution while preserving
|
15
|
+
# the original concurrent processing capabilities using concurrent-ruby.
|
16
|
+
# It fires lifecycle events for observability.
|
17
|
+
#
|
18
|
+
# Enhanced with structured logging and performance monitoring for production observability.
|
19
|
+
class StepExecutor
|
20
|
+
include Tasker::Concerns::IdempotentStateTransitions
|
21
|
+
include Tasker::Concerns::EventPublisher
|
22
|
+
include Tasker::Concerns::StructuredLogging
|
23
|
+
|
24
|
+
# Configuration-driven execution settings
|
25
|
+
# These delegate to Tasker.configuration.execution for configurable values
|
26
|
+
# while maintaining architectural constants for Ruby-specific optimizations
|
27
|
+
|
28
|
+
def execution_config
|
29
|
+
@execution_config ||= Tasker.configuration.execution
|
30
|
+
end
|
31
|
+
|
32
|
+
# Execute a collection of viable steps
|
33
|
+
#
|
34
|
+
# This method preserves the original concurrent processing logic while
|
35
|
+
# adding observability through lifecycle events and structured logging.
|
36
|
+
#
|
37
|
+
# @param task [Tasker::Task] The task containing the steps
|
38
|
+
# @param sequence [Tasker::Types::StepSequence] The step sequence
|
39
|
+
# @param viable_steps [Array<Tasker::WorkflowStep>] Steps ready for execution
|
40
|
+
# @param task_handler [Object] The task handler instance
|
41
|
+
# @return [Array<Tasker::WorkflowStep>] Successfully processed steps
|
42
|
+
def execute_steps(task, sequence, viable_steps, task_handler)
|
43
|
+
return [] if viable_steps.empty?
|
44
|
+
|
45
|
+
execution_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
46
|
+
|
47
|
+
# Always use concurrent processing - sequential mode has been deprecated
|
48
|
+
processing_mode = 'concurrent'
|
49
|
+
|
50
|
+
log_orchestration_event('step_batch_execution', :started,
|
51
|
+
task_id: task.task_id,
|
52
|
+
step_count: viable_steps.size,
|
53
|
+
processing_mode: processing_mode,
|
54
|
+
step_names: viable_steps.map(&:name))
|
55
|
+
|
56
|
+
# Fire observability event through orchestrator
|
57
|
+
publish_steps_execution_started(
|
58
|
+
task,
|
59
|
+
step_count: viable_steps.size,
|
60
|
+
processing_mode: processing_mode
|
61
|
+
)
|
62
|
+
|
63
|
+
# Always use concurrent processing with dynamic concurrency optimization
|
64
|
+
processed_steps = execute_steps_concurrently_with_monitoring(task, sequence, viable_steps, task_handler)
|
65
|
+
|
66
|
+
execution_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - execution_start_time
|
67
|
+
successful_count = processed_steps.count do |s|
|
68
|
+
s&.status == Tasker::Constants::WorkflowStepStatuses::COMPLETE
|
69
|
+
end
|
70
|
+
|
71
|
+
# Log performance metrics
|
72
|
+
log_performance_event('step_batch_execution', execution_duration,
|
73
|
+
task_id: task.task_id,
|
74
|
+
step_count: viable_steps.size,
|
75
|
+
processed_count: processed_steps.size,
|
76
|
+
successful_count: successful_count,
|
77
|
+
failure_count: processed_steps.size - successful_count,
|
78
|
+
processing_mode: processing_mode)
|
79
|
+
|
80
|
+
# Fire completion event through orchestrator
|
81
|
+
publish_steps_execution_completed(
|
82
|
+
task,
|
83
|
+
processed_count: processed_steps.size,
|
84
|
+
successful_count: successful_count
|
85
|
+
)
|
86
|
+
|
87
|
+
log_orchestration_event('step_batch_execution', :completed,
|
88
|
+
task_id: task.task_id,
|
89
|
+
processed_count: processed_steps.size,
|
90
|
+
successful_count: successful_count,
|
91
|
+
failure_count: processed_steps.size - successful_count,
|
92
|
+
duration_ms: (execution_duration * 1000).round(2))
|
93
|
+
|
94
|
+
processed_steps.compact
|
95
|
+
end
|
96
|
+
|
97
|
+
# Calculate optimal concurrency based on system health and resources
|
98
|
+
#
|
99
|
+
# This method dynamically determines the maximum number of steps that can be
|
100
|
+
# executed concurrently based on current system load, database connections,
|
101
|
+
# and other health metrics. Now enhanced with ConnectionPoolIntelligence
|
102
|
+
# for Rails-aware connection management.
|
103
|
+
#
|
104
|
+
# @return [Integer] Optimal number of concurrent steps (between configured min and max)
|
105
|
+
def max_concurrent_steps
|
106
|
+
# Return cached value if still valid
|
107
|
+
cache_duration = execution_config.concurrency_cache_duration.seconds
|
108
|
+
if @max_concurrent_steps && @concurrency_calculated_at &&
|
109
|
+
(Time.current - @concurrency_calculated_at) < cache_duration
|
110
|
+
return @max_concurrent_steps
|
111
|
+
end
|
112
|
+
|
113
|
+
# Calculate new concurrency level using enhanced intelligence
|
114
|
+
@max_concurrent_steps = calculate_optimal_concurrency
|
115
|
+
@concurrency_calculated_at = Time.current
|
116
|
+
|
117
|
+
@max_concurrent_steps
|
118
|
+
end
|
119
|
+
|
120
|
+
# Handle viable steps discovered event
|
121
|
+
#
|
122
|
+
# Convenience method for event-driven workflows that takes an event payload
|
123
|
+
# and executes the discovered steps.
|
124
|
+
#
|
125
|
+
# @param event [Hash] Event payload with task_id, step_ids, and processing_mode
|
126
|
+
def handle_viable_steps_discovered(event)
|
127
|
+
task_id = event[:task_id]
|
128
|
+
step_ids = event[:step_ids] || []
|
129
|
+
|
130
|
+
return [] if step_ids.empty?
|
131
|
+
|
132
|
+
with_correlation_id(event[:correlation_id]) do
|
133
|
+
log_orchestration_event('event_driven_execution', :started,
|
134
|
+
task_id: task_id,
|
135
|
+
step_ids: step_ids,
|
136
|
+
trigger: 'viable_steps_discovered')
|
137
|
+
|
138
|
+
task = Tasker::Task.find(task_id)
|
139
|
+
task_handler = Tasker::HandlerFactory.instance.get(task.name)
|
140
|
+
sequence = Tasker::Orchestration::StepSequenceFactory.get_sequence(task, task_handler)
|
141
|
+
viable_steps = task.workflow_steps.where(workflow_step_id: step_ids)
|
142
|
+
|
143
|
+
execute_steps(task, sequence, viable_steps, task_handler)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Execute a single step with state machine transitions and error handling
|
148
|
+
#
|
149
|
+
# Enhanced with structured logging and performance monitoring.
|
150
|
+
#
|
151
|
+
# @param task [Tasker::Task] The task containing the step
|
152
|
+
# @param sequence [Tasker::Types::StepSequence] The step sequence
|
153
|
+
# @param step [Tasker::WorkflowStep] The step to execute
|
154
|
+
# @param task_handler [Object] The task handler instance
|
155
|
+
# @return [Tasker::WorkflowStep, nil] The executed step or nil if failed
|
156
|
+
def execute_single_step(task, sequence, step, task_handler)
|
157
|
+
step_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
158
|
+
|
159
|
+
log_step_event(step, :execution_starting,
|
160
|
+
task_id: task.task_id,
|
161
|
+
step_status: step.status,
|
162
|
+
attempt_count: step.attempts)
|
163
|
+
|
164
|
+
# Guard clauses - fail fast if preconditions aren't met
|
165
|
+
return nil unless validate_step_preconditions_with_logging(step)
|
166
|
+
return nil unless ensure_step_has_initial_state_with_logging(step)
|
167
|
+
return nil unless step_ready_for_execution_with_logging?(step)
|
168
|
+
|
169
|
+
# Main execution workflow with monitoring
|
170
|
+
result = execute_step_workflow_with_monitoring(task, sequence, step, task_handler, step_start_time)
|
171
|
+
|
172
|
+
step_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time
|
173
|
+
|
174
|
+
if result
|
175
|
+
log_performance_event('single_step_execution', step_duration,
|
176
|
+
task_id: task.task_id,
|
177
|
+
step_id: step.workflow_step_id,
|
178
|
+
step_name: step.name,
|
179
|
+
result: 'success',
|
180
|
+
attempt_count: step.attempts)
|
181
|
+
else
|
182
|
+
log_performance_event('single_step_execution', step_duration,
|
183
|
+
task_id: task.task_id,
|
184
|
+
step_id: step.workflow_step_id,
|
185
|
+
step_name: step.name,
|
186
|
+
result: 'failure',
|
187
|
+
attempt_count: step.attempts)
|
188
|
+
end
|
189
|
+
|
190
|
+
result
|
191
|
+
rescue StandardError => e
|
192
|
+
step_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time
|
193
|
+
|
194
|
+
# Log unexpected errors that occur outside the normal workflow
|
195
|
+
step_id = step&.workflow_step_id
|
196
|
+
log_exception(e, context: {
|
197
|
+
step_id: step_id,
|
198
|
+
task_id: task&.task_id,
|
199
|
+
operation: 'single_step_execution',
|
200
|
+
duration: step_duration
|
201
|
+
})
|
202
|
+
|
203
|
+
Rails.logger.error("StepExecutor: Unexpected error in execute_single_step for step #{step_id}: #{e.message}")
|
204
|
+
nil
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
# Execute steps concurrently with monitoring
|
210
|
+
#
|
211
|
+
# @param task [Tasker::Task] The task containing the steps
|
212
|
+
# @param sequence [Tasker::Types::StepSequence] The step sequence
|
213
|
+
# @param viable_steps [Array<Tasker::WorkflowStep>] Steps ready for execution
|
214
|
+
# @param task_handler [Object] The task handler instance
|
215
|
+
# @return [Array<Tasker::WorkflowStep>] Successfully processed steps
|
216
|
+
def execute_steps_concurrently_with_monitoring(task, sequence, viable_steps, task_handler)
|
217
|
+
log_orchestration_event('concurrent_execution', :started,
|
218
|
+
task_id: task.task_id,
|
219
|
+
step_count: viable_steps.size,
|
220
|
+
max_concurrency: max_concurrent_steps)
|
221
|
+
|
222
|
+
concurrent_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
223
|
+
|
224
|
+
# Use the original concurrent execution logic without thread_pool
|
225
|
+
results = execute_steps_concurrently(task, sequence, viable_steps, task_handler)
|
226
|
+
|
227
|
+
concurrent_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - concurrent_start_time
|
228
|
+
successful_count = results.count { |r| r&.status == Tasker::Constants::WorkflowStepStatuses::COMPLETE }
|
229
|
+
|
230
|
+
log_performance_event('concurrent_execution', concurrent_duration,
|
231
|
+
task_id: task.task_id,
|
232
|
+
step_count: viable_steps.size,
|
233
|
+
successful_count: successful_count,
|
234
|
+
failure_count: results.size - successful_count)
|
235
|
+
|
236
|
+
log_orchestration_event('concurrent_execution', :completed,
|
237
|
+
task_id: task.task_id,
|
238
|
+
step_count: viable_steps.size,
|
239
|
+
successful_count: successful_count,
|
240
|
+
duration_ms: (concurrent_duration * 1000).round(2))
|
241
|
+
|
242
|
+
results
|
243
|
+
end
|
244
|
+
|
245
|
+
# Validate that the step and database connection are ready
|
246
|
+
def validate_step_preconditions_with_logging(step)
|
247
|
+
unless ActiveRecord::Base.connection.active?
|
248
|
+
log_step_event(step, :validation_failed,
|
249
|
+
reason: 'database_connection_inactive',
|
250
|
+
step_status: step&.status)
|
251
|
+
Rails.logger.error("StepExecutor: Database connection inactive for step #{step&.workflow_step_id}")
|
252
|
+
return false
|
253
|
+
end
|
254
|
+
|
255
|
+
step = step.reload if step&.persisted?
|
256
|
+
unless step
|
257
|
+
log_structured(:error, 'Step validation failed',
|
258
|
+
reason: 'step_nil_or_not_persisted',
|
259
|
+
entity_type: 'step')
|
260
|
+
Rails.logger.error('StepExecutor: Step is nil or not persisted')
|
261
|
+
return false
|
262
|
+
end
|
263
|
+
|
264
|
+
log_step_event(step, :validation_passed,
|
265
|
+
step_status: step.status,
|
266
|
+
step_attempts: step.attempts)
|
267
|
+
|
268
|
+
true
|
269
|
+
rescue ActiveRecord::ConnectionNotEstablished, PG::ConnectionBad => e
|
270
|
+
log_exception(e, context: {
|
271
|
+
step_id: step&.workflow_step_id,
|
272
|
+
operation: 'step_precondition_validation'
|
273
|
+
})
|
274
|
+
Rails.logger.error("StepExecutor: Database connection error for step #{step&.workflow_step_id}: #{e.message}")
|
275
|
+
false
|
276
|
+
rescue StandardError => e
|
277
|
+
log_exception(e, context: {
|
278
|
+
step_id: step&.workflow_step_id,
|
279
|
+
operation: 'step_precondition_validation'
|
280
|
+
})
|
281
|
+
Rails.logger.error("StepExecutor: Unexpected error checking step #{step&.workflow_step_id}: #{e.message}")
|
282
|
+
false
|
283
|
+
end
|
284
|
+
|
285
|
+
# Ensure step has an initial state, set to pending if blank
|
286
|
+
def ensure_step_has_initial_state_with_logging(step) # rubocop:disable Naming/PredicateMethod
|
287
|
+
current_state = step.state_machine.current_state
|
288
|
+
return true if current_state.present?
|
289
|
+
|
290
|
+
log_step_event(step, :state_initialization,
|
291
|
+
current_state: current_state,
|
292
|
+
target_state: Tasker::Constants::WorkflowStepStatuses::PENDING)
|
293
|
+
|
294
|
+
Rails.logger.debug { "StepExecutor: Step #{step.workflow_step_id} has no state, setting to pending" }
|
295
|
+
unless safe_transition_to(step, Tasker::Constants::WorkflowStepStatuses::PENDING)
|
296
|
+
log_step_event(step, :state_initialization_failed,
|
297
|
+
current_state: current_state,
|
298
|
+
target_state: Tasker::Constants::WorkflowStepStatuses::PENDING)
|
299
|
+
Rails.logger.error("StepExecutor: Failed to initialize step #{step.workflow_step_id} to pending state")
|
300
|
+
return false
|
301
|
+
end
|
302
|
+
|
303
|
+
step.reload
|
304
|
+
log_step_event(step, :state_initialized,
|
305
|
+
new_state: step.state_machine.current_state)
|
306
|
+
true
|
307
|
+
end
|
308
|
+
|
309
|
+
# Check if step is in the correct state for execution
|
310
|
+
def step_ready_for_execution_with_logging?(step)
|
311
|
+
current_state = step.state_machine.current_state
|
312
|
+
is_ready = current_state == Tasker::Constants::WorkflowStepStatuses::PENDING
|
313
|
+
|
314
|
+
log_step_event(step, :readiness_check,
|
315
|
+
current_state: current_state,
|
316
|
+
is_ready: is_ready,
|
317
|
+
expected_state: Tasker::Constants::WorkflowStepStatuses::PENDING)
|
318
|
+
|
319
|
+
return true if is_ready
|
320
|
+
|
321
|
+
Rails.logger.debug do
|
322
|
+
"StepExecutor: Skipping step #{step.workflow_step_id} - not pending (current: '#{current_state}')"
|
323
|
+
end
|
324
|
+
false
|
325
|
+
end
|
326
|
+
|
327
|
+
# Execute the main step workflow with monitoring: transition -> execute -> complete
|
328
|
+
def execute_step_workflow_with_monitoring(task, sequence, step, task_handler, step_start_time)
|
329
|
+
publish_execution_started_event(task, step)
|
330
|
+
|
331
|
+
log_step_event(step, :workflow_starting,
|
332
|
+
task_id: task.task_id,
|
333
|
+
step_status: step.status,
|
334
|
+
elapsed_time_ms: ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time) * 1000).round(2))
|
335
|
+
|
336
|
+
# Execute step handler and handle both success and error cases
|
337
|
+
begin
|
338
|
+
# Transition to in_progress first - if this fails, it should be treated as an error
|
339
|
+
transition_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
340
|
+
transition_step_to_in_progress!(step)
|
341
|
+
transition_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - transition_start_time
|
342
|
+
|
343
|
+
log_performance_event('step_state_transition', transition_duration,
|
344
|
+
task_id: task.task_id,
|
345
|
+
step_id: step.workflow_step_id,
|
346
|
+
from_state: Tasker::Constants::WorkflowStepStatuses::PENDING,
|
347
|
+
to_state: Tasker::Constants::WorkflowStepStatuses::IN_PROGRESS)
|
348
|
+
|
349
|
+
# Execute the actual step handler with timing
|
350
|
+
handler_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
351
|
+
execute_step_handler(task, sequence, step, task_handler)
|
352
|
+
handler_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - handler_start_time
|
353
|
+
|
354
|
+
log_performance_event('step_handler_execution', handler_duration,
|
355
|
+
task_id: task.task_id,
|
356
|
+
step_id: step.workflow_step_id,
|
357
|
+
step_name: step.name,
|
358
|
+
result: 'success')
|
359
|
+
|
360
|
+
# Complete step execution with timing
|
361
|
+
completion_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
362
|
+
result = complete_step_execution(task, step)
|
363
|
+
completion_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - completion_start_time
|
364
|
+
|
365
|
+
log_performance_event('step_completion', completion_duration,
|
366
|
+
task_id: task.task_id,
|
367
|
+
step_id: step.workflow_step_id,
|
368
|
+
final_status: result&.status)
|
369
|
+
|
370
|
+
total_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time
|
371
|
+
log_step_event(step, :workflow_completed,
|
372
|
+
task_id: task.task_id,
|
373
|
+
final_status: result&.status,
|
374
|
+
total_duration_ms: (total_duration * 1000).round(2),
|
375
|
+
handler_duration_ms: (handler_duration * 1000).round(2))
|
376
|
+
|
377
|
+
result
|
378
|
+
rescue StandardError => e
|
379
|
+
error_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - step_start_time
|
380
|
+
|
381
|
+
log_step_event(step, :workflow_failed,
|
382
|
+
task_id: task.task_id,
|
383
|
+
error: e.message,
|
384
|
+
error_class: e.class.name,
|
385
|
+
duration_ms: (error_duration * 1000).round(2))
|
386
|
+
|
387
|
+
log_performance_event('step_handler_execution', error_duration,
|
388
|
+
task_id: task.task_id,
|
389
|
+
step_id: step.workflow_step_id,
|
390
|
+
step_name: step.name,
|
391
|
+
result: 'failure',
|
392
|
+
error_class: e.class.name)
|
393
|
+
|
394
|
+
# Store error data in step.results like legacy code
|
395
|
+
store_step_error_data(step, e)
|
396
|
+
|
397
|
+
# Complete error step execution with persistence (similar to complete_step_execution but for errors)
|
398
|
+
error_completion_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
399
|
+
result = complete_error_step_execution(task, step)
|
400
|
+
error_completion_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - error_completion_start_time
|
401
|
+
|
402
|
+
log_performance_event('step_error_completion', error_completion_duration,
|
403
|
+
task_id: task.task_id,
|
404
|
+
step_id: step.workflow_step_id,
|
405
|
+
final_status: result&.status)
|
406
|
+
|
407
|
+
nil
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
# Publish event for step execution start
|
412
|
+
def publish_execution_started_event(_task, step)
|
413
|
+
# Use clean API for step execution start
|
414
|
+
publish_step_started(step)
|
415
|
+
end
|
416
|
+
|
417
|
+
# Transition step to in_progress state (bang version that raises on failure)
|
418
|
+
def transition_step_to_in_progress!(step)
|
419
|
+
unless safe_transition_to(step, Tasker::Constants::WorkflowStepStatuses::IN_PROGRESS)
|
420
|
+
current_state = step.state_machine.current_state
|
421
|
+
error_message = "Cannot transition step #{step.workflow_step_id} from '#{current_state}' to 'in_progress'. " \
|
422
|
+
'Check step dependencies and current state.'
|
423
|
+
|
424
|
+
Rails.logger.warn("StepExecutor: #{error_message}")
|
425
|
+
raise Tasker::ProceduralError, error_message
|
426
|
+
end
|
427
|
+
|
428
|
+
true
|
429
|
+
end
|
430
|
+
|
431
|
+
# Execute the actual step handler logic
|
432
|
+
def execute_step_handler(task, sequence, step, task_handler)
|
433
|
+
step_handler = task_handler.get_step_handler(step)
|
434
|
+
step_handler.handle(task, sequence, step)
|
435
|
+
end
|
436
|
+
|
437
|
+
# Complete step execution and publish completion event
|
438
|
+
#
|
439
|
+
# This method ensures atomic completion by wrapping both the step save
|
440
|
+
# and state transition in a database transaction. This is critical for
|
441
|
+
# idempotency: if either operation fails, the step remains in "in_progress"
|
442
|
+
# and can be safely retried without repeating the actual work.
|
443
|
+
def complete_step_execution(_task, step)
|
444
|
+
completed_step = nil
|
445
|
+
|
446
|
+
# Update attempt tracking like legacy code (for consistency with error path)
|
447
|
+
step.attempts ||= 0
|
448
|
+
step.attempts += 1
|
449
|
+
step.last_attempted_at = Time.zone.now
|
450
|
+
|
451
|
+
# Use database transaction to ensure atomic completion
|
452
|
+
ActiveRecord::Base.transaction do
|
453
|
+
# STEP 1: Set completion flags for successful steps
|
454
|
+
# Mark step as processed and not in_process (mirrors error path logic)
|
455
|
+
step.processed = true
|
456
|
+
step.in_process = false
|
457
|
+
step.processed_at = Time.zone.now
|
458
|
+
|
459
|
+
# STEP 2: Save the step results and flags
|
460
|
+
# This persists the output of the work that has already been performed
|
461
|
+
step.save!
|
462
|
+
|
463
|
+
# STEP 3: Transition to complete state
|
464
|
+
# This marks the step as done, but only if the save succeeded
|
465
|
+
unless safe_transition_to(step, Tasker::Constants::WorkflowStepStatuses::COMPLETE)
|
466
|
+
Rails.logger.error("StepExecutor: Failed to transition step #{step.workflow_step_id} to complete")
|
467
|
+
# Raise exception to trigger transaction rollback
|
468
|
+
raise ActiveRecord::Rollback, "Failed to transition step #{step.workflow_step_id} to complete state"
|
469
|
+
end
|
470
|
+
|
471
|
+
completed_step = step
|
472
|
+
end
|
473
|
+
|
474
|
+
# If we got here, both save and transition succeeded
|
475
|
+
unless completed_step
|
476
|
+
Rails.logger.error("StepExecutor: Step completion transaction rolled back for step #{step.workflow_step_id}")
|
477
|
+
return nil
|
478
|
+
end
|
479
|
+
|
480
|
+
# Publish completion event outside transaction (for performance)
|
481
|
+
publish_step_completed(
|
482
|
+
step,
|
483
|
+
attempt_number: step.attempts,
|
484
|
+
execution_duration: step.processed_at&.-(step.updated_at)
|
485
|
+
)
|
486
|
+
|
487
|
+
Rails.logger.debug { "StepExecutor: Successfully completed step #{step.workflow_step_id}" }
|
488
|
+
completed_step
|
489
|
+
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
|
490
|
+
Rails.logger.error("StepExecutor: Failed to save step #{step.workflow_step_id}: #{e.message}")
|
491
|
+
nil
|
492
|
+
rescue StandardError => e
|
493
|
+
Rails.logger.error("StepExecutor: Unexpected error completing step #{step.workflow_step_id}: #{e.message}")
|
494
|
+
nil
|
495
|
+
end
|
496
|
+
|
497
|
+
# Store error data in step results (matching legacy pattern)
|
498
|
+
def store_step_error_data(step, error)
|
499
|
+
step.results ||= {}
|
500
|
+
step.results = step.results.merge(
|
501
|
+
error: error.to_s,
|
502
|
+
backtrace: error.backtrace.join("\n"),
|
503
|
+
error_class: error.class.name
|
504
|
+
)
|
505
|
+
|
506
|
+
# Update attempt tracking like legacy code
|
507
|
+
step.attempts ||= 0
|
508
|
+
step.attempts += 1
|
509
|
+
step.last_attempted_at = Time.zone.now
|
510
|
+
end
|
511
|
+
|
512
|
+
# Complete error step execution with persistence and state transition
|
513
|
+
#
|
514
|
+
# This mirrors complete_step_execution but handles error state persistence.
|
515
|
+
# Critical: We MUST save error steps to preserve error data and attempt tracking.
|
516
|
+
def complete_error_step_execution(_task, step)
|
517
|
+
completed_error_step = nil
|
518
|
+
|
519
|
+
# Use database transaction to ensure atomic error completion
|
520
|
+
ActiveRecord::Base.transaction do
|
521
|
+
# STEP 1: Reset step flags for retry eligibility
|
522
|
+
# Failed steps must be marked as not in_process and not processed
|
523
|
+
# so they can be picked up for retry by the step readiness view
|
524
|
+
step.in_process = false
|
525
|
+
step.processed = false
|
526
|
+
|
527
|
+
# STEP 2: Save the step with error data
|
528
|
+
# This persists the error information and attempt tracking
|
529
|
+
step.save!
|
530
|
+
|
531
|
+
# STEP 3: Transition to error state
|
532
|
+
# This marks the step as failed, but only if the save succeeded
|
533
|
+
unless safe_transition_to(step, Tasker::Constants::WorkflowStepStatuses::ERROR)
|
534
|
+
Rails.logger.error("StepExecutor: Failed to transition step #{step.workflow_step_id} to error")
|
535
|
+
# Raise exception to trigger transaction rollback
|
536
|
+
raise ActiveRecord::Rollback, "Failed to transition step #{step.workflow_step_id} to error state"
|
537
|
+
end
|
538
|
+
|
539
|
+
completed_error_step = step
|
540
|
+
end
|
541
|
+
|
542
|
+
# If we got here, both save and transition succeeded
|
543
|
+
unless completed_error_step
|
544
|
+
step_id = step.workflow_step_id
|
545
|
+
Rails.logger.error("StepExecutor: Error step completion transaction rolled back for step #{step_id}")
|
546
|
+
return nil
|
547
|
+
end
|
548
|
+
|
549
|
+
# Publish error event outside transaction (for performance)
|
550
|
+
publish_step_failed(
|
551
|
+
step,
|
552
|
+
error_message: step.results['error'],
|
553
|
+
error_class: step.results['error_class'],
|
554
|
+
attempt_number: step.attempts,
|
555
|
+
backtrace: step.results['backtrace']&.split("\n")&.first(10)
|
556
|
+
)
|
557
|
+
|
558
|
+
Rails.logger.debug { "StepExecutor: Successfully saved error step #{step.workflow_step_id}" }
|
559
|
+
completed_error_step
|
560
|
+
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved => e
|
561
|
+
Rails.logger.error("StepExecutor: Failed to save error step #{step.workflow_step_id}: #{e.message}")
|
562
|
+
nil
|
563
|
+
rescue StandardError => e
|
564
|
+
step_id = step.workflow_step_id
|
565
|
+
Rails.logger.error("StepExecutor: Unexpected error completing error step #{step_id}: #{e.message}")
|
566
|
+
nil
|
567
|
+
end
|
568
|
+
|
569
|
+
# Calculate optimal concurrency based on system health metrics
|
570
|
+
#
|
571
|
+
# Enhanced with ConnectionPoolIntelligence for Rails-aware connection management.
|
572
|
+
# Falls back to legacy calculation if ConnectionPoolIntelligence is unavailable.
|
573
|
+
#
|
574
|
+
# @return [Integer] Calculated concurrency level
|
575
|
+
def calculate_optimal_concurrency
|
576
|
+
# Use enhanced ConnectionPoolIntelligence for Rails-aware calculation
|
577
|
+
intelligence_concurrency = Tasker::Orchestration::ConnectionPoolIntelligence
|
578
|
+
.intelligent_concurrency_for_step_executor
|
579
|
+
|
580
|
+
# Combine with system health analysis for comprehensive optimization
|
581
|
+
health_data = fetch_system_health_data
|
582
|
+
if health_data
|
583
|
+
# Apply additional system load considerations
|
584
|
+
base_concurrency = calculate_base_concurrency(health_data)
|
585
|
+
|
586
|
+
# Use the most conservative of both approaches for safety
|
587
|
+
optimal_concurrency = [intelligence_concurrency, base_concurrency].min
|
588
|
+
else
|
589
|
+
# Use ConnectionPoolIntelligence recommendation when health data unavailable
|
590
|
+
optimal_concurrency = intelligence_concurrency
|
591
|
+
end
|
592
|
+
|
593
|
+
# Ensure we stay within configured bounds
|
594
|
+
optimal_concurrency.clamp(execution_config.min_concurrent_steps, execution_config.max_concurrent_steps_limit)
|
595
|
+
rescue StandardError => e
|
596
|
+
log_structured(:warn, 'Optimal concurrency calculation failed, using fallback', {
|
597
|
+
error_class: e.class.name,
|
598
|
+
error_message: e.message,
|
599
|
+
fallback_concurrency: execution_config.min_concurrent_steps
|
600
|
+
})
|
601
|
+
execution_config.min_concurrent_steps
|
602
|
+
end
|
603
|
+
|
604
|
+
# Fetch system health data with error handling
|
605
|
+
#
|
606
|
+
# @return [HealthMetrics, nil] Health metrics or nil if unavailable
|
607
|
+
def fetch_system_health_data
|
608
|
+
Tasker::Functions::FunctionBasedSystemHealthCounts.call
|
609
|
+
rescue StandardError => e
|
610
|
+
Rails.logger.warn("StepExecutor: Failed to fetch system health data: #{e.message}")
|
611
|
+
nil
|
612
|
+
end
|
613
|
+
|
614
|
+
# Fetch connection pool size with error handling
|
615
|
+
#
|
616
|
+
# @return [Integer, nil] Connection pool size or nil if unavailable
|
617
|
+
def fetch_connection_pool_size
|
618
|
+
ActiveRecord::Base.connection_pool&.size
|
619
|
+
rescue StandardError => e
|
620
|
+
Rails.logger.warn("StepExecutor: Failed to fetch connection pool size: #{e.message}")
|
621
|
+
nil
|
622
|
+
end
|
623
|
+
|
624
|
+
# Calculate base concurrency from system health metrics
|
625
|
+
#
|
626
|
+
# @param health_data [HealthMetrics] System health metrics
|
627
|
+
# @return [Integer] Base concurrency level
|
628
|
+
def calculate_base_concurrency(health_data)
|
629
|
+
# Calculate system load factor (0.0 to 1.0+)
|
630
|
+
total_active_work = [health_data.in_progress_tasks + health_data.pending_tasks, 1].max
|
631
|
+
load_factor = total_active_work.to_f / [health_data.total_tasks, 1].max
|
632
|
+
|
633
|
+
# Calculate step processing load
|
634
|
+
total_active_steps = [health_data.in_progress_steps + health_data.pending_steps, 1].max
|
635
|
+
step_load_factor = total_active_steps.to_f / [health_data.total_steps, 1].max
|
636
|
+
|
637
|
+
# Combine load factors (weighted toward step load)
|
638
|
+
combined_load = (load_factor * 0.3) + (step_load_factor * 0.7)
|
639
|
+
|
640
|
+
# Calculate concurrency based on load (inverse relationship)
|
641
|
+
min_steps = execution_config.min_concurrent_steps
|
642
|
+
max_steps = execution_config.max_concurrent_steps_limit
|
643
|
+
|
644
|
+
if combined_load <= 0.3
|
645
|
+
# Low load: Allow higher concurrency
|
646
|
+
max_steps
|
647
|
+
elsif combined_load <= 0.6
|
648
|
+
# Moderate load: Medium concurrency
|
649
|
+
(((max_steps - min_steps) * 0.6) + min_steps).round
|
650
|
+
else
|
651
|
+
# High load: Conservative concurrency
|
652
|
+
min_steps + 1
|
653
|
+
end
|
654
|
+
rescue StandardError => e
|
655
|
+
Rails.logger.warn("StepExecutor: Error calculating base concurrency: #{e.message}")
|
656
|
+
execution_config.min_concurrent_steps
|
657
|
+
end
|
658
|
+
|
659
|
+
# Calculate connection-constrained concurrency
|
660
|
+
#
|
661
|
+
# @param health_data [HealthMetrics] System health metrics
|
662
|
+
# @param pool_size [Integer] Connection pool size
|
663
|
+
# @return [Integer] Connection-constrained concurrency level
|
664
|
+
def calculate_connection_constrained_concurrency(health_data, pool_size)
|
665
|
+
min_steps = execution_config.min_concurrent_steps
|
666
|
+
max_steps = execution_config.max_concurrent_steps_limit
|
667
|
+
|
668
|
+
return min_steps if pool_size <= 0
|
669
|
+
|
670
|
+
# Get current connection usage
|
671
|
+
active_connections = [health_data.active_connections, 0].max
|
672
|
+
connection_utilization = active_connections.to_f / pool_size
|
673
|
+
|
674
|
+
# Calculate available connections with safety margin
|
675
|
+
available_connections = pool_size - active_connections
|
676
|
+
safety_margin = [pool_size * 0.2, 2].max.round # 20% safety margin, minimum 2
|
677
|
+
|
678
|
+
safe_available = available_connections - safety_margin
|
679
|
+
|
680
|
+
# Don't allow concurrency that would exhaust connections
|
681
|
+
if connection_utilization >= 0.9 || safe_available <= 2
|
682
|
+
min_steps
|
683
|
+
elsif connection_utilization >= 0.7
|
684
|
+
[safe_available / 2, min_steps + 1].max
|
685
|
+
else
|
686
|
+
[safe_available, max_steps].min
|
687
|
+
end
|
688
|
+
rescue StandardError => e
|
689
|
+
Rails.logger.warn("StepExecutor: Error calculating connection-constrained concurrency: #{e.message}")
|
690
|
+
execution_config.min_concurrent_steps
|
691
|
+
end
|
692
|
+
|
693
|
+
# Execute steps concurrently using concurrent-ruby with enhanced memory management
|
694
|
+
#
|
695
|
+
# Enhanced with timeout protection, comprehensive future cleanup, and intelligent
|
696
|
+
# garbage collection triggers to prevent memory leaks in long-running processes.
|
697
|
+
#
|
698
|
+
# @param task [Tasker::Task] The task containing the steps
|
699
|
+
# @param sequence [Tasker::Types::StepSequence] The step sequence
|
700
|
+
# @param viable_steps [Array<Tasker::WorkflowStep>] Steps to execute
|
701
|
+
# @param task_handler [Object] The task handler instance
|
702
|
+
# @return [Array<Tasker::WorkflowStep>] Processed steps
|
703
|
+
def execute_steps_concurrently(task, sequence, viable_steps, task_handler)
|
704
|
+
results = []
|
705
|
+
current_max_concurrency = max_concurrent_steps
|
706
|
+
|
707
|
+
viable_steps.each_slice(current_max_concurrency) do |step_batch|
|
708
|
+
futures = nil
|
709
|
+
batch_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
710
|
+
|
711
|
+
begin
|
712
|
+
# Create futures with connection pool management
|
713
|
+
futures = step_batch.map do |step|
|
714
|
+
Concurrent::Future.execute do
|
715
|
+
# Ensure each future has its own database connection
|
716
|
+
ActiveRecord::Base.connection_pool.with_connection do
|
717
|
+
execute_single_step(task, sequence, step, task_handler)
|
718
|
+
end
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
# Enhanced timeout protection with graceful degradation
|
723
|
+
batch_results = collect_results_with_timeout(futures, step_batch.size, task.task_id)
|
724
|
+
results.concat(batch_results.compact)
|
725
|
+
rescue Concurrent::TimeoutError
|
726
|
+
log_structured(:warn, 'Batch execution timeout',
|
727
|
+
task_id: task.task_id,
|
728
|
+
batch_size: step_batch.size,
|
729
|
+
timeout_seconds: calculate_batch_timeout(step_batch.size),
|
730
|
+
correlation_id: current_correlation_id)
|
731
|
+
|
732
|
+
# Graceful degradation: collect any completed results
|
733
|
+
completed_results = collect_completed_results(futures)
|
734
|
+
results.concat(completed_results.compact)
|
735
|
+
rescue StandardError => e
|
736
|
+
log_structured(:error, 'Batch execution error',
|
737
|
+
task_id: task.task_id,
|
738
|
+
batch_size: step_batch.size,
|
739
|
+
error_class: e.class.name,
|
740
|
+
error_message: e.message,
|
741
|
+
correlation_id: current_correlation_id)
|
742
|
+
|
743
|
+
# Attempt to collect any completed results before cleanup
|
744
|
+
completed_results = collect_completed_results(futures) if futures
|
745
|
+
results.concat(completed_results.compact) if completed_results
|
746
|
+
ensure
|
747
|
+
# ENHANCED: Comprehensive memory cleanup with intelligent GC
|
748
|
+
cleanup_futures_with_memory_management(futures, step_batch.size, batch_start_time, task.task_id)
|
749
|
+
end
|
750
|
+
end
|
751
|
+
|
752
|
+
results
|
753
|
+
end
|
754
|
+
|
755
|
+
# Get current correlation ID for logging context
|
756
|
+
#
|
757
|
+
# Ensures we always have a correlation ID for traceability. If the StepExecutor
|
758
|
+
# doesn't have the StructuredLogging concern properly included, this will fail fast
|
759
|
+
# rather than silently returning nil and masking workflow issues.
|
760
|
+
#
|
761
|
+
# @return [String] Current correlation ID (never nil)
|
762
|
+
# @raise [RuntimeError] If StructuredLogging concern is not properly included
|
763
|
+
def current_correlation_id
|
764
|
+
unless respond_to?(:correlation_id, true)
|
765
|
+
raise 'StepExecutor must include StructuredLogging concern for correlation ID support. ' \
|
766
|
+
'This indicates a workflow or initialization issue.'
|
767
|
+
end
|
768
|
+
|
769
|
+
# The StructuredLogging concern automatically generates correlation IDs if none exist
|
770
|
+
# This ensures we always have traceability without masking logical sequencing issues
|
771
|
+
correlation_id
|
772
|
+
end
|
773
|
+
|
774
|
+
# Collect results with configurable timeout protection
|
775
|
+
#
|
776
|
+
# @param futures [Array<Concurrent::Future>] The futures to collect from
|
777
|
+
# @param batch_size [Integer] Size of the batch for timeout calculation
|
778
|
+
# @param task_id [String] Task ID for logging context
|
779
|
+
# @return [Array] Results from completed futures
|
780
|
+
def collect_results_with_timeout(futures, batch_size, task_id)
|
781
|
+
timeout_seconds = calculate_batch_timeout(batch_size)
|
782
|
+
|
783
|
+
log_structured(:debug, 'Collecting batch results with timeout',
|
784
|
+
task_id: task_id,
|
785
|
+
batch_size: batch_size,
|
786
|
+
timeout_seconds: timeout_seconds,
|
787
|
+
correlation_id: current_correlation_id)
|
788
|
+
|
789
|
+
futures.map { |future| future.value(timeout_seconds) }
|
790
|
+
rescue Concurrent::TimeoutError
|
791
|
+
# Log timeout but let the caller handle graceful degradation
|
792
|
+
log_structured(:warn, 'Future collection timeout',
|
793
|
+
task_id: task_id,
|
794
|
+
batch_size: batch_size,
|
795
|
+
timeout_seconds: timeout_seconds,
|
796
|
+
correlation_id: current_correlation_id)
|
797
|
+
raise
|
798
|
+
end
|
799
|
+
|
800
|
+
# Calculate appropriate timeout based on batch size
|
801
|
+
#
|
802
|
+
# @param batch_size [Integer] Number of steps in the batch
|
803
|
+
# @return [Numeric] Timeout in seconds
|
804
|
+
def calculate_batch_timeout(batch_size)
|
805
|
+
# Delegate to execution config for timeout calculation
|
806
|
+
execution_config.calculate_batch_timeout(batch_size)
|
807
|
+
end
|
808
|
+
|
809
|
+
# Collect results from completed futures without waiting
|
810
|
+
#
|
811
|
+
# @param futures [Array<Concurrent::Future>] The futures to check
|
812
|
+
# @return [Array] Results from completed futures only
|
813
|
+
def collect_completed_results(futures)
|
814
|
+
return [] unless futures
|
815
|
+
|
816
|
+
completed_results = []
|
817
|
+
futures.each do |future|
|
818
|
+
if future.complete? && !future.rejected?
|
819
|
+
completed_results << future.value
|
820
|
+
elsif future.rejected?
|
821
|
+
log_structured(:warn, 'Future rejected during collection',
|
822
|
+
reason: future.reason&.message,
|
823
|
+
correlation_id: current_correlation_id)
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
completed_results
|
828
|
+
rescue StandardError => e
|
829
|
+
log_structured(:error, 'Error collecting completed results',
|
830
|
+
error_class: e.class.name,
|
831
|
+
error_message: e.message,
|
832
|
+
correlation_id: current_correlation_id)
|
833
|
+
[]
|
834
|
+
end
|
835
|
+
|
836
|
+
# Comprehensive future cleanup with memory management
|
837
|
+
#
|
838
|
+
# @param futures [Array<Concurrent::Future>] The futures to clean up
|
839
|
+
# @param batch_size [Integer] Size of the processed batch
|
840
|
+
# @param batch_start_time [Float] When the batch started processing
|
841
|
+
# @param task_id [String] Task ID for logging context
|
842
|
+
def cleanup_futures_with_memory_management(futures, batch_size, batch_start_time, task_id)
|
843
|
+
return unless futures
|
844
|
+
|
845
|
+
cleanup_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
846
|
+
|
847
|
+
begin
|
848
|
+
# Step 1: Cancel any pending futures using domain-specific logic
|
849
|
+
pending_count = 0
|
850
|
+
futures.each do |future|
|
851
|
+
analyzer = FutureStateAnalyzer.new(future)
|
852
|
+
if analyzer.should_cancel?
|
853
|
+
future.cancel
|
854
|
+
pending_count += 1
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
# Step 2: Wait briefly for executing futures to complete gracefully
|
859
|
+
executing_count = 0
|
860
|
+
futures.each do |future|
|
861
|
+
analyzer = FutureStateAnalyzer.new(future)
|
862
|
+
if analyzer.should_wait_for_completion?
|
863
|
+
future.wait(execution_config.future_cleanup_wait_seconds)
|
864
|
+
executing_count += 1
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
# Step 3: Clear future references to prevent memory leaks
|
869
|
+
futures.clear
|
870
|
+
|
871
|
+
# Step 4: Intelligent GC trigger for memory pressure relief
|
872
|
+
trigger_intelligent_gc(batch_size, task_id) if should_trigger_gc?(batch_size, batch_start_time)
|
873
|
+
|
874
|
+
# Log cleanup metrics for observability
|
875
|
+
cleanup_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - cleanup_start_time
|
876
|
+
log_structured(:debug, 'Future cleanup completed',
|
877
|
+
task_id: task_id,
|
878
|
+
batch_size: batch_size,
|
879
|
+
pending_cancelled: pending_count,
|
880
|
+
executing_waited: executing_count,
|
881
|
+
cleanup_duration_ms: (cleanup_duration * 1000).round(2),
|
882
|
+
gc_triggered: should_trigger_gc?(batch_size, batch_start_time),
|
883
|
+
correlation_id: current_correlation_id)
|
884
|
+
rescue StandardError => e
|
885
|
+
log_structured(:error, 'Error during future cleanup',
|
886
|
+
task_id: task_id,
|
887
|
+
batch_size: batch_size,
|
888
|
+
error_class: e.class.name,
|
889
|
+
error_message: e.message,
|
890
|
+
correlation_id: current_correlation_id)
|
891
|
+
end
|
892
|
+
end
|
893
|
+
|
894
|
+
# Determine if garbage collection should be triggered
|
895
|
+
#
|
896
|
+
# @param batch_size [Integer] Size of the processed batch
|
897
|
+
# @param batch_start_time [Float] When the batch started processing
|
898
|
+
# @return [Boolean] Whether GC should be triggered
|
899
|
+
def should_trigger_gc?(batch_size, batch_start_time)
|
900
|
+
batch_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - batch_start_time
|
901
|
+
|
902
|
+
# Delegate to execution config for GC decision
|
903
|
+
execution_config.should_trigger_gc?(batch_size, batch_duration)
|
904
|
+
end
|
905
|
+
|
906
|
+
# Trigger intelligent garbage collection with logging
|
907
|
+
#
|
908
|
+
# @param batch_size [Integer] Size of the batch that triggered GC
|
909
|
+
# @param task_id [String] Task ID for logging context
|
910
|
+
def trigger_intelligent_gc(batch_size, task_id)
|
911
|
+
gc_start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
912
|
+
|
913
|
+
# Record memory stats before GC
|
914
|
+
memory_before = GC.stat[:heap_live_slots] if GC.respond_to?(:stat)
|
915
|
+
|
916
|
+
# Trigger GC
|
917
|
+
GC.start
|
918
|
+
|
919
|
+
# Record memory stats after GC
|
920
|
+
memory_after = GC.stat[:heap_live_slots] if GC.respond_to?(:stat)
|
921
|
+
gc_duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - gc_start_time
|
922
|
+
|
923
|
+
# Log GC metrics for memory monitoring
|
924
|
+
log_structured(:info, 'Intelligent GC triggered',
|
925
|
+
task_id: task_id,
|
926
|
+
batch_size: batch_size,
|
927
|
+
gc_duration_ms: (gc_duration * 1000).round(2),
|
928
|
+
memory_before: memory_before,
|
929
|
+
memory_after: memory_after,
|
930
|
+
memory_freed: memory_before && memory_after ? (memory_before - memory_after) : nil,
|
931
|
+
correlation_id: current_correlation_id)
|
932
|
+
rescue StandardError => e
|
933
|
+
log_structured(:error, 'Error during intelligent GC',
|
934
|
+
task_id: task_id,
|
935
|
+
error_class: e.class.name,
|
936
|
+
error_message: e.message,
|
937
|
+
correlation_id: current_correlation_id)
|
938
|
+
end
|
939
|
+
end
|
940
|
+
end
|
941
|
+
end
|