async-tools 0.2.9 → 0.2.10

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: fc3694f2f03f7c7845ee1c6911a3a2f2042b0d4b55c20fdef6c616e205c45c64
4
- data.tar.gz: d7c56b6cb80b8dc83a041f3f53bc5b876b0f2ea6286bf6b53dd3c0173b264e7d
3
+ metadata.gz: 0d1ee91075ee99708de1e43d75c7184d9a23c79a0cf36291d91d7ede9314d22d
4
+ data.tar.gz: b49e3539aadec63fa3aeed3c3e95bb6bbeca1ef33ef3487bf406b8a28430941d
5
5
  SHA512:
6
- metadata.gz: 42cc9c27e20fca3fd48287c0c0f0bd5c4176d607d540d9e3ecb5237eebecf494ffb4fe8dc9b025e85ed45771bedadfa3b227fb702a344fe11bf42e0ca212699a
7
- data.tar.gz: ff2dcf6780a51aad4e220dbb0548a0757dcc603072b1c7b195b7124e9175e14a31300b349d1dfde1868020511317182ad07074499ea94835424e08ebed59e1dd
6
+ metadata.gz: 02e5dce453e331558d760ff45769f466192e1b95963373e23cd9668c364bc131902f3d09457363b987308326def15e841651c3311b09d1a1878c61fba03b595d
7
+ data.tar.gz: ab0d8f31349e0514d14ff1c232072c530658db77c6302e5af13238b8bb25cc96226e3b8d7075367653c62095dcf1ca9e64b2120a234364119ed3310e126c2a7f
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- async-tools (0.2.9)
4
+ async-tools (0.2.10)
5
5
  async (~> 2.3)
6
6
  zeitwerk (~> 2.6)
7
7
 
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async::App::AutoloadComponent
4
+ def self.included(base) = base.include(Async::App::Component)
5
+ end
@@ -7,5 +7,19 @@ module Async::App::Component
7
7
  base.include(Async::Logger)
8
8
  end
9
9
 
10
- def run = nil
10
+ def start!
11
+ init!
12
+ after_init
13
+ run!
14
+ after_run
15
+ end
16
+
17
+ def init! = nil
18
+ def run! = info { "Started" }
19
+
20
+ # TODO: unsubscribe from everything on stop
21
+ def stop! = info { "Stopped" }
22
+
23
+ def after_init = nil
24
+ def after_run = nil
11
25
  end
@@ -3,7 +3,7 @@
3
3
  class Async::App::EventLogger
4
4
  include Async::App::Component
5
5
 
6
- def run
6
+ def after_init
7
7
  bus.subscribe(/.*/) do |payload, name|
8
8
  debug { "Event #{name} received. Payload:\n\n#{payload.pretty_inspect}\n" }
9
9
  end
@@ -3,7 +3,7 @@
3
3
  module Async::App::Injector
4
4
  def inject(name)
5
5
  define_method(name) do
6
- $__ASYNC_APP.container[name] # rubocop:disable Style/GlobalVars
6
+ Async::App.instance.container[name]
7
7
  end
8
8
  private name
9
9
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Async::App::Metrics::ComponentsMetricsCollector
4
+ include Async::App::TimerComponent
5
+ include Async::App::AutoloadComponent
6
+
7
+ def on_tick = bus.publish("metrics.updated", metrics)
8
+ def interval = 5
9
+ def run_on_start = true
10
+ def on_error(exception) = warn { exception }
11
+
12
+ private
13
+
14
+ def metrics
15
+ {
16
+ async_app_components: { value: Async::App.instance.components.count },
17
+ async_app_autoloadable_components: { value: Async::App.instance.autoloadable_components.count },
18
+ async_app_timer_components: { value: Async::App.instance.timer_components.count }
19
+ }
20
+ end
21
+ end
@@ -1,16 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Async::App::Metrics::RubyRuntimeMetricsCollector
4
- include Async::App::Component
4
+ include Async::App::TimerComponent
5
+ include Async::App::AutoloadComponent
5
6
 
6
- INTERVAL = 5
7
+ def on_tick = bus.publish("metrics.updated", metrics)
8
+ def interval = 5
9
+ def run_on_start = true
10
+ def on_error(exception) = warn { exception }
7
11
 
