async-background 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -0
  3. data/README.md +36 -15
  4. data/async-background.gemspec +13 -7
  5. data/lib/async/background/job.rb +75 -28
  6. data/lib/async/background/metrics.rb +41 -33
  7. data/lib/async/background/min_heap.rb +16 -12
  8. data/lib/async/background/queue/client.rb +11 -8
  9. data/lib/async/background/queue/options.rb +3 -3
  10. data/lib/async/background/queue/schema.rb +19 -15
  11. data/lib/async/background/queue/socket_notifier.rb +14 -4
  12. data/lib/async/background/queue/socket_waker.rb +139 -56
  13. data/lib/async/background/queue/sql.rb +31 -31
  14. data/lib/async/background/queue/store.rb +44 -45
  15. data/lib/async/background/runner/lifecycle.rb +131 -0
  16. data/lib/async/background/runner/queue_execution.rb +101 -57
  17. data/lib/async/background/runner/schedule.rb +20 -17
  18. data/lib/async/background/runner.rb +98 -77
  19. data/lib/async/background/runtime/notification.rb +42 -0
  20. data/lib/async/background/runtime/semaphore.rb +151 -0
  21. data/lib/async/background/runtime/task.rb +146 -0
  22. data/lib/async/background/runtime/task_group.rb +81 -0
  23. data/lib/async/background/runtime.rb +246 -0
  24. data/lib/async/background/scheduler.rb +145 -0
  25. data/lib/async/background/version.rb +1 -1
  26. data/lib/async/background/web/app.rb +43 -40
  27. data/lib/async/background/web/assets/app.css +174 -0
  28. data/lib/async/background/web/assets/app.js +486 -0
  29. data/lib/async/background/web/assets/index.html +29 -0
  30. data/lib/async/background/web/assets.rb +4 -697
  31. data/lib/async/background/web/auth.rb +3 -3
  32. data/lib/async/background/web/configuration.rb +40 -93
  33. data/lib/async/background/web/cursor.rb +22 -26
  34. data/lib/async/background/web/event_hub.rb +12 -7
  35. data/lib/async/background/web/metrics_reader.rb +28 -36
  36. data/lib/async/background/web/response.rb +39 -51
  37. data/lib/async/background/web/serializer.rb +23 -63
  38. data/lib/async/background/web/snapshot.rb +35 -81
  39. data/lib/async/background/web/sql.rb +63 -69
  40. data/lib/async/background/web/stream.rb +52 -32
  41. data/lib/async/background/web.rb +1 -1
  42. data/lib/async/background.rb +1 -2
  43. metadata +30 -22
@@ -13,6 +13,20 @@ module Async
13
13
  include Clock
14
14
 
15
15
  CacheEntry = Data.define(:value, :created_at)
16
+ RAW_COLUMNS = { 'args' => :args_raw, 'options' => :options_raw }.freeze
17
+
18
+ ROW_KEYS = SQL::EXTRA_COLUMNS.to_h do |status, extra|
19
+ columns = SQL::BASE_COLUMNS + extra
20
+ [status, columns.map { |column| RAW_COLUMNS.fetch(column) { column.to_sym } }.freeze]
21
+ end.freeze
22
+
23
+ PAGING = {
24
+ executing: [SQL::EXECUTING, nil, nil].freeze,
25
+ claimed: [SQL::CLAIMED, nil, nil].freeze,
26
+ done: [SQL::DONE, SQL::DONE_AFTER, %i[finished_at id].freeze].freeze,
27
+ failed: [SQL::FAILED, SQL::FAILED_AFTER, %i[finished_at id].freeze].freeze,
28
+ pending: [SQL::PENDING, SQL::PENDING_AFTER, %i[run_at id].freeze].freeze
29
+ }.freeze
16
30
 
17
31
  def initialize(path:, counts_cache_ttl:)
18
32
  @path = path
@@ -65,26 +79,23 @@ module Async
65
79
  end
66
80
 
67
81
  def executing(limit:)
68
- read_rows(SQL::EXECUTING, [limit]).map { |row| executing_row(row) }
82
+ list(:executing, limit: limit)
69
83
  end
70
84
 
71
85
  def claimed(limit:)
72
- read_rows(SQL::CLAIMED, [limit]).map { |row| claimed_row(row) }
86
+ list(:claimed, limit: limit)
73
87
  end
74
88
 
75
89
  def recent_done(limit:, cursor: nil)
