Class: Ibex::Watch::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/watch/runner.rb,
sig/ibex/watch/runner.rbs

Overview

Polls source fingerprints, debounces changes, and publishes stable builds.

Constant Summary collapse

DEFAULT_INTERVAL =

Signature:

  • Float

Returns:

  • (Float)
0.25
DEFAULT_DEBOUNCE =

Signature:

  • Float

Returns:

  • (Float)
0.05

Instance Method Summary collapse

Constructor Details

#initialize(paths:, build:, publish:, failure_paths:, stderr:, clock:, sleeper:, iteration_hook:, interval: DEFAULT_INTERVAL, debounce: DEFAULT_DEBOUNCE) ⇒ Runner

rubocop:disable Layout/LineLength

RBS:

  • (paths: Array[String], build: ^() -> untyped, publish: ^(untyped, SourceSnapshot, ^() -> bool) -> void, failure_paths: ^() -> Array[String], stderr: untyped, clock: ^() -> Float, sleeper: ^(Float) -> void, iteration_hook: ^(Symbol, Integer, Array[String]) -> (Integer | Symbol | nil), ?interval: Float, ?debounce: Float) -> void

Parameters:

  • paths: (Array[String])
  • build: (^() -> untyped)
  • publish: (^(untyped, SourceSnapshot, ^() -> bool) -> void)
  • failure_paths: (^() -> Array[String])
  • stderr: (Object)
  • clock: (^() -> Float)
  • sleeper: (^(Float) -> void)
  • iteration_hook: (^(Symbol, Integer, Array[String]) -> (Integer | Symbol | nil))
  • interval: (Float) (defaults to: DEFAULT_INTERVAL)
  • debounce: (Float) (defaults to: DEFAULT_DEBOUNCE)


13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/ibex/watch/runner.rb', line 13

def initialize(paths:, build:, publish:, failure_paths:, stderr:, clock:, sleeper:, iteration_hook:,
               interval: DEFAULT_INTERVAL, debounce: DEFAULT_DEBOUNCE)
  @fixed_paths = normalize_paths(paths)
  @successful_paths = [] #: Array[String]
  @paths = @fixed_paths
  @build = build
  @publish = publish
  @failure_paths = failure_paths
  @stderr = stderr
  @clock = clock
  @sleeper = sleeper
  @iteration_hook = iteration_hook
  @interval = interval
  @debounce = debounce
  @signal_status = nil #: Integer?
  @requested_status = nil #: Integer?
  @last_failure_message = nil #: String?
  @last_failure_snapshot = nil #: SourceSnapshot?
  @iteration = 0
end

Instance Method Details

#build_once(before) ⇒ [ SourceSnapshot, bool ]

RBS:

  • (SourceSnapshot before) -> [SourceSnapshot, bool]

Parameters:

Returns:



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ibex/watch/runner.rb', line 65

def build_once(before)
  @iteration += 1
  result = @build.call
  @paths = normalize_paths(@fixed_paths + result.source_paths + result.attempted_paths)
  rendered = SourceSnapshot.new(@paths)
  return [rendered, true] unless rendered.unchanged_since?(before)
  return [rendered, false] if @signal_status

  @publish.call(result, rendered, -> { @signal_status.nil? })
  @last_failure_message = nil
  @last_failure_snapshot = nil
  @successful_paths = @paths - @fixed_paths
  published = SourceSnapshot.new(@paths)
  [published, published != rendered]
rescue GenerationTransaction::SourceChanged
  [SourceSnapshot.new(@paths), true]
rescue GenerationTransaction::Error => e
  raise if e.rollback_failed

  failed_build(before, e)
rescue Ibex::Error, SystemCallError, SystemStackError => e
  failed_build(before, e)
end

#debounce(changed) ⇒ SourceSnapshot

RBS:

  • (SourceSnapshot changed) -> SourceSnapshot

Parameters:

Returns:



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/ibex/watch/runner.rb', line 106

def debounce(changed)
  loop do
    @clock.call
    @sleeper.call(@debounce)
    return changed if @signal_status

    settled = SourceSnapshot.new(@paths)
    return settled if settled == changed

    changed = settled
  end
end

