Module: Async::Background::Scheduler

Defined in:
lib/async/background/scheduler.rb

Defined Under Namespace

Classes: Unavailable, UnknownScheduler

Constant Summary collapse

ENV_KEY =
'ASYNC_BACKGROUND_SCHEDULER'
THREAD_ENV_KEY =
'ASYNC_BACKGROUND_SCHEDULER_THREAD'
KNOWN =
%i[async itsi].freeze

Class Method Summary collapse

Class Method Details

.available?(feature) ⇒ Boolean

Returns:

  • (Boolean)


61
62
63
64
65
# File 'lib/async/background/scheduler.rb', line 61

def available?(feature)
  !Gem.find_files(feature).empty? || !Gem.find_files("#{feature}.rb").empty?
rescue StandardError
  try_require(feature)
end

.currentObject



19
20
21
22
23
# File 'lib/async/background/scheduler.rb', line 19

def current
  return nil unless installed?

  Fiber.scheduler.class.name
end

.detectObject

Raises:



51
52
53
54
55
56
57
58
59
# File 'lib/async/background/scheduler.rb', line 51

def detect
  return :async if defined?(::Async::Scheduler)
  return :itsi if defined?(::Itsi::Scheduler)

  return :async if available?('async')
  return :itsi if available?('itsi/scheduler')

  raise Unavailable, 'no fiber scheduler available: add `async` or `itsi-scheduler` to your bundle'
end

.installed?Boolean

Returns:

  • (Boolean)


17
# File 'lib/async/background/scheduler.rb', line 17

def installed? = !Fiber.scheduler.nil?

.preload!(kind = nil) ⇒ Object



25
26
27
28
29
30
31
# File 'lib/async/background/scheduler.rb', line 25

def preload!(kind = nil)
  case resolve(kind)
  when :async then require 'async'
  when :itsi then require 'itsi/scheduler'
  end
  true
end

.resolve(kind = nil) ⇒ Object

Raises:



43
44
45
46
47
48
49
# File 'lib/async/background/scheduler.rb', line 43

def resolve(kind = nil)
  name = (kind || ENV.fetch(ENV_KEY, 'auto')).to_s.downcase
  return name.to_sym if KNOWN.include?(name.to_sym)
  return detect if name == 'auto'

  raise UnknownScheduler, "unknown scheduler #{name.inspect}, expected one of: #{KNOWN.join(', ')}, auto"
end

.run(kind = nil, &block) ⇒ Object

Raises:

  • (ArgumentError)


33
34
35
36
37
38
39
40
41
# File 'lib/async/background/scheduler.rb', line 33

def run(kind = nil, &block)
  raise ArgumentError, 'block required' unless block
  return block.call if installed?

  case resolve(kind)
  when :async then run_async(&block)
  when :itsi then run_itsi(&block)
  end
end

.run_async(&block) ⇒ Object



74
75
76
77
78
79
80
# File 'lib/async/background/scheduler.rb', line 74

def run_async(&block)
  require 'async'

  result = nil
  send(:Async) { result = block.call }
  result
end

.run_itsi(&block) ⇒ Object



82
83
84
85
86
87
88
# File 'lib/async/background/scheduler.rb', line 82

def run_itsi(&block)
  require 'itsi/scheduler'

  return run_on_thread(::Itsi::Scheduler, &block) if threaded?

  run_on_current_thread(::Itsi::Scheduler.new, &block)
end

.run_on_current_thread(scheduler, &block) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/async/background/scheduler.rb', line 94

def run_on_current_thread(scheduler, &block)
  result = nil
  failure = nil
  finished = false

  previous = Fiber.scheduler
  Fiber.set_scheduler(scheduler)

  begin
    Fiber.schedule do
      result = block.call
    rescue Exception => e # rubocop:disable Lint/RescueException
      failure = e
    ensure
      finished = true
    end

    scheduler.run if scheduler.respond_to?(:run)
  ensure
    Fiber.set_scheduler(previous)
  end

  raise failure if failure
  unless finished
    raise Unavailable,
          "#{scheduler.class} did not run the scheduled fiber to completion on close; " \
          "set #{THREAD_ENV_KEY}=1 to fall back to a dedicated scheduler thread"
  end

  result
end

.run_on_thread(scheduler_class, &block) ⇒ Object



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/async/background/scheduler.rb', line 126

def run_on_thread(scheduler_class, &block)
  result = nil
  failure = nil

  thread = Thread.new do
    Fiber.set_scheduler(scheduler_class.new)
    Fiber.schedule do
      result = block.call
    rescue Exception => e # rubocop:disable Lint/RescueException
      failure = e
    end
  end
  thread.join
  raise failure if failure

  result
end

.threaded?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/async/background/scheduler.rb', line 90

def threaded?
  %w[1 true yes].include?(ENV.fetch(THREAD_ENV_KEY, '').to_s.downcase)
end

.try_require(feature) ⇒ Object



67
68
69
70
71
72
# File 'lib/async/background/scheduler.rb', line 67

def try_require(feature)
  require feature
  true
rescue LoadError
  false
end