76
- sql, binds = terminal_query(SQL::DONE, SQL::DONE_AFTER, limit, cursor)
77
- read_rows(sql, binds).map { |row| done_row(row) }
90
+ list(:done, limit: limit, cursor: cursor)
78
91
  end
79
92
 
80
93
  def recent_failed(limit:, cursor: nil)
81
- sql, binds = terminal_query(SQL::FAILED, SQL::FAILED_AFTER, limit, cursor)
82
- read_rows(sql, binds).map { |row| failed_row(row) }
94
+ list(:failed, limit: limit, cursor: cursor)
83
95
  end
84
96
 
85
97
  def pending(limit:, cursor: nil)
86
- sql, binds = pending_query(limit, cursor)
87
- read_rows(sql, binds).map { |row| pending_row(row) }
98
+ list(:pending, limit: limit, cursor: cursor)
88
99
  end
89
100
 
90
101
  private
@@ -98,12 +109,12 @@ module Async
98
109
  SQLite3::Database.new(database_uri, uri: true)
99
110
  rescue LoadError
100
111
  raise
101
- rescue StandardError => error
102
- raise UnavailableError, "cannot open queue database: #{error.message}"
112
+ rescue StandardError => e
113
+ raise UnavailableError, "cannot open queue database: #{e.message}"
103
114
  end
104
115
 
105
116
  def database_uri
106
- path = URI::DEFAULT_PARSER.escape(File.expand_path(@path)).gsub('?', '%3F')
117
+ path = URI::RFC2396_PARSER.escape(File.expand_path(@path)).gsub('?', '%3F')
107
118
  "file:#{path}?mode=ro"
108
119
  end
109
120
 
@@ -153,10 +164,10 @@ module Async
153
164
  {
154
165
  counts: {
155
166
  executing: db.get_first_value(SQL::OVERVIEW_EXECUTING).to_i,
156
- claimed: db.get_first_value(SQL::OVERVIEW_CLAIMED).to_i,
157
- pending: db.get_first_value(SQL::OVERVIEW_PENDING).to_i,
158
- done: db.get_first_value(SQL::OVERVIEW_DONE).to_i,
159
- failed: db.get_first_value(SQL::OVERVIEW_FAILED).to_i
167
+ claimed: db.get_first_value(SQL::OVERVIEW_CLAIMED).to_i,
168
+ pending: db.get_first_value(SQL::OVERVIEW_PENDING).to_i,
169
+ done: db.get_first_value(SQL::OVERVIEW_DONE).to_i,
170
+ failed: db.get_first_value(SQL::OVERVIEW_FAILED).to_i
160
171
  }.freeze,
161
172
  next_pending_run_at: db.get_first_value(SQL::OVERVIEW_NEXT_PENDING),
162
173
  data_version: db.get_first_value(Queue::SQL::DATA_VERSION).to_i,
@@ -164,81 +175,24 @@ module Async
164
175
  }
165
176
  end
166
177
 
167
- def terminal_query(first_page_sql, next_page_sql, limit, cursor)
168
- return [first_page_sql, [limit]] unless cursor
169
-
170
- [next_page_sql, [cursor.fetch(:finished_at), cursor.fetch(:id), limit]]
171
- end
172
-
173
- def pending_query(limit, cursor)
174
- return [SQL::PENDING, [limit]] unless cursor
175
-
176
- [SQL::PENDING_AFTER, [cursor.fetch(:run_at), cursor.fetch(:id), limit]]
178
+ def list(status, limit:, cursor: nil)
179
+ sql, binds = page_query(status, limit, cursor)
180
+ keys = ROW_KEYS.fetch(status)
181
+ read_rows(sql, binds).map { |row| keys.zip(row).to_h }
177
182
  end
178
183
 
179
- def executing_row(row)
180
- {
181
- id: row[0],
182
- class_name: row[1],
183
- args_raw: row[2],
184
- options_raw: row[3],
185
- started_at: row[4],
186
- locked_by: row[5],
187
- locked_at: row[6]
188
- }
189
- end
184
+ def page_query(status, limit, cursor)
185
+ first_sql, seek_sql, cursor_keys = PAGING.fetch(status)
186
+ return [first_sql, [limit]] unless cursor
190
187
 
