async-tools 0.2.2 → 0.2.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b3b08f9b120bebf744df3e7c0da6e2cb7c8d4c5acb01458c25f8be8ff7588fa
4
- data.tar.gz: 7c02d0bfce7a3bcb21b8ac8b2f4a8aec4bfad5a12233141b7d1d73b139c08684
3
+ metadata.gz: 47081e302bddc4861bc4526016dd26f4f601014d24b6696427b100446704b6b3
4
+ data.tar.gz: 6b71e93bbdbb81df60b5e20b0323a514759c1ab4e974c28a339cf8979fcc3d82
5
5
  SHA512:
6
- metadata.gz: 496680113c44a54745da6846bca478f426454eaa34c125de569205c9f5fe3bc82ec6fe53589f8c86c0b1222361ec949c7938baa64b2be0246a15da280ca8f30a
7
- data.tar.gz: 14d6d23403ede1040d74e68590ad0572e1c01b402ff6d7d0ff25dfb11c6f0f7a187b894c5681772e7828c88a8ba5f2e95751a3bce58ba9d9dc2aa14c31078e67
6
+ metadata.gz: a0636dd84879123e42e22599289ceb3f474f14a7ebdf32abe379ad02d7ac7b4e6f1dafbe54a96686d37bbfa0428b0bc7950c975f3b20c37c89c80c6b8e46cad0
7
+ data.tar.gz: 8045ea3c5c51d63b7ccd7349697af0c7f34c7ce937c11d0d65a76b154b5ca04bf37aee70a21261901e770c805bb7b487def5915f2def9193f8488e8dc1136a28
data/Gemfile CHANGED
@@ -7,6 +7,9 @@ gemspec
7
7
 
8
8
  gem "rake"
9
9
 
10
+ # optional
11
+ gem "activesupport"
12
+
10
13
  gem "async-http"
11
14
  gem "async-rspec"
12
15
  gem "rspec"
data/Gemfile.lock CHANGED
@@ -1,13 +1,18 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- async-tools (0.2.2)
4
+ async-tools (0.2.5)
5
5
  async (~> 2.3)
6
6
  zeitwerk (~> 2.6)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
+ activesupport (7.0.4.2)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
11
16
  ast (2.4.2)
12
17
  async (2.3.1)
13
18
  console (~> 1.10)
@@ -32,12 +37,15 @@ GEM
32
37
  backport (1.2.0)
33
38
  benchmark (0.2.1)
34
39
  childprocess (4.1.0)
40
+ concurrent-ruby (1.2.2)
35
41
  console (1.16.2)
36
42
  fiber-local
37
43
  diff-lcs (1.5.0)
38
44
  docile (1.4.0)
39
45
  e2mmap (0.1.0)
40
46
  fiber-local (1.0.0)
47
+ i18n (1.12.0)
48
+ concurrent-ruby (~> 1.0)
41
49
  iniparse (1.5.0)
42
50
  io-event (1.1.6)
43
51
  jaro_winkler (1.5.4)
@@ -46,6 +54,7 @@ GEM
46
54
  rexml
47
55
  kramdown-parser-gfm (1.1.0)
48
56
  kramdown (~> 2.0)
57
+ minitest (5.18.0)
49
58
  nokogiri (1.14.1-x86_64-linux)
50
59
  racc (~> 1.4)
51
60
  overcommit (0.60.0)
@@ -134,6 +143,8 @@ GEM
134
143
  tilt (2.0.11)
135
144
  timers (4.3.5)
136
145
  traces (0.8.0)
146
+ tzinfo (2.0.6)
147
+ concurrent-ruby (~> 1.0)
137
148
  unicode-display_width (2.4.2)
138
149
  webrick (1.7.0)
139
150
  yard (0.9.28)
@@ -144,6 +155,7 @@ PLATFORMS
144
155
  x86_64-linux
145
156
 
146
157
  DEPENDENCIES
158
+ activesupport
147
159
  async-http
148
160
  async-rspec
149
161
  async-tools!