8
- def run
9
- Async::Timer.new(INTERVAL, run_on_start: true, on_error: ->(e) { warn(e) }) do
10
- bus.publish("metrics.updated", metrics)
11
- end
12
- info { "Started" }
13
- end
12
+ private
14
13
 
15
14
  def metrics
16
15
  fibers = ObjectSpace.each_object(Fiber)
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async::App::TimerComponent
4
+ def self.included(base)
5
+ base.include(Async::App::Component)
6
+ base.include(InstanceMethods)
7
+ end
8
+
9
+ module InstanceMethods
10
+ def init!
11
+ super
12
+ @timer = Async::Timer.new(run_on_start:, start: false, on_error: method(:on_error)) { tick! }
13
+ end
14
+
15
+ def run!
16
+ return if @timer.active?
17
+
18
+ @timer.start(interval)
19
+ info { "Started. Interval = #{interval}" }
20
+ end
21
+
22
+ def stop!
23
+ @timer&.stop
24
+ super
25
+ end
26
+
27
+ # TimerComponent - specific methods
28
+ def tick!
29
+ debug { "Started" }
30
+ on_tick
31
+ debug { "Finished" }
32
+ end
33
+
34
+ def restart!
35
+ @timer.restart(interval)
36
+ info { "Restarted. Polling interval=#{interval}" }
37
+ end
38
+
39
+ private
40
+
41
+ def interval = raise NotImplementedError
42
+ def run_on_start = raise NotImplementedError
43
+ def on_tick = raise NotImplementedError
44
+ def on_error(exception) = raise exception
45
+ end
46
+ end
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Async::App::WebServer::HealthApp
4
- include Async::App::Component
3
+ class Async::App::WebApps::HealthApp
4
+ include Async::App::WebComponent
5
+ include Async::App::AutoloadComponent
5
6
 
6
7
  PATHS = ["/health", "/health/"].freeze
7
8
 
8
- def initialize
9
+ def after_init
9
10
  @healthy = false
10
-
11
11
  bus.subscribe("health.updated") { @healthy = _1 }
12
12
  end
13
13
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Async::App::WebServer::MetricsApp::Serializer
3
+ class Async::App::WebApps::MetricsApp::Serializer
4
4
  def initialize(prefix:, store:)
5
5
  @prefix = prefix
6
6
  @store = store
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Async::App::WebServer::MetricsApp::Store
3
+ class Async::App::WebApps::MetricsApp::Store
4
4
  include Enumerable
5
5
 
6
6
  def set(name, value:, suffix: "total", **labels)
@@ -1,13 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Async::App::WebServer::MetricsApp
4
- include Async::App::Component
3
+ class Async::App::WebApps::MetricsApp
4
+ include Async::App::WebComponent
5
+ include Async::App::AutoloadComponent
5
6
 
6
7
  PATHS = ["/metrics", "/metrics/"].freeze
7
8
 
8
- def initialize(metrics_prefix:)
9
+ inject :async_app_name
10
+
11
+ def after_init
9
12
  store = Store.new
10
- @serializer = Serializer.new(prefix: metrics_prefix, store:)
13
+ @serializer = Serializer.new(prefix: async_app_name, store:)
11
14
 
12
15
  bus.subscribe("metrics.updated") do |metrics|
13
16
  metrics.each { store.set(_1, **_2) }
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Async::App::WebComponent
4
+ def self.included(base)
5
+ base.include(Async::App::Component)
6
+ base.include(InstanceMethods)
7
+ end
8
+
9
+ module InstanceMethods
10
+ def run! = bus.publish(Async::App::WebServer::APP_ADDED, self)
11
+
12
+ def can_handle?(request) = raise NotImplementedError
13
+ def call(*) = raise NotImplementedError
14
+ end
15
+ end
@@ -1,17 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Async::App::WebServer
4
- include Async::Logger
4
+ include Async::App::Component
5
5
 