191
- def claimed_row(row)
192
- {
193
- id: row[0],
194
- class_name: row[1],
195
- args_raw: row[2],
196
- options_raw: row[3],
197
- locked_at: row[4],
198
- locked_by: row[5]
199
- }
200
- end
201
-
202
- def done_row(row)
203
- {
204
- id: row[0],
205
- class_name: row[1],
206
- args_raw: row[2],
207
- options_raw: row[3],
208
- finished_at: row[4],
209
- duration_ms: row[5]
210
- }
211
- end
212
-
213
- def failed_row(row)
214
- {
215
- id: row[0],
216
- class_name: row[1],
217
- args_raw: row[2],
218
- options_raw: row[3],
219
- finished_at: row[4],
220
- duration_ms: row[5],
221
- last_error_class: row[6],
222
- last_error_message: row[7]
223
- }
224
- end
225
-
226
- def pending_row(row)
227
- {
228
- id: row[0],
229
- class_name: row[1],
230
- args_raw: row[2],
231
- options_raw: row[3],
232
- created_at: row[4],
233
- run_at: row[5]
234
- }
188
+ [seek_sql, cursor_keys.map { |key| cursor.fetch(key) } << limit]
235
189
  end
236
190
 
237
191
  def require_sqlite3
238
192
  require 'sqlite3'
239
193
  rescue LoadError
240
194
  raise LoadError,
241
- "sqlite3 gem is required for Async::Background::Web. " \
195
+ 'sqlite3 gem is required for Async::Background::Web. ' \
242
196
  "Add `gem 'sqlite3', '~> 2.0'` to your Gemfile."
243
197
  end
244
198
  end
@@ -4,84 +4,78 @@ module Async
4
4
  module Background
5
5
  module Web
6
6
  module SQL
7
- BEGIN_READ_TRANSACTION = 'BEGIN'.freeze
8
- COMMIT = 'COMMIT'.freeze
9
- ROLLBACK = 'ROLLBACK'.freeze
10
- QUERY_ONLY = 'PRAGMA query_only = ON'.freeze
11
- BUSY_TIMEOUT = 'PRAGMA busy_timeout = 2000'.freeze
7
+ BEGIN_READ_TRANSACTION = 'BEGIN'
8
+ COMMIT = 'COMMIT'
9
+ ROLLBACK = 'ROLLBACK'
10
+ QUERY_ONLY = 'PRAGMA query_only = ON'
11
+ BUSY_TIMEOUT = 'PRAGMA busy_timeout = 2000'
12
+ BASE_COLUMNS = %w[id class_name args options].freeze
12
13
 
13
- OVERVIEW_EXECUTING = "SELECT COUNT(*) FROM jobs WHERE status = 'running' AND started_at IS NOT NULL".freeze
14
- OVERVIEW_CLAIMED = "SELECT COUNT(*) FROM jobs WHERE status = 'running' AND started_at IS NULL".freeze
15
- OVERVIEW_PENDING = "SELECT COUNT(*) FROM jobs WHERE status = 'pending'".freeze
16
- OVERVIEW_DONE = "SELECT COUNT(*) FROM jobs WHERE status = 'done'".freeze
17
- OVERVIEW_FAILED = "SELECT COUNT(*) FROM jobs WHERE status = 'failed'".freeze
18
- OVERVIEW_NEXT_PENDING = "SELECT MIN(run_at) FROM jobs WHERE status = 'pending'".freeze
14
+ STATUS_PREDICATE = {
15
+ executing: "status = 'running' AND started_at IS NOT NULL",
16
+ claimed: "status = 'running' AND started_at IS NULL",
17
+ pending: "status = 'pending'",
18
+ done: "status = 'done'",
19
+ failed: "status = 'failed'"
20
+ }.freeze
19
21
 
20
- EXECUTING = <<~SQL.freeze
21
- SELECT id, class_name, args, options, started_at, locked_by, locked_at
22
- FROM jobs
23
- WHERE status = 'running' AND started_at IS NOT NULL
24
- ORDER BY started_at, id
25
- LIMIT ?
26
- SQL
22
+ EXTRA_COLUMNS = {
23
+ executing: %w[started_at locked_by locked_at],
24
+ claimed: %w[locked_at locked_by],
25
+ pending: %w[created_at run_at],
26
+ done: %w[finished_at duration_ms],
27
+ failed: %w[finished_at duration_ms last_error_class last_error_message]
28
+ }.freeze
27
29
 
