async-tools 0.2.9 → 0.2.10

Sign up to get free protection for your applications and to get access to all the features.
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