Class: Roby::Schedulers::Global
- Extended by:
- Logger::Forward, Logger::Hierarchy
- Includes:
- Logger::Forward, Logger::Hierarchy
- Defined in:
- lib/roby/schedulers/global.rb
Overview
Scheduler that handles temporal, scheduling, dependency and planning relations in a global graph-based resolution
As all schedulers, its only entry point is #initial_events. The rest must be considered private API
It bases scheduling decisions on temporal constraints and on the scheduling constraint graph. It interprets some core Roby relations as schedule_as constraints:
- children of the depends_on relation should be scheduled as their parents
- children of the planned_by relation should be scheduled as their parents
The main difference between the two is the handling of non-executable tasks. In the case of the dependency relation, the child will not be schedulable if the parent is non-executable. It is obviously not the case for the planned_by relation
The main idea of the algorithm is to resolve the "scheduling groups" and their relationships, that is the set of tasks that have to be scheduled together because they form a connected component in the schedule_as relation. These groups then are organized in a graph where an edge represents that all tasks of group have to wait for all the tasks of the other. We then reason only on these groups
The constraints is "relaxed" in the presence of temporal constraints: if a child must be started before its parent, the scheduler will start the child first, but only if the parent would be scheduled assuming other temporal constraints are met. This allows to add cross-constraints on scheduling and trust the scheduler to relax them when temporal constraints need it
It is a global resolution scheduler. That is, it will simultaneously start all tasks in the graph that can be started at the same time. If a parent task needs to be started first (the behaviour of Basic and Temporal), add an explicit temporal relation via the Task#should_start_after helper
Defined Under Namespace
Classes: InternalError, RelaxState, RelaxationGraph, SchedulingGroup, SchedulingGroupsGraph, TemporalConstraintResult
Constant Summary collapse
- STATE_UNDECIDED =
nil- STATE_SCHEDULABLE =
:schedulable- STATE_NON_SCHEDULABLE =
:non_schedulable- STATE_PENDING_CONSTRAINTS =
:pending_constraints- STATES_COMPATIBLE_WITH_RELAXATION =
[STATE_SCHEDULABLE, STATE_PENDING_CONSTRAINTS].freeze
Instance Attribute Summary collapse
-
#plan ⇒ Object
readonly
Returns the value of attribute plan.
Instance Method Summary collapse
- #compute_tasks_to_schedule(time: Time.now) ⇒ Object
-
#create_scheduled_as_graph(candidates) ⇒ Object
Create a graph where u->v indicates that
vshould be schedulable foruto be schedulable (u.schedule_as(v)). -
#create_scheduling_group_graph(scheduled_as) ⇒ SchedulingGroupsGraph
Create the condensed graph of the given scheduling graph, where vertices are instances of SchedulingGroup.
-
#debug_output_group_temporal_constraints(candidates, group) ⇒ Object
Debug output helper for #scheduling_state_resolve_temporal_constraints.
- #debug_output_scheduled_tasks(result) ⇒ Object
- #debug_relaxation_show_holding_groups(group, holding_groups) ⇒ Object
-
#debug_relaxed_group(related) ⇒ Object
private
Emit the debug messages for #relax_scheduling_constraints when a group state was relaxed from PENDING_CONSTRAINTS to SCHEDULABLE.
-
#debug_relaxed_group_failure(related) ⇒ Object
private
Emit the debug messages for #relax_scheduling_constraints when a group state could not be relaxed.
-
#group_has_external_temporal_constraints?(candidates, group) ⇒ Boolean
Test whether some of the failed temporal constraints depend on tasks that are beyond the reach of the scheduler.
-
#initial_events(time: Time.now) ⇒ Object
Starts all tasks that are eligible.
-
#initialize(plan = Roby.plan) ⇒ Global
constructor
A new instance of Global.
-
#non_executable_resolution_tasks(non_executable_task) ⇒ Object
Resolve the set of tasks that can turn a non-executable task into an executable one.
-
#propagate_scheduling_next_steps(graph, group) ⇒ Array<SchedulingGroup>
Return the groups that can be added to the processing queue of #propagate_scheduling_state main loop because of the finished processing of the given group.
-
#propagate_scheduling_ready?(graph, group) ⇒ Boolean
Tests whether the given group can be processed by the propagate_scheduling_state main loop.
-
#propagate_scheduling_state(candidates, graph, time) ⇒ Object
Follow the scheduling constraints to add holding constraints to the groups that are scheduled as the held group.
-
#relax_group_scheduling_constraints(group, scheduling_groups, relaxation_graph) ⇒ Object
Perform relaxation on a single group which is in STATE_PENDING_CONSTRAINTS.
-
#relax_scheduling_constraints(scheduling_groups) ⇒ Object
Look for recursive scheduling constraints - temporal or about non-executable tasks - and check if we can resolve them by allowing some tasks to be executed.
-
#relaxation_add_groups(relaxation_graph, ref, groups) ⇒ Object
Helper for #relaxation_create_graph.
- #relaxation_add_temporal_constraints_to_dependent_groups(dependent_groups, holding_group, scheduling_groups) ⇒ Object
- #relaxation_compute_dependent_groups(scheduling_groups, holding_group) ⇒ Object
-
#relaxation_compute_related_groups(scheduling_groups, relaxation_graph, seed_group) ⇒ Object
Compute the set of groups that need to be resolved together to allow for their (collective) scheduling.
- #relaxation_compute_single_group_relations(group, seen_holding_groups, scheduling_groups, relaxation_graph) ⇒ Object
-
#relaxation_create_graph(scheduling_groups) ⇒ Object
Create a graph on which #relax_scheduling_constraints will work.
- #relaxation_resolve_non_executable_tasks(dependent_groups, holding_group, scheduling_groups) ⇒ Object
- #report_group_non_executable(group) ⇒ Object
- #report_group_non_relaxable_pending_constraints(group) ⇒ Object
-
#resolve_holding_groups(root_group) ⇒ Object
Compute the set of groups that this group (recursively) depends on.
-
#resolve_scheduling_constraints(graph, group) ⇒ Object
Register the reasons why a group is held by scheduling constraints.
- #resolve_task_temporal_constraints(result, task) ⇒ Object
-
#resolve_tasks_to_schedule(scheduling_groups, time) ⇒ Object
Return the set of tasks that can be started.
-
#resolve_temporal_constraints(group, time) ⇒ TemporalConstraintResult
Register all temporal constraints failures within a group.
- #scheduling_graph_add_dependency(graph) ⇒ Object
- #scheduling_graph_add_edges(graph) ⇒ Object
- #scheduling_graph_add_planned_by(graph) ⇒ Object
- #scheduling_graph_add_scheduling_constraints(graph) ⇒ Object
- #scheduling_state_resolve_can_schedule_and_execute_group(group) ⇒ Object
- #scheduling_state_resolve_temporal_constraints(candidates, group, time) ⇒ Object
-
#state ⇒ Object
One of the STATE constants.
- #task_can_execute?(task) ⇒ Boolean
-
#task_can_schedule?(task) ⇒ Boolean
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity.
-
#tasks ⇒ Array<Task>
The list of tasks in the group.
-
#temporal_constraints ⇒ Array<Task>
A list of tasks from the group that are parents in a temporal constraint whose child is also in the group.
- #validate_scheduling_state_propagation(graph) ⇒ Object
Methods inherited from Reporting
#report_action, #report_holdoff, #report_pending_non_executable_task, #report_trigger
Constructor Details
Instance Attribute Details
#plan ⇒ Object (readonly)
Returns the value of attribute plan.
51 52 53 |
# File 'lib/roby/schedulers/global.rb', line 51 def plan @plan end |
Instance Method Details
#compute_tasks_to_schedule(time: Time.now) ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/roby/schedulers/global.rb', line 74 def compute_tasks_to_schedule(time: Time.now) candidates = @plan.find_tasks.pending.self_owned.to_a return [] if candidates.empty? scheduled_as = create_scheduled_as_graph(candidates) scheduling_groups = create_scheduling_group_graph(scheduled_as) Roby.log_pp(scheduling_groups, logger, :debug) propagate_scheduling_state(candidates, scheduling_groups, time) validate_scheduling_state_propagation(scheduling_groups) relax_scheduling_constraints(scheduling_groups) result = resolve_tasks_to_schedule(scheduling_groups, time) debug_output_scheduled_tasks(result) result end |
#create_scheduled_as_graph(candidates) ⇒ Object
Create a graph where u->v indicates that v should be schedulable for u
to be schedulable (u.schedule_as(v))
624 625 626 627 628 629 630 631 632 633 634 |
# File 'lib/roby/schedulers/global.rb', line 624 def create_scheduled_as_graph(candidates) graph = Relations::BidirectionalDirectedAdjacencyGraph.new candidates.each { |v| graph.add_vertex(v) } scheduling_graph_add_edges(graph) graph.delete_vertex_if do |v| !candidates.include?(v) end graph end |
#create_scheduling_group_graph(scheduled_as) ⇒ SchedulingGroupsGraph
Create the condensed graph of the given scheduling graph, where vertices are instances of SchedulingGroup
In the scheduling graph, an edge a->b means that 'a' is scheduled_as 'b'
674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 |
# File 'lib/roby/schedulers/global.rb', line 674 def create_scheduling_group_graph(scheduled_as) condensed = scheduled_as.condensation_graph graph = SchedulingGroupsGraph.new set_id_to_group = {} set_id_to_group.compare_by_identity condensed.each_vertex do |task_set| group = SchedulingGroup.for_tasks( tasks: task_set, id: set_id_to_group.size ) set_id_to_group[task_set] = group graph.add_vertex(group) end condensed.each_edge do |u, v| graph.add_edge(set_id_to_group[u], set_id_to_group[v]) end graph end |
#debug_output_group_temporal_constraints(candidates, group) ⇒ Object
Debug output helper for #scheduling_state_resolve_temporal_constraints
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'lib/roby/schedulers/global.rb', line 200 def debug_output_group_temporal_constraints(candidates, group) return unless Roby.log_level_enabled?(self, :debug) unless group.external_temporal_constraints debug "#{group.id}: has temporal constraints" return end debug "#{group.id}: has external temporal constraints" tasks = group.temporal_constraints. .find_all { |task| !candidates.include?(task) } tasks.each do |t| debug " #{t}" end end |
#debug_output_scheduled_tasks(result) ⇒ Object
91 92 93 94 95 96 97 98 99 |
# File 'lib/roby/schedulers/global.rb', line 91 def debug_output_scheduled_tasks(result) debug do debug "scheduling #{result.size} tasks" result.each do |t| debug " #{t}" end nil end end |
#debug_relaxation_show_holding_groups(group, holding_groups) ⇒ Object
424 425 426 427 428 429 430 431 |
# File 'lib/roby/schedulers/global.rb', line 424 def debug_relaxation_show_holding_groups(group, holding_groups) debug do groups_to_s = holding_groups.map(&:id).sort.map(&:to_s).join(", ") debug " group #{group.id} held by #{holding_groups.size} groups, " \ "trying to relax: #{groups_to_s}" nil end end |
#debug_relaxed_group(related) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Emit the debug messages for #relax_scheduling_constraints when a group state was relaxed from PENDING_CONSTRAINTS to SCHEDULABLE
293 294 295 296 297 298 299 300 |
# File 'lib/roby/schedulers/global.rb', line 293 def debug_relaxed_group() return unless Roby.log_level_enabled?(self, :debug) groups_to_s = .map(&:id).sort.map(&:to_s).join(", ") debug " found #{.size} related groups in " \ "SCHEDULABLE or PENDING_CONSTRAINTS state, " \ "relaxing: #{groups_to_s}" end |
#debug_relaxed_group_failure(related) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Emit the debug messages for #relax_scheduling_constraints when a group state could not be relaxed
306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/roby/schedulers/global.rb', line 306 def debug_relaxed_group_failure() return unless Roby.log_level_enabled?(self, :debug) missing = .find_all { |g| !g.state_compatible_with_relaxation? } groups_to_s = .map(&:id).sort.map(&:to_s).join(", ") missing_to_s = missing.sort_by(&:id).map { |g| "#{g.id}[#{g.state}]" } debug " found #{.size} related groups " \ "(#{groups_to_s}) but #{missing.size} are not " \ "in either SCHEDULABLE or PENDING_CONSTRAINTS " \ "states, leaving state as-is: #{missing_to_s}" end |
#group_has_external_temporal_constraints?(candidates, group) ⇒ Boolean
Test whether some of the failed temporal constraints depend on tasks that are beyond the reach of the scheduler
218 219 220 221 222 |
# File 'lib/roby/schedulers/global.rb', line 218 def group_has_external_temporal_constraints?(candidates, group) group.temporal_constraints..any? do |task| !candidates.include?(task) end end |
#initial_events(time: Time.now) ⇒ Object
Starts all tasks that are eligible. See the documentation of the Basic class for an in-depth description
69 70 71 72 |
# File 'lib/roby/schedulers/global.rb', line 69 def initial_events(time: Time.now) compute_tasks_to_schedule(time: time) .each(&:start!) end |
#non_executable_resolution_tasks(non_executable_task) ⇒ Object
Resolve the set of tasks that can turn a non-executable task into an executable one
473 474 475 476 |
# File 'lib/roby/schedulers/global.rb', line 473 def non_executable_resolution_tasks(non_executable_task) @plan.task_relation_graph_for(TaskStructure::PlannedBy) .out_neighbours(non_executable_task) end |
#propagate_scheduling_next_steps(graph, group) ⇒ Array<SchedulingGroup>
Return the groups that can be added to the processing queue of #propagate_scheduling_state main loop because of the finished processing of the given group
165 166 167 168 169 |
# File 'lib/roby/schedulers/global.rb', line 165 def propagate_scheduling_next_steps(graph, group) graph.each_in_neighbour(group).find_all do |child| propagate_scheduling_ready?(graph, child) end end |
#propagate_scheduling_ready?(graph, group) ⇒ Boolean
Tests whether the given group can be processed by the propagate_scheduling_state main loop
176 177 178 179 |
# File 'lib/roby/schedulers/global.rb', line 176 def propagate_scheduling_ready?(graph, group) graph.each_out_neighbour(group) .all? { |parent| parent.state != STATE_UNDECIDED } end |
#propagate_scheduling_state(candidates, graph, time) ⇒ Object
Follow the scheduling constraints to add holding constraints to the groups that are scheduled as the held group
At the end of this call:
- all held_by_temporal and held_non_schedulable of a group are added to the groups that should be scheduled the same way.
- the 'state' reflects the worst-case (non_schedulable > scheduling > temporal)
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/roby/schedulers/global.rb', line 138 def propagate_scheduling_state(candidates, graph, time) queue = graph.each_vertex.find_all { |v| graph.leaf?(v) } # Do a BFS by hand, queueing vertices only when all scheduling parents # have been resolved until queue.empty? group = queue.shift resolve_scheduling_constraints(graph, group) scheduling_state_resolve_can_schedule_and_execute_group(group) scheduling_state_resolve_temporal_constraints(candidates, group, time) group.state = log_nest(2) do group.resolve_state(logger: self) end debug "#{group.id}: in state #{group.state}" queue.concat(propagate_scheduling_next_steps(graph, group)) end end |
#relax_group_scheduling_constraints(group, scheduling_groups, relaxation_graph) ⇒ Object
Perform relaxation on a single group which is in STATE_PENDING_CONSTRAINTS
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'lib/roby/schedulers/global.rb', line 262 def relax_group_scheduling_constraints( group, scheduling_groups, relaxation_graph ) debug "relaxing scheduling constraints on group #{group.id}" = ( scheduling_groups, relaxation_graph, group ) unless debug " failed, marking #{group.id} as non-schedulable" # In the negative, we can't infer anything about other # groups ... need to re-process them group.state = STATE_NON_SCHEDULABLE report_group_non_relaxable_pending_constraints(group) return end relaxed = .all?(&:state_compatible_with_relaxation?) unless relaxed debug_relaxed_group_failure() return end debug_relaxed_group() .each { |g| g.state = STATE_SCHEDULABLE } end |
#relax_scheduling_constraints(scheduling_groups) ⇒ Object
Look for recursive scheduling constraints - temporal or about non-executable tasks - and check if we can resolve them by allowing some tasks to be executed
In practice, this looks for set of groups that we have to relax together to allow for the scheduling of all of them (or almost)
The 'almost' part has to do with temporal constraints and non-executable tasks. What the relaxation is trying to find is the set of groups that, if we were to allow for the execution of planning tasks and/or temporal prerequisite (i.e. scheduling subsets), would be schedulable.
247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/roby/schedulers/global.rb', line 247 def relax_scheduling_constraints(scheduling_groups) relaxation_graph = relaxation_create_graph(scheduling_groups) scheduling_groups.each_vertex do |group| next unless group.state == STATE_PENDING_CONSTRAINTS relax_group_scheduling_constraints( group, scheduling_groups, relaxation_graph ) end end |
#relaxation_add_groups(relaxation_graph, ref, groups) ⇒ Object
Helper for #relaxation_create_graph
370 371 372 373 374 |
# File 'lib/roby/schedulers/global.rb', line 370 def relaxation_add_groups(relaxation_graph, ref, groups) groups.each do |holding_group| relaxation_graph.add_edge(ref, holding_group) end end |
#relaxation_add_temporal_constraints_to_dependent_groups(dependent_groups, holding_group, scheduling_groups) ⇒ Object
478 479 480 481 482 483 484 485 486 |
# File 'lib/roby/schedulers/global.rb', line 478 def relaxation_add_temporal_constraints_to_dependent_groups( dependent_groups, holding_group, scheduling_groups ) holding_group.temporal_constraints..all? do |task| group = scheduling_groups.find_task_group(task) # If nil, this is not a task we can schedule dependent_groups << group if group end end |
#relaxation_compute_dependent_groups(scheduling_groups, holding_group) ⇒ Object
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 |
# File 'lib/roby/schedulers/global.rb', line 433 def relaxation_compute_dependent_groups(scheduling_groups, holding_group) dependent_groups = Set.new all_planned = relaxation_resolve_non_executable_tasks( dependent_groups, holding_group, scheduling_groups ) unless all_planned debug "could not relax all non-executable tasks" return end all_valid = relaxation_add_temporal_constraints_to_dependent_groups( dependent_groups, holding_group, scheduling_groups ) unless all_valid debug "could not relax all temporal constraints" return end dependent_groups end |
#relaxation_compute_related_groups(scheduling_groups, relaxation_graph, seed_group) ⇒ Object
Compute the set of groups that need to be resolved together to allow for their (collective) scheduling
378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'lib/roby/schedulers/global.rb', line 378 def ( scheduling_groups, relaxation_graph, seed_group ) queue = [seed_group] seen_holding_groups = Set.new = Set.new until queue.empty? g = queue.shift next unless .add?(g) dependent_groups = relaxation_compute_single_group_relations( g, seen_holding_groups, scheduling_groups, relaxation_graph ) unless dependent_groups debug " could not relax" return end dependent_groups.compact.map(&:to_a).each do |groups| queue.concat(groups) end end end |
#relaxation_compute_single_group_relations(group, seen_holding_groups, scheduling_groups, relaxation_graph) ⇒ Object
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 |
# File 'lib/roby/schedulers/global.rb', line 404 def relaxation_compute_single_group_relations( group, seen_holding_groups, scheduling_groups, relaxation_graph ) holding_groups = relaxation_graph.out_neighbours(group) debug_relaxation_show_holding_groups(group, holding_groups) holding_groups.map do |holding_g| next unless seen_holding_groups.add?(holding_g) dependent_groups = log_nest(2) do relaxation_compute_dependent_groups( scheduling_groups, holding_g ) end break unless dependent_groups dependent_groups end end |
#relaxation_create_graph(scheduling_groups) ⇒ Object
Create a graph on which #relax_scheduling_constraints will work
This graph represents the 'live' scheduling constraints, that is an edge
a->b means that a has a schedule_as constraint on b and b is
currently not directly schedulable. This is a transitive relation, that is
the out edges of a given group represent all the known constraints
354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/roby/schedulers/global.rb', line 354 def relaxation_create_graph(scheduling_groups) relaxation_graph = RelaxationGraph.new scheduling_groups.each_vertex do |group| next unless group.state == STATE_PENDING_CONSTRAINTS relaxation_add_groups( relaxation_graph, group, group.held_by_temporal ) relaxation_add_groups( relaxation_graph, group, group.held_non_executable ) end relaxation_graph end |
#relaxation_resolve_non_executable_tasks(dependent_groups, holding_group, scheduling_groups) ⇒ Object
455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 |
# File 'lib/roby/schedulers/global.rb', line 455 def relaxation_resolve_non_executable_tasks( dependent_groups, holding_group, scheduling_groups ) holding_group.non_executable_tasks.all? do |non_executable_task| resolution_groups = non_executable_resolution_tasks(non_executable_task).map do |t| scheduling_groups.find_planning_task_group(holding_group, t) end resolution_groups = resolution_groups.compact unless resolution_groups.empty? dependent_groups.merge(resolution_groups) end end end |
#report_group_non_executable(group) ⇒ Object
116 117 118 119 120 |
# File 'lib/roby/schedulers/global.rb', line 116 def report_group_non_executable(group) group.tasks.each do |task| report_holdoff "non executable tasks in scheduling group", task end end |
#report_group_non_relaxable_pending_constraints(group) ⇒ Object
122 123 124 125 126 127 128 |
# File 'lib/roby/schedulers/global.rb', line 122 def report_group_non_relaxable_pending_constraints(group) group.tasks.each do |task| report_holdoff( "scheduling group has non-relaxable pending constraints", task ) end end |
#resolve_holding_groups(root_group) ⇒ Object
Compute the set of groups that this group (recursively) depends on
This is the actual set that #relax_scheduling_constraints is trying to solve
492 493 494 495 496 497 498 499 500 501 502 503 504 |
# File 'lib/roby/schedulers/global.rb', line 492 def resolve_holding_groups(root_group) result = Set.new queue = [root_group] until queue.empty? group = queue.shift next unless result.add?(group) queue.concat( (group.held_by_temporal | group.held_non_executable).to_a ) end result end |
#resolve_scheduling_constraints(graph, group) ⇒ Object
Register the reasons why a group is held by scheduling constraints
507 508 509 510 511 512 513 514 515 516 517 518 519 |
# File 'lib/roby/schedulers/global.rb', line 507 def resolve_scheduling_constraints(graph, group) graph.each_out_neighbour(group) do |scheduled_as_group| group.held_by_temporal.merge( scheduled_as_group.held_by_temporal ) group.held_non_schedulable.merge( scheduled_as_group.held_non_schedulable ) group.held_non_executable.merge( scheduled_as_group.held_non_executable ) end end |
#resolve_task_temporal_constraints(result, task) ⇒ Object
535 536 537 538 539 540 541 542 |
# File 'lib/roby/schedulers/global.rb', line 535 def resolve_task_temporal_constraints(result, task) start_event = task.start_event result.failed_temporal[task] = start_event.each_failed_temporal_constraint(time).to_a result.failed_occurence[task] = start_event .each_failed_occurence_constraint(use_last_event: true).to_a end |
#resolve_tasks_to_schedule(scheduling_groups, time) ⇒ Object
Return the set of tasks that can be started
102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/roby/schedulers/global.rb', line 102 def resolve_tasks_to_schedule(scheduling_groups, time) scheduling_groups.each_vertex.each_with_object(Set.new) do |group, set| next unless group.state == STATE_SCHEDULABLE debug "#{group.id}: #{group.non_executable_tasks.size}" unless group.non_executable_tasks.empty? report_group_non_executable(group) next end group.resolve_tasks_to_schedule(set, time) end end |
#resolve_temporal_constraints(group, time) ⇒ TemporalConstraintResult
Register all temporal constraints failures within a group
524 525 526 527 528 529 530 531 532 533 |
# File 'lib/roby/schedulers/global.rb', line 524 def resolve_temporal_constraints(group, time) result = TemporalConstraintResult.new( failed_temporal: {}, failed_occurence: {} ) group.each do |task| result.add_from_task(task, time) end result end |
#scheduling_graph_add_dependency(graph) ⇒ Object
654 655 656 657 658 659 |
# File 'lib/roby/schedulers/global.rb', line 654 def scheduling_graph_add_dependency(graph) graph.merge( @plan.task_relation_graph_for(TaskStructure::Dependency) .reverse ) end |
#scheduling_graph_add_edges(graph) ⇒ Object
636 637 638 639 640 |
# File 'lib/roby/schedulers/global.rb', line 636 def scheduling_graph_add_edges(graph) scheduling_graph_add_scheduling_constraints(graph) scheduling_graph_add_dependency(graph) scheduling_graph_add_planned_by(graph) end |
#scheduling_graph_add_planned_by(graph) ⇒ Object
661 662 663 664 665 666 |
# File 'lib/roby/schedulers/global.rb', line 661 def scheduling_graph_add_planned_by(graph) graph.merge( @plan.task_relation_graph_for(TaskStructure::PlannedBy) .reverse ) end |
#scheduling_graph_add_scheduling_constraints(graph) ⇒ Object
642 643 644 645 646 647 648 649 650 651 652 |
# File 'lib/roby/schedulers/global.rb', line 642 def scheduling_graph_add_scheduling_constraints(graph) scheduled_as = @plan.event_relation_graph_for(EventStructure::SchedulingConstraints) scheduled_as.each_edge do |u, v| next unless u.respond_to?(:task) && v.respond_to?(:task) next unless u.symbol == :start && v.symbol == :start graph.add_edge(v.task, u.task) end end |
#scheduling_state_resolve_can_schedule_and_execute_group(group) ⇒ Object
181 182 183 184 185 186 |
# File 'lib/roby/schedulers/global.rb', line 181 def scheduling_state_resolve_can_schedule_and_execute_group(group) group.can_schedule = group.tasks.all? { |t| task_can_schedule?(t) } group.tasks.each do |t| group.non_executable_tasks << t unless task_can_execute?(t) end end |
#scheduling_state_resolve_temporal_constraints(candidates, group, time) ⇒ Object
188 189 190 191 192 193 194 195 196 197 |
# File 'lib/roby/schedulers/global.rb', line 188 def scheduling_state_resolve_temporal_constraints(candidates, group, time) group.temporal_constraints = resolve_temporal_constraints(group, time) return if group.temporal_constraints.ok? group.held_by_temporal << group group.external_temporal_constraints = group_has_external_temporal_constraints?(candidates, group) debug_output_group_temporal_constraints(candidates, group) end |
#state ⇒ Object
Returns one of the STATE constants.
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 |
# File 'lib/roby/schedulers/global.rb', line 743 SchedulingGroup = Struct.new( :tasks, :temporal_constraints, :external_temporal_constraints, :can_schedule, :non_executable_tasks, :held_by_temporal, :held_non_schedulable, :held_non_executable, :state, :id, keyword_init: true ) do def self.for_tasks(tasks:, id:) SchedulingGroup.new( tasks: tasks, id: id, held_by_temporal: Set.new, held_non_schedulable: Set.new, held_non_executable: Set.new, non_executable_tasks: Set.new ) end def each(&block) tasks.each(&block) end def scheduling_constraint_state(logger: nil) if !held_non_schedulable.empty? logger&.debug "#{id} is held by non-schedulable groups" STATE_NON_SCHEDULABLE elsif !held_non_executable.empty? logger&.debug "#{id} is held by non-executable groups" STATE_PENDING_CONSTRAINTS elsif !held_by_temporal.empty? logger&.debug "#{id} is held by temporal constraints within " \ "the scheduling groups" STATE_PENDING_CONSTRAINTS end end def resolve_state(logger: nil) state = nil debug_output_resolve_state(logger) if logger unless non_executable_tasks.empty? held_non_executable << self state = STATE_PENDING_CONSTRAINTS end if !can_schedule held_non_schedulable << self state = STATE_NON_SCHEDULABLE elsif external_temporal_constraints held_non_schedulable << self state = STATE_NON_SCHEDULABLE else state = scheduling_constraint_state(logger: logger) || state end state || STATE_SCHEDULABLE end def debug_output_resolve_state(logger) return unless Roby.log_level_enabled?(logger, :debug) unless non_executable_tasks.empty? logger.debug "#{id} has non-executable tasks" end logger.debug "#{id} has !can_schedule" unless can_schedule if external_temporal_constraints logger.debug "#{id} has external temporal constraints" end nil end def hash object_id end def ==(other) equal?(other) end def eql?(other) equal?(other) end # Assuming this group can be scheduled, return the tasks that should # be scheduled for it def resolve_tasks_to_schedule(set, time) if held_by_temporal.empty? set.merge(tasks) else held_by_temporal.each do |group| = group.temporal_constraints. = find_all_schedulable(, time) set.merge() end end end def find_all_schedulable(tasks, time) tasks = tasks.find_all(&:executable?) tasks.find_all do |t| start = t.start_event has_failed_temporal = start.each_failed_temporal_constraint(time).any? has_failed_occurence = start.each_failed_occurence_constraint(use_last_event: true) .any? !has_failed_occurence && !has_failed_temporal end end # Tests whether this group is in a state that is compatible with # state relaxation # # @see {Global#relax_group_scheduling_constraints} def state_compatible_with_relaxation? STATES_COMPATIBLE_WITH_RELAXATION.include?(state) end end |
#task_can_execute?(task) ⇒ Boolean
695 696 697 698 699 700 701 702 |
# File 'lib/roby/schedulers/global.rb', line 695 def task_can_execute?(task) unless task.executable? report_pending_non_executable_task("#{task} is not executable", task) return false end true end |
#task_can_schedule?(task) ⇒ Boolean
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 |
# File 'lib/roby/schedulers/global.rb', line 704 def task_can_schedule?(task) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity start_event = task.start_event unless start_event.controlable? report_holdoff "start event not controlable", task return false end if (agent = task.execution_agent) && !agent.ready_event.emitted? report_holdoff "task's execution agent %2 is not ready", task, agent return false end unless start_event.root?(EventStructure::CausalLink) report_holdoff "start event not root in the causal link relation", task return false end task.each_relation do |r| if r.respond_to?(:scheduling?) && !r.scheduling? && !task.root?(r) report_holdoff "not root in %2, which forbids scheduling", task, r return false end end true end |
#tasks ⇒ Array<Task>
Returns the list of tasks in the group.
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 |
# File 'lib/roby/schedulers/global.rb', line 743 SchedulingGroup = Struct.new( :tasks, :temporal_constraints, :external_temporal_constraints, :can_schedule, :non_executable_tasks, :held_by_temporal, :held_non_schedulable, :held_non_executable, :state, :id, keyword_init: true ) do def self.for_tasks(tasks:, id:) SchedulingGroup.new( tasks: tasks, id: id, held_by_temporal: Set.new, held_non_schedulable: Set.new, held_non_executable: Set.new, non_executable_tasks: Set.new ) end def each(&block) tasks.each(&block) end def scheduling_constraint_state(logger: nil) if !held_non_schedulable.empty? logger&.debug "#{id} is held by non-schedulable groups" STATE_NON_SCHEDULABLE elsif !held_non_executable.empty? logger&.debug "#{id} is held by non-executable groups" STATE_PENDING_CONSTRAINTS elsif !held_by_temporal.empty? logger&.debug "#{id} is held by temporal constraints within " \ "the scheduling groups" STATE_PENDING_CONSTRAINTS end end def resolve_state(logger: nil) state = nil debug_output_resolve_state(logger) if logger unless non_executable_tasks.empty? held_non_executable << self state = STATE_PENDING_CONSTRAINTS end if !can_schedule held_non_schedulable << self state = STATE_NON_SCHEDULABLE elsif external_temporal_constraints held_non_schedulable << self state = STATE_NON_SCHEDULABLE else state = scheduling_constraint_state(logger: logger) || state end state || STATE_SCHEDULABLE end def debug_output_resolve_state(logger) return unless Roby.log_level_enabled?(logger, :debug) unless non_executable_tasks.empty? logger.debug "#{id} has non-executable tasks" end logger.debug "#{id} has !can_schedule" unless can_schedule if external_temporal_constraints logger.debug "#{id} has external temporal constraints" end nil end def hash object_id end def ==(other) equal?(other) end def eql?(other) equal?(other) end # Assuming this group can be scheduled, return the tasks that should # be scheduled for it def resolve_tasks_to_schedule(set, time) if held_by_temporal.empty? set.merge(tasks) else held_by_temporal.each do |group| = group.temporal_constraints. = find_all_schedulable(, time) set.merge() end end end def find_all_schedulable(tasks, time) tasks = tasks.find_all(&:executable?) tasks.find_all do |t| start = t.start_event has_failed_temporal = start.each_failed_temporal_constraint(time).any? has_failed_occurence = start.each_failed_occurence_constraint(use_last_event: true) .any? !has_failed_occurence && !has_failed_temporal end end # Tests whether this group is in a state that is compatible with # state relaxation # # @see {Global#relax_group_scheduling_constraints} def state_compatible_with_relaxation? STATES_COMPATIBLE_WITH_RELAXATION.include?(state) end end |
#temporal_constraints ⇒ Array<Task>
Returns a list of tasks from the group that are parents in a temporal constraint whose child is also in the group.
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 |
# File 'lib/roby/schedulers/global.rb', line 743 SchedulingGroup = Struct.new( :tasks, :temporal_constraints, :external_temporal_constraints, :can_schedule, :non_executable_tasks, :held_by_temporal, :held_non_schedulable, :held_non_executable, :state, :id, keyword_init: true ) do def self.for_tasks(tasks:, id:) SchedulingGroup.new( tasks: tasks, id: id, held_by_temporal: Set.new, held_non_schedulable: Set.new, held_non_executable: Set.new, non_executable_tasks: Set.new ) end def each(&block) tasks.each(&block) end def scheduling_constraint_state(logger: nil) if !held_non_schedulable.empty? logger&.debug "#{id} is held by non-schedulable groups" STATE_NON_SCHEDULABLE elsif !held_non_executable.empty? logger&.debug "#{id} is held by non-executable groups" STATE_PENDING_CONSTRAINTS elsif !held_by_temporal.empty? logger&.debug "#{id} is held by temporal constraints within " \ "the scheduling groups" STATE_PENDING_CONSTRAINTS end end def resolve_state(logger: nil) state = nil debug_output_resolve_state(logger) if logger unless non_executable_tasks.empty? held_non_executable << self state = STATE_PENDING_CONSTRAINTS end if !can_schedule held_non_schedulable << self state = STATE_NON_SCHEDULABLE elsif external_temporal_constraints held_non_schedulable << self state = STATE_NON_SCHEDULABLE else state = scheduling_constraint_state(logger: logger) || state end state || STATE_SCHEDULABLE end def debug_output_resolve_state(logger) return unless Roby.log_level_enabled?(logger, :debug) unless non_executable_tasks.empty? logger.debug "#{id} has non-executable tasks" end logger.debug "#{id} has !can_schedule" unless can_schedule if external_temporal_constraints logger.debug "#{id} has external temporal constraints" end nil end def hash object_id end def ==(other) equal?(other) end def eql?(other) equal?(other) end # Assuming this group can be scheduled, return the tasks that should # be scheduled for it def resolve_tasks_to_schedule(set, time) if held_by_temporal.empty? set.merge(tasks) else held_by_temporal.each do |group| = group.temporal_constraints. = find_all_schedulable(, time) set.merge() end end end def find_all_schedulable(tasks, time) tasks = tasks.find_all(&:executable?) tasks.find_all do |t| start = t.start_event has_failed_temporal = start.each_failed_temporal_constraint(time).any? has_failed_occurence = start.each_failed_occurence_constraint(use_last_event: true) .any? !has_failed_occurence && !has_failed_temporal end end # Tests whether this group is in a state that is compatible with # state relaxation # # @see {Global#relax_group_scheduling_constraints} def state_compatible_with_relaxation? STATES_COMPATIBLE_WITH_RELAXATION.include?(state) end end |
#validate_scheduling_state_propagation(graph) ⇒ Object
224 225 226 227 228 229 230 231 |
# File 'lib/roby/schedulers/global.rb', line 224 def validate_scheduling_state_propagation(graph) graph.each_vertex do |group| if group.state == STATE_UNDECIDED raise InternalError, "group in STATE_UNDECIDED after propagation" end end end |