28
- CLAIMED = <<~SQL.freeze
29
- SELECT id, class_name, args, options, locked_at, locked_by
30
- FROM jobs
31
- WHERE status = 'running' AND started_at IS NULL
32
- ORDER BY locked_at, id
33
- LIMIT ?
34
- SQL
30
+ KEYSET = {
31
+ executing: ['started_at', :asc],
32
+ claimed: ['locked_at', :asc],
33
+ pending: ['run_at', :asc],
34
+ done: ['finished_at', :desc],
35
+ failed: ['finished_at', :desc]
36
+ }.freeze
35
37
 
36
- DONE = <<~SQL.freeze
37
- SELECT id, class_name, args, options, finished_at, duration_ms
38
- FROM jobs
39
- WHERE status = 'done'
40
- ORDER BY finished_at DESC, id DESC
41
- LIMIT ?
42
- SQL
38
+ ORDER_SQL = { asc: 'ORDER BY %<column>s, id', desc: 'ORDER BY %<column>s DESC, id DESC' }.freeze
39
+ SEEK_SQL = { asc: '(%<column>s, id) > (?, ?)', desc: '(%<column>s, id) < (?, ?)' }.freeze
43
40
 
44
- DONE_AFTER = <<~SQL.freeze
45
- SELECT id, class_name, args, options, finished_at, duration_ms
46
- FROM jobs
47
- WHERE status = 'done' AND (finished_at, id) < (?, ?)
48
- ORDER BY finished_at DESC, id DESC
49
- LIMIT ?
50
- SQL
41
+ def self.columns_for(status)
42
+ (BASE_COLUMNS + EXTRA_COLUMNS.fetch(status)).join(', ')
43
+ end
51
44
 
52
- FAILED = <<~SQL.freeze
53
- SELECT id, class_name, args, options, finished_at, duration_ms,
54
- last_error_class, last_error_message
55
- FROM jobs
56
- WHERE status = 'failed'
57
- ORDER BY finished_at DESC, id DESC
58
- LIMIT ?
59
- SQL
45
+ def self.count_query(status)
46
+ "SELECT COUNT(*) FROM jobs WHERE #{STATUS_PREDICATE.fetch(status)}".freeze
47
+ end
60
48
 
61
- FAILED_AFTER = <<~SQL.freeze
62
- SELECT id, class_name, args, options, finished_at, duration_ms,
63
- last_error_class, last_error_message
64
- FROM jobs
65
- WHERE status = 'failed' AND (finished_at, id) < (?, ?)
66
- ORDER BY finished_at DESC, id DESC
67
- LIMIT ?
68
- SQL
49
+ def self.list_query(status, seek: false)
50
+ column, direction = KEYSET.fetch(status)
51
+ predicate = STATUS_PREDICATE.fetch(status)
52
+ predicate += " AND #{format(SEEK_SQL.fetch(direction), column: column)}" if seek
69
53
 
70
- PENDING = <<~SQL.freeze
71
- SELECT id, class_name, args, options, created_at, run_at
72
- FROM jobs
73
- WHERE status = 'pending'
74
- ORDER BY run_at, id
75
- LIMIT ?
76
- SQL
54
+ <<~SQL.freeze
55
+ SELECT #{columns_for(status)}
56
+ FROM jobs
57
+ WHERE #{predicate}
58
+ #{format(ORDER_SQL.fetch(direction), column: column)}
59
+ LIMIT ?
60
+ SQL
61
+ end
77
62
 
78
- PENDING_AFTER = <<~SQL.freeze
79
- SELECT id, class_name, args, options, created_at, run_at
80
- FROM jobs
81
- WHERE status = 'pending' AND (run_at, id) > (?, ?)
82
- ORDER BY run_at, id
83
- LIMIT ?
84
- SQL
63
+ OVERVIEW_EXECUTING = count_query(:executing)
64
+ OVERVIEW_CLAIMED = count_query(:claimed)
65
+ OVERVIEW_PENDING = count_query(:pending)
66
+ OVERVIEW_DONE = count_query(:done)
67
+ OVERVIEW_FAILED = count_query(:failed)
68
+ OVERVIEW_NEXT_PENDING = "SELECT MIN(run_at) FROM jobs WHERE #{STATUS_PREDICATE.fetch(:pending)}".freeze
69
+
70
+ EXECUTING = list_query(:executing)
71
+ CLAIMED = list_query(:claimed)
72
+ DONE = list_query(:done)
73
+ FAILED = list_query(:failed)
74
+ PENDING = list_query(:pending)
75
+
76
+ DONE_AFTER = list_query(:done, seek: true)
77
+ FAILED_AFTER = list_query(:failed, seek: true)
78
+ PENDING_AFTER = list_query(:pending, seek: true)
85
79
  end