@@ -4,18 +4,8 @@ module Async::App::Component
4
4
  def self.included(base)
5
5
  base.extend(Async::App::Injector)
6
6
  base.inject(:bus)
7
-
8
7
  base.include(Async::Logger)
9
-
10
- strict = Dry.Types::Strict
11
-
12
- string_like = (strict::String | strict::Symbol).constructor(&:to_s)
13
- kv = strict::Hash.map(string_like, strict::String)
14
-
15
- base.const_set(:T, Module.new do
16
- include Dry.Types
17
- const_set(:StringLike, string_like)
18
- const_set(:KV, kv)
19
- end)
20
8
  end
9
+
10
+ def run = nil
21
11
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::EventLogger
4
+ include Async::App::Component
5
+
6
+ def run
7
+ bus.subscribe(/.*/) do |name, payload|
8
+ debug { "Event #{name} received. Payload:\n\n#{payload.pretty_inspect}\n" }
9
+ end
10
+ end
11
+ end
data/lib/async/app.rb CHANGED
@@ -15,11 +15,9 @@ class Async::App
15
15
  @task = Async::Task.current
16
16
 
17
17
  set_traps!
18
- {
19
- bus: Async::Bus.new(app_name),
20
- **container_config
21
- }.each { container.register(_1, _2) }
18
+ init_container!
22
19
 
20
+ start_event_logger!
23
21
  start_metrics_server!
24
22
  run!
25
23
  info { "Started" }
@@ -53,6 +51,13 @@ class Async::App
53
51
  trap("TERM") { stop }
54
52
  end
55
53
 
54
+ def init_container!
55
+ {
56
+ bus: Async::Bus.new,
57
+ **container_config
58
+ }.each { container.register(_1, _2) }
59
+ end
60
+
56
61
  def force_exit!
57
62
  fatal { "Forced exit" }
58
63
  exit(1)
@@ -63,4 +68,6 @@ class Async::App
63
68
  bus.subscribe("metrics.updated") { server.update_metrics(_1) }
64
69
  end
65
70
  end
71
+
72
+ def start_event_logger! = EventLogger.new.run
66
73
  end
data/lib/async/bus.rb CHANGED
@@ -2,30 +2,20 @@
2
2
 
3
3
  class Async::Bus
4
4
  include Async::Logger
5
- # dry-events is not a dependency of async-tools on purpose.
6
- # add it to your bundle yourself
7
-
8
- # Semantics:
9
- # - Lazily registeres events
10
- # - Synchronous by default
11
- # - Catches exceptions in subscribers, logs them
12
- def initialize(name)
13
- @name = name
14
- @w = Class.new.include(Dry::Events::Publisher[name]).new
15
- end
5
+ # A tiny wrapper around ac ActiveSupport::Notifications
16
6
 
17
7
  # BLOCKING unless subscribers run in tasks
18
8
  def publish(name, *args, **params)
19
- @w.register_event(name)
20
- @w.publish(name, payload: (args.first || params))
9
+ ActiveSupport::Notifications.instrument(name, payload: (args.first || params))
21
10
  rescue StandardError => e
22
11
  log_error(name, e)
23
12
  end
24
13
 
25
14
  # NON-BLOCKING
26
- def subscribe(name)
27
- @w.register_event(name)
28
- @w.subscribe(name) { yield(_1[:payload]) }
15
+ def subscribe(pattern)
16
+ ActiveSupport::Notifications.subscribe(pattern) do |name, _start, _finish, _id, params|
17
+ yield params[:payload], name
18
+ end
29
19
  end
30
20
 
