Skip to content

Orchestration#

The primary use case for Barista is to orchestrate work concurrently.
Barista capitalizes on Crystal's syntax and concurrency mechanisms to accomplish this.

The main entrypoint for this is Barista::Orchestrator, (but you can write your own).

Serial tasks#

An way to prevent concurreny among a group of tasks is using Barista::TaskClassMethods#sequence

The sequence class method takes an array of strings.
As that task is executing, if any of the same sequences are encountered in other tasks during a concurrent build, Barista::Orchestrator will block those tasks one-by-one until each task is complete.

Any tasks that do not share the same sequences or have no sequences will continue to execute concurrently.

For instance, perhaps some tasks all mutate the same state, which might cause race conditions. We can avoid this as demonstrated below.

Note

although named sequence, this mechanism offers no guarantees on the order that each task is executed. If order is important, use dependency

class TaskOne < Barista::Task
  sequence ["mutates-shared-state"]

  def execute : Nil
    File.open("/some/shared/state", "w") do |file|
      file.puts "This comes from TaskOne"
    end
  end
end

class TaskTwo < Barista::Task
  sequence ["mutates-shared-state"]

  def execute : Nil
    File.open("/some/shared/state", "w") do |file|
      file.puts "This comes from TaskTwo"
    end
  end
end

class NonSequencedTask < Barista::Task
  def execute : Nil
    puts "No sequences here!"
  end
end

Even though TaskOne and TaskTwo are independent, only one will run at a time. NonSequencedTask will continue to run concurrently to the serial group.

Orchestration Events#

As Barista::Orchestrator executes the graph of dependencies, it will emit events.

The list of events can be found in Barista::OrchestrationEvents.

We can subscribe to events as follows.

orchestrator = Barista::Orchestrator(Barista::Task).new(some_registry)

orchestrator.on_run_start do
  puts "Starting to execute the tasks"
end

orchestrator.on_run_finsihed do
  puts "The tasks are all finished!"
end

orchestrator.on_unblocked do |orchestration_info|
  puts "Lifecycle Information: #{orchestration_info.to_s}"
end

orchestrator.on_task_start do |task|
  puts "Task #{task} is starting"
end

orchestrator.on_task_failed do |task, exception_string|
  puts "Task #{task} failed with exception: #{exception_string}"
end

orchestrator.on_task_succeed do |task|
  puts "Task #{task} succeeed"
end

orchestrator.on_task_finished do |task|
  puts "Task succeeded or failed"
end

orchestrator.execute