86
80
  end
87
81
  end
@@ -26,41 +26,13 @@ module Async
26
26
  end
27
27
 
28
28
  yield frame
29
- last_yield = monotonic_now
30
- unavailable_announced = false
31
-
32
- loop do
33
- sleep_for_poll
34
-
35
- begin
36
- new_version = @hub.current_version
37
-
38
- if new_version != version
39
- version = new_version
40
- yield @hub.frame_for(version)
41
- last_yield = monotonic_now
42
- elsif (monotonic_now - last_yield) >= @heartbeat_seconds
43
- yield EventHub::HEARTBEAT_FRAME
44
- last_yield = monotonic_now
45
- end
46
-
47
- unavailable_announced = false
48
- rescue ClosedError
49
- break
50
- rescue UnavailableError
51
- unless unavailable_announced
52
- yield EventHub::UNAVAILABLE_FRAME
53
- unavailable_announced = true
54
- last_yield = monotonic_now
55
- end
56
- end
57
- end
29
+ stream_updates(version, monotonic_now) { |event| yield event }
58
30
  rescue Errno::EPIPE, Errno::ECONNRESET, IOError
59
31
  nil
60
- rescue StandardError => error
32
+ rescue StandardError => e
61
33
  @logger&.error(
62
- "[async-background-web] SSE stream terminated: " \
63
- "#{error.class}: #{error.message}"
34
+ '[async-background-web] SSE stream terminated: ' \
35
+ "#{e.class}: #{e.message}"
64
36
  )
65
37
  nil
66
38
  end
@@ -76,6 +48,54 @@ module Async
76
48
  def sleep_for_poll
77
49
  sleep(@poll_seconds)
78
50
  end
51
+
52
+ def stream_updates(version, last_yield)
53
+ unavailable_announced = false
54
+ loop do
55
+ sleep_for_poll
56
+ state = next_state(version, last_yield, unavailable_announced) { |event| yield event }
57
+ break unless state
58
+
59
+ version, last_yield, unavailable_announced = state
60
+ end
61
+ end
62
+
63
+ def next_state(version, last_yield, unavailable_announced)
64
+ current_version = @hub.current_version
65
+ return updated_state(current_version) { |event| yield event } if current_version != version
66
+ return heartbeat_state(version) { |event| yield event } if heartbeat_due?(last_yield)
67
+
68
+ [version, last_yield, false]
69
+ rescue ClosedError
70
+ nil
71
+ rescue UnavailableError
72
+ announce_unavailable(version, last_yield, unavailable_announced) { |event| yield event }
73
+ end
74
+
75
+ def updated_state(version)
76
+ yield @hub.frame_for(version)
77
+ [version, monotonic_now, false]
78
+ end
79
+
80
+ def heartbeat_state(version)
81
+ yield EventHub::HEARTBEAT_FRAME
82
+ [version, monotonic_now, false]
83
+ end
84
+
85
+ def heartbeat_due?(last_yield)
86
+ (monotonic_now - last_yield) >= @heartbeat_seconds
87
+ end
88
+
89
+ def announce_unavailable(version, last_yield, announced)
90
+ return [version, last_yield, true] if announced
91
+
92
+ yield_unavailable { |event| yield event }
93
+ [version, monotonic_now, true]
94
+ end
95
+
96
+ def yield_unavailable
97
+ yield EventHub::UNAVAILABLE_FRAME
98
+ end
79
99
  end
80
100
  end
81
101
  end
@@ -37,7 +37,7 @@ module Async
37
37
 
38
38
  def configuration
39
39
  @configuration or raise NotConfiguredError,
40
- 'Async::Background::Web is not configured. Call Async::Background::Web.configure.'
40
+ 'Async::Background::Web is not configured. Call Async::Background::Web.configure.'
41
41
  end
42
42
 
43
43
  def reset!
@@ -1,12 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'async'
4
- require 'async/semaphore'
5
3
  require 'console'
6
4
  require 'fugit'
7
5
  require 'tmpdir'
8
6
 
9
7
  require_relative 'background/version'
8
+ require_relative 'background/runtime'
10
9
  require_relative 'background/clock'
11
10
  require_relative 'background/min_heap'
12
11
  require_relative 'background/entry'
metadata CHANGED
@@ -1,31 +1,30 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-background
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Hajdarov
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-08-15 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
- name: async
13
+ name: console
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '2.0'
18
+ version: '1.0'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '2.0'
25
+ version: '1.0'
27
26
  - !ruby/object:Gem::Dependency