#failed_build(before, error) ⇒ [ SourceSnapshot, bool ]

RBS:

  • (SourceSnapshot before, Exception error) -> [SourceSnapshot, bool]

Parameters:

Returns:



125
126
127
128
129
130
131
132
# File 'lib/ibex/watch/runner.rb', line 125

def failed_build(before, error)
  previous_paths = @paths
  @paths = normalize_paths(@fixed_paths + @successful_paths + @failure_paths.call)
  current = SourceSnapshot.new(@paths)
  retry_build = @paths != previous_paths || !current.unchanged_since?(before)
  report_failure_once(error, current)
  [current, retry_build]
end

#normalize_paths(paths) ⇒ Array[String]

RBS:

  • (Array[String] paths) -> Array[String]

Parameters:

  • paths (Array[String])

Returns:

  • (Array[String])


155
156
157
# File 'lib/ibex/watch/runner.rb', line 155

def normalize_paths(paths)
  paths.map { |path| File.expand_path(path) }.uniq.sort
end

#report_failure(error) ⇒ void

This method returns an undefined value.

RBS:

  • (Exception error) -> void

Parameters:

  • error (Exception)


120
121
122
# File 'lib/ibex/watch/runner.rb', line 120

def report_failure(error)
  @stderr.puts(error.message)
end

#report_failure_once(error, snapshot) ⇒ void

This method returns an undefined value.

RBS:

  • (Exception error, SourceSnapshot snapshot) -> void

Parameters:



135
136
137
138
139
140
# File 'lib/ibex/watch/runner.rb', line 135

def report_failure_once(error, snapshot)
  duplicate = @last_failure_message == error.message && @last_failure_snapshot == snapshot
  report_failure(error) unless duplicate
  @last_failure_message = error.message
  @last_failure_snapshot = snapshot
end

#runInteger

RBS:

  • () -> Integer

Returns:

  • (Integer)


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
# File 'lib/ibex/watch/runner.rb', line 36

def run
  with_signal_handlers do
    snapshot = SourceSnapshot.new(@paths)
    loop do
      status = stop_status(:before_build)
      return status if status

      snapshot, retry_build = build_once(snapshot)
      status = stop_status(:after_build)
      return status if status

      if retry_build
        snapshot = debounce(snapshot)
        status = @signal_status || @requested_status
        return status if status

        next
      end

      snapshot = wait_for_change(snapshot)
      status = @signal_status || @requested_status
      return status if status
    end
  end
end

#stop_status(event) ⇒ Integer?

RBS:

  • (Symbol event) -> Integer?

Parameters:

  • event (Symbol)

Returns:

  • (Integer, nil)


143
144
145
146
147
148
149
150
151
152
# File 'lib/ibex/watch/runner.rb', line 143

def stop_status(event)
  return @signal_status if @signal_status

  result = @iteration_hook.call(event, @iteration, @paths.dup)
  @requested_status = result if result.is_a?(Integer)
  @requested_status = 0 if result == :stop
  return @requested_status if @requested_status

  nil
end

#wait_for_change(snapshot) ⇒ SourceSnapshot

RBS:

  • (SourceSnapshot snapshot) -> SourceSnapshot

Parameters:

Returns:



90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/ibex/watch/runner.rb', line 90

def wait_for_change(snapshot)
  loop do
    @sleeper.call(@interval)
    return snapshot if @signal_status

    status = stop_status(:poll)
    return snapshot if status

    changed = SourceSnapshot.new(@paths)
    next if changed == snapshot

    return debounce(changed)
  end
end

#with_signal_handlers { ... } ⇒ Integer

RBS:

  • () { () -> Integer } -> Integer

Yields:

Yield Returns:

  • (Integer)

Returns:

  • (Integer)


160
161
162
163
164
165
166
167
168
169
# File 'lib/ibex/watch/runner.rb', line 160

def with_signal_handlers
  previous = {} #: Hash[String, untyped]
  if Thread.current == Thread.main
    previous["INT"] = Signal.trap("INT") { @signal_status = 130 }
    previous["TERM"] = Signal.trap("TERM") { @signal_status = 143 }
  end
  yield
ensure
  previous&.each { |signal, handler| Signal.trap(signal, handler) }
end