31
21
  # NON-BLOCKING, runs subscriber in a task
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::Cache
4
+ Item = Struct.new("Item", :task, :value, :created_at, :duration) do
5
+ def expired? = created_at && Time.now - created_at >= duration
6
+ end
7
+
8
+ def cache(id, duration:, parent: Async::Task.current)
9
+ cleanup!
10
+ find_or_create(id, duration:) do |item|
11
+ parent.async do |task|
12
+ item.task = task
13
+ item.value = yield(id) if block_given?
14
+ item.created_at = Time.now
15
+ end.wait
16
+ end.value
17
+ end
18
+
19
+ def cleanup! = storage.delete_if { _2.expired? }
20
+ def count = storage.count
21
+
22
+ private
23
+
24
+ def find_or_create(id, duration:)
25
+ storage[id].tap do |item|
26
+ item.duration = duration
27
+ item.task&.wait
28
+ return item if item.created_at
29
+
30
+ yield(item)
31
+ end
32
+ end
33
+
34
+ def storage = @storage ||= Hash.new { _1[_2] = Item.new }
35
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # inspired by https://github.com/negativecode/vines/blob/master/lib/vines/token_bucket.rb
4
+ class Async::Throttler
5
+ def initialize(capacity, rate, parent: Async::Task.current)
6
+ raise ArgumentError, "capacity must be > 0" unless capacity.positive?
7
+ raise ArgumentError, "rate must be > 0" unless rate.positive?
8
+
9
+ @capacity = capacity
10
+ @tokens = capacity
11
+ @rate = rate
12
+ @parent = parent
13
+
14
+ @timestamp = Time.new
15
+ end
16
+
17
+ def wait(timeout: 0)
18
+ with_timeout(timeout) do
19
+ while @tokens < 1
20
+ fill!
21
+ sleep(1.0 / @rate)
22
+ end
23
+ end
24
+
25
+ @tokens -= 1
26
+ end
27
+
28
+ def async(parent: @parent, timeout: 0, &)
29
+ wait(timeout:)
30
+ parent.async(&)
31
+ end
32
+
33
+ private
34
+
35
+ def with_timeout(timeout, &)
36
+ return yield if timeout.zero?
37
+
38
+ Fiber.scheduler.with_timeout(timeout, &)
39
+ end
40
+
41
+ def fill!
42
+ return if @tokens >= @capacity
43
+
44
+ now = Time.new
45
+ @tokens += @rate * (now - @timestamp)
46
+ @tokens = @capacity if @tokens > @capacity
47
+ @timestamp = now
48
+ end
49
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Async # rubocop:disable Style/ClassAndModuleChildren
4
4
  module Tools
5
- VERSION = "0.2.2"
5
+ VERSION = "0.2.5"
6
6
  end
7
7
  end
data/lib/async/tools.rb CHANGED
@@ -22,9 +22,11 @@ module Async
22
22
  end
23
23
  end
24
24
 
25
- def self.map(collection, **params, &)
26
- WorkerPool.with(queue_limit: collection.count, **params) do |pool|
27
- pool.schedule_all(collection, &).map(&:wait)
25
+ def self.map(collection, concurrency: nil, parent: Async::Task.current, &)
26
+ Async::Semaphore.new(concurrency || collection.count, parent:).then do |s|
27
+ collection.map do |item|
28
+ s.async { yield(item) }
29
+ end.map(&:wait)
28
30
  end
29
31
  end
30
32
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gleb Sinyavskiy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-03-03 00:00:00.000000000 Z
11
+ date: 2023-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -62,16 +62,19 @@ files:
62
62
  - bin/setup
63
63
  - lib/async/app.rb
64
64
  - lib/async/app/component.rb
65
+ - lib/async/app/event_logger.rb
65
66
  - lib/async/app/injector.rb
66
67
  - lib/async/app/metrics/ruby_runtime_monitor.rb
67
68
  - lib/async/app/metrics/serializer.rb
68
69
  - lib/async/app/metrics/server.rb
69
70
  - lib/async/app/metrics/store.rb
70
71
  - lib/async/bus.rb
72
+ - lib/async/cache.rb
71
73
  - lib/async/channel.rb
72
74
  - lib/async/logger.rb
73
75
  - lib/async/q.rb
74
76
  - lib/async/result_notification.rb
77
+ - lib/async/throttler.rb
75
78
  - lib/async/timer.rb
76
79
  - lib/async/tools.rb
77
80
  - lib/async/tools/version.rb