28
- name: console
27
+ name: fugit
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
@@ -39,33 +38,33 @@ dependencies:
39
38
  - !ruby/object:Gem::Version
40
39
  version: '1.0'
41
40
  - !ruby/object:Gem::Dependency
42
- name: fugit
41
+ name: base64
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - "~>"
46
45
  - !ruby/object:Gem::Version
47
- version: '1.0'
46
+ version: '0.2'
48
47
  type: :runtime
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - "~>"
53
52
  - !ruby/object:Gem::Version
54
- version: '1.0'
53
+ version: '0.2'
55
54
  - !ruby/object:Gem::Dependency
56
- name: base64
55
+ name: async
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - "~>"
60
59
  - !ruby/object:Gem::Version
61
- version: '0.2'
62
- type: :runtime
60
+ version: '2.0'
61
+ type: :development
63
62
  prerelease: false
64
63
  version_requirements: !ruby/object:Gem::Requirement
65
64
  requirements:
66
65
  - - "~>"
67
66
  - !ruby/object:Gem::Version
68
- version: '0.2'
67
+ version: '2.0'
69
68
  - !ruby/object:Gem::Dependency
70
69
  name: rake
71
70
  requirement: !ruby/object:Gem::Requirement
@@ -128,10 +127,11 @@ dependencies:
128
127
  - - "<"
129
128
  - !ruby/object:Gem::Version
130
129
  version: '0.5'
131
- description: A production-grade lightweight scheduler built on top of Async. Single
132
- event loop with min-heap timer, skip-overlapping execution, jitter, monotonic clock
133
- intervals, semaphore concurrency control, and deterministic worker sharding. Designed
134
- for Falcon but works with any Async-based application.
130
+ description: 'A production-grade lightweight background job scheduler built on the
131
+ Fiber::Scheduler interface itself, not on any one implementation: it runs under
132
+ Async/Falcon, Itsi, or anything else that installs a scheduler. Single event loop
133
+ with min-heap timer, skip-overlapping execution, jitter, monotonic clock intervals,
134
+ semaphore concurrency control, and deterministic worker sharding.'
135
135
  email:
136
136
  - romnhajdarov@gmail.com
137
137
  executables: []
@@ -157,12 +157,22 @@ files:
157
157
  - lib/async/background/queue/sql.rb
158
158
  - lib/async/background/queue/store.rb
159
159
  - lib/async/background/runner.rb
160
+ - lib/async/background/runner/lifecycle.rb
160
161
  - lib/async/background/runner/queue_execution.rb
161
162
  - lib/async/background/runner/schedule.rb
163
+ - lib/async/background/runtime.rb
164
+ - lib/async/background/runtime/notification.rb
165
+ - lib/async/background/runtime/semaphore.rb
166
+ - lib/async/background/runtime/task.rb
167
+ - lib/async/background/runtime/task_group.rb
168
+ - lib/async/background/scheduler.rb
162
169
  - lib/async/background/version.rb
163
170
  - lib/async/background/web.rb
164
171
  - lib/async/background/web/app.rb
165
172
  - lib/async/background/web/assets.rb
173
+ - lib/async/background/web/assets/app.css
174
+ - lib/async/background/web/assets/app.js
175
+ - lib/async/background/web/assets/index.html
166
176
  - lib/async/background/web/auth.rb
167
177
  - lib/async/background/web/configuration.rb
168
178
  - lib/async/background/web/cursor.rb
@@ -183,7 +193,6 @@ metadata:
183
193
  source_code_uri: https://github.com/roman-haidarov/async-background
184
194
  changelog_uri: https://github.com/roman-haidarov/async-background/blob/main/CHANGELOG.md
185
195
  bug_tracker_uri: https://github.com/roman-haidarov/async-background/issues
186
- post_install_message:
187
196
  rdoc_options: []
188
197
  require_paths:
189
198
  - lib
@@ -198,8 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
207
  - !ruby/object:Gem::Version
199
208
  version: '0'
200
209
  requirements: []
201
- rubygems_version: 3.4.22
202
- signing_key:
210
+ rubygems_version: 3.6.7
203
211
  specification_version: 4
204
- summary: Lightweight heap-based cron/interval scheduler for Async.
212
+ summary: Lightweight heap-based cron/interval scheduler for any Fiber scheduler.
205
213
  test_files: []