6
- def initialize(metrics_prefix:, port: 8080)
7
- @router = Async::App::WebServer::Router.new(
8
- MetricsApp.new(metrics_prefix:),
9
- HealthApp.new
10
- )
6
+ APP_ADDED = "async-app.web_app.added"
7
+
8
+ class Router
9
+ def initialize
10
+ @apps = []
11
+ end
12
+
13
+ def add(app) = @apps << app
14
+
15
+ def call(request)
16
+ @apps.reverse_each { return Protocol::HTTP::Response[*_1.call(request)] if _1.can_handle?(request) }
17
+
18
+ Protocol::HTTP::Response[404, {}, ["Not found"]]
19
+ end
20
+ end
21
+
22
+ def initialize(port: 8080)
23
+ @router = Router.new
11
24
  @endpoint = Async::HTTP::Endpoint.parse("http://0.0.0.0:#{port}")
12
25
  end
13
26
 
14
- def run
27
+ def after_init = bus.subscribe(APP_ADDED) { add_app(_1) }
28
+
29
+ def add_app(app) = @router.add(app)
30
+
31
+ def run!
15
32
  Async { Async::HTTP::Server.new(@router, @endpoint).run }
16
33
  info { "Started on #{@endpoint.url}" }
17
34
  end
data/lib/async/app.rb CHANGED
@@ -1,68 +1,68 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Async::App
4
- extend Async::App::Injector
4
+ include Component
5
5
 
6
- include Async::Logger
6
+ class << self
7
+ def instance = @instance = instances(self).first
7
8
 
8
- inject :bus
9
-
10
- # rubocop:disable Style/GlobalVars
11
- def initialize
12
- raise "only one instance of #{self.class} is allowed" if $__ASYNC_APP
9
+ def instances(klass) = ObjectSpace.each_object(klass)
10
+ end
13
11
 
14
- $__ASYNC_APP = self
15
- @task = Async::Task.current
12
+ def init!
13
+ raise "only one instance of #{self.class} is allowed" if instances(self.class).count > 1
16
14
 
15
+ @parent = Async::Task.current
17
16
  set_traps!
18
17
  init_container!
19
-
20
- start_event_logger!
21
- start_web_server!
22
-
23
- start_runtime_metrics_collector!
24
-
25
- run!
26
-
27
- info { "Started" }
28
- bus.publish("health.updated", true)
18
+ super
29
19
  rescue StandardError => e
30
20
  fatal { e }
31
- stop
21
+ stop!
32
22
  exit(1)
33
23
  end
34
24
 
35
- def container = @container ||= Dry::Container.new
36
- def run! = nil
37
- def container_config = {}
38
- def app_name = :async_app
39
-
40
- def stop
41
- @task&.stop
42
- $__ASYNC_APP = nil
43
- info { "Stopped" }
25
+ def stop!
26
+ @parent&.stop(true)
27
+ super
44
28
  end
45
29
 
46
- # rubocop:enable Style/GlobalVars
30
+ def container = @container ||= Dry::Container.new
31
+ def instances(klass) = self.class.instances(klass)
32
+ def components = instances(Class).select { _1.included_modules.include?(Component) }.reject { _1 <= self.class }
33
+ def autoloadable_components = components.select { _1.included_modules.include?(AutoloadComponent) }
34
+ def timer_components = components.select { _1.included_modules.include?(TimerComponent) }
47
35
 
48
36
  private
49
37
 
38
+ def container_config = {}
39
+ def async_app_name = :async_app
40
+
41
+ def run!
42
+ start_event_logger!
43
+ start_web_server!
44
+
45
+ autoload_components!
46
+ super
47
+ bus.publish("health.updated", true)
48
+ end
49
+
50
50
  def set_traps!
51
51
  trap("INT") do
52
52
  force_exit! if @stopping
53
53
  @stopping = true
54
54
  warn { "Interrupted, stopping. Press ^C once more to force exit." }
55
- stop
55
+ stop!
56
56
  end
57
57
 
58
- trap("TERM") { stop }
58
+ trap("TERM") { stop! }
59
59
  end
60
60
 
61
61
  def init_container!
62
- {
63
- bus: Async::Bus.new,
64
- **container_config
65
- }.each { container.register(_1, _2) }
62
+ container.register(:bus, Async::Bus.new)
63
+ container.register(:async_app_name, async_app_name)
64
+
65
+ container_config.each { container.register(_1, _2) }
66
66
  end
67
67
 
68
68
  def force_exit!
@@ -70,7 +70,7 @@ class Async::App
70
70
  exit(1)
71
71
  end
72
72
 
73
- def start_web_server! = WebServer.new(metrics_prefix: app_name).run
74
- def start_event_logger! = EventLogger.new.run
75
- def start_runtime_metrics_collector! = Async::App::Metrics::RubyRuntimeMetricsCollector.new.run
73
+ def autoload_components! = autoloadable_components.each { _1.new.start! }
74
+ def start_web_server! = WebServer.new.start!
75
+ def start_event_logger! = EventLogger.new.start!
76
76
  end
data/lib/async/timer.rb CHANGED
@@ -7,7 +7,7 @@ class Async::Timer
7
7
 
8
8
  class AlreadyStarted < Error; end
9
9
 
10
- def initialize(delay, # rubocop:disable Metrics/CyclomaticComplexity,Metrics/ParameterLists
10
+ def initialize(delay = nil, # rubocop:disable Metrics/CyclomaticComplexity,Metrics/ParameterLists
11
11
  repeat: true,
12
12
  start: true,
13
13
  run_on_start: false,
@@ -32,14 +32,16 @@ class Async::Timer
32
32
 
33
33
  def active? = @active
34
34
 
35
- def restart
35
+ def restart(delay = @delay, run: false)
36
36
  stop
37
- start
37
+ start(delay, run:)
38
38
  end
39
39
 
40
- def start(run: false)
40
+ def start(delay = @delay, run: false)
41
41
  raise AlreadyStarted, "Timer already started" if active?
42
+ raise ArgumentError, "delay cannot be nil" if delay.nil?
42
43
 
44
+ @delay = delay
43
45
  @active = true
44
46
 
45
47
  @task = @parent.async do
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Async # rubocop:disable Style/ClassAndModuleChildren
4
4
  module Tools
5
- VERSION = "0.2.9"
5
+ VERSION = "0.2.10"
6
6
  end
7
7
  end
data/lib/async/tools.rb CHANGED
@@ -13,8 +13,6 @@ loader.tag = File.basename(__FILE__, ".rb")
13
13
  loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
14
14
  loader.push_dir(File.expand_path("..", __dir__.to_s))
15
15
 
16
- loader.setup
17
-
18
16
  module Async
19
17
  # Your code goes here...
20
18
  module Tools # rubocop:disable Style/ClassAndModuleChildren
@@ -30,3 +28,6 @@ module Async
30
28
  end
31
29
  end
32
30
  end
31
+
32
+ loader.setup
33
+ loader.eager_load
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.9
4
+ version: 0.2.10
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-08 00:00:00.000000000 Z
11
+ date: 2023-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async
@@ -61,16 +61,19 @@ files:
61
61
  - bin/console
62
62
  - bin/setup
63
63
  - lib/async/app.rb
64
+ - lib/async/app/autoload_component.rb
64
65
  - lib/async/app/component.rb
65
66
  - lib/async/app/event_logger.rb
66
67
  - lib/async/app/injector.rb
68
+ - lib/async/app/metrics/components_metrics_collector.rb
67
69
  - lib/async/app/metrics/ruby_runtime_metrics_collector.rb
70
+ - lib/async/app/timer_component.rb
71
+ - lib/async/app/web_apps/health_app.rb
72
+ - lib/async/app/web_apps/metrics_app.rb
73
+ - lib/async/app/web_apps/metrics_app/serializer.rb
74
+ - lib/async/app/web_apps/metrics_app/store.rb
75
+ - lib/async/app/web_component.rb
68
76
  - lib/async/app/web_server.rb
69
- - lib/async/app/web_server/health_app.rb
70
- - lib/async/app/web_server/metrics_app.rb
71
- - lib/async/app/web_server/metrics_app/serializer.rb
72
- - lib/async/app/web_server/metrics_app/store.rb
73
- - lib/async/app/web_server/router.rb
74
77
  - lib/async/bus.rb
75
78
  - lib/async/cache.rb
76
79
  - lib/async/channel.rb
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Async::App::WebServer::Router
4
- extend Async::App::Injector
5
-
6
- def initialize(*apps)
7
- @apps = apps
8
- end
9
-
10
- def call(request)
11
- @apps.each { return Protocol::HTTP::Response[*_1.call(request)] if _1.can_handle?(request) }
12
-
13
- Protocol::HTTP::Response[404, {}, ["Not found"]]
14
- end
15
- end