Class: BuildBatchinator

Inherits:
Object show all
Defined in:
lib/ceedling/build_batchinator.rb

Overview

Ceedling - Test-Centered Build System for C
ThrowTheSwitch.org
Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams
SPDX-License-Identifier: MIT

Instance Method Summary collapse

Instance Method Details

#build_step(msg, heading: true, &block) ⇒ Object

Neaten up a build step with progress message and some scope encapsulation



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/ceedling/build_batchinator.rb', line 17

def build_step(msg, heading: true, &block)
  if heading
    msg = @reportinator.generate_heading( @loginator.decorate( msg, LogLabels::RUN ) )
  else # Progress message
    msg = "\n" + @reportinator.generate_progress( @loginator.decorate( msg, LogLabels::RUN ) )
  end

  @loginator.log( msg )

  yield # Execute build step block
end

#exec(workload:, things:, &block) ⇒ Object

Parallelize work to be done:

- Enqueue things (thread-safe)
- Spin up a number of worker threads within constraints of project file config and amount of work
- Each worker thread consumes one item from queue and runs the block against its details
- When the queue is empty, the worker threads wind down


34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ceedling/build_batchinator.rb', line 34

def exec(workload:, things:, &block)
  workers = 0

  case workload
  when :compile
    workers = @configurator.project_compile_threads
  when :test
    workers = @configurator.project_test_threads
  else
    raise NameError.new("Unrecognized batch workload type: #{workload}")
  end

  # Enqueue all the items the block will execute against
  things.each { |thing| @queue << thing }

  # Choose lesser of max workers or number of things to process & redefine workers
  # (It's neater and more efficient to avoid workers we won't use)
  workers = [workers, things.size].min

  threads = (1..workers).collect do
    thread = Thread.new do
      Thread.handle_interrupt(Exception => :never) do
        begin
          Thread.handle_interrupt(Exception => :immediate) do
            # Run tasks until there are no more enqueued
            loop do
              # pop(true) is non-blocking and raises ThreadError when queue is empty
              yield @queue.pop(true)
            end
          end

        # First, handle thread exceptions (should always be due to empty queue)
        rescue ThreadError => e
          # Typical case: do nothing and allow thread to wind down

          # ThreadError outside scope of expected empty queue condition
          unless e.message.strip.casecmp("queue empty")
            # Shutdown all worker threads
            shutdown_threads(threads) 
            # Raise exception again after intervening
            raise(e)
          end

        # Second, catch every other kind of exception so we can intervene with thread cleanup.
        # Generally speaking, catching Exception is a no-no, but we must in this case.
        # Raise the exception again so that:
        #  1. Calling code knows something bad happened and handles appropriately
        #  2. Ruby runtime can handle most serious problems
        rescue Exception => e
          # Shutdown all worker threads
          shutdown_threads(threads) 
          # Raise exception again after intervening
          raise(e)
        end
      end
    end

    # Hand thread to Enumerable collect() routine
    thread.abort_on_exception = true
    thread
  end

  # Hand worker threads to scheduler / wait for them to finish
  threads.each { |thread| thread.join }
end

#setupObject



12
13
14
# File 'lib/ceedling/build_batchinator.rb', line 12

def setup
  @queue = Queue.new
end