async-tools 0.1.10 → 0.2.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.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +3 -1
- data/.rubocop.yml +9 -0
- data/Gemfile.lock +1 -1
- data/Rakefile +1 -1
- data/lib/async/app.rb +85 -0
- data/lib/async/bus.rb +42 -7
- data/lib/async/logger.rb +11 -0
- data/lib/async/tools/version.rb +1 -1
- data/lib/async/tools.rb +1 -0
- metadata +4 -3
- data/lib/async/bus/bus.rb +0 -95
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37e9af668ebad3f0c2356cc2a428b1bedf8b432e239099dd1b961f16d0b01036
|
4
|
+
data.tar.gz: 201bc5e60a2b19e5a1b1498c14c0e792e96bd21a688b3605f443e6f98a2b6f29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f92056eef4bc4dc65210c682b8637fc6e39088b1213cca0f4b9bf54a06a5b736ce841c351bbaf211bac3777ccda8a7220ee2a926d822e54758a83e87b5c856f8
|
7
|
+
data.tar.gz: e463ee25e793a281c1130ede835c427f03c47f15bd51d708f28396533652ef6be14e7ecd0b08b577ec2dadf73e8ec90522bc986a6911b64ea1f049c3c5cd264e
|
data/.github/workflows/main.yml
CHANGED
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
data/Rakefile
CHANGED
data/lib/async/app.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Async::App
|
4
|
+
# rubocop:disable Style/GlobalVars
|
5
|
+
|
6
|
+
module Injector
|
7
|
+
def inject(name)
|
8
|
+
define_method(name) do
|
9
|
+
$__ASYNC_APP.container[name]
|
10
|
+
end
|
11
|
+
private name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Component
|
16
|
+
def self.included(base)
|
17
|
+
base.extend(Injector)
|
18
|
+
base.inject(:bus)
|
19
|
+
|
20
|
+
base.include(Async::Logger)
|
21
|
+
|
22
|
+
strict = Dry.Types::Strict
|
23
|
+
|
24
|
+
string_like = (strict::String | strict::Symbol).constructor(&:to_s)
|
25
|
+
kv = strict::Hash.map(string_like, strict::String)
|
26
|
+
|
27
|
+
base.const_set(:T, Module.new do
|
28
|
+
include Dry.Types
|
29
|
+
const_set(:StringLike, string_like)
|
30
|
+
const_set(:KV, kv)
|
31
|
+
end)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
extend Injector
|
36
|
+
include Async::Logger
|
37
|
+
|
38
|
+
inject :bus
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
raise "only one instance of #{self.class} is allowed" if $__ASYNC_APP
|
42
|
+
|
43
|
+
$__ASYNC_APP = self
|
44
|
+
|
45
|
+
container.register(:bus, Async::Bus.new(:__async_app))
|
46
|
+
|
47
|
+
set_traps!
|
48
|
+
@task = Async::Task.current
|
49
|
+
container_config.each { container.register(_1, _2) }
|
50
|
+
run!
|
51
|
+
info { "Started" }
|
52
|
+
rescue StandardError => e
|
53
|
+
fatal { e }
|
54
|
+
stop
|
55
|
+
exit(1)
|
56
|
+
end
|
57
|
+
# rubocop:enable Style/GlobalVars
|
58
|
+
|
59
|
+
def container = @container ||= Dry::Container.new
|
60
|
+
def run! = nil
|
61
|
+
def container_config = {}
|
62
|
+
|
63
|
+
def stop
|
64
|
+
@task&.stop
|
65
|
+
info { "Stopped" }
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def set_traps!
|
71
|
+
trap("INT") do
|
72
|
+
force_exit! if @stopping
|
73
|
+
@stopping = true
|
74
|
+
warn { "Interrupted, stopping. Press ^C once more to force exit." }
|
75
|
+
stop
|
76
|
+
end
|
77
|
+
|
78
|
+
trap("TERM") { stop }
|
79
|
+
end
|
80
|
+
|
81
|
+
def force_exit!
|
82
|
+
fatal { "Forced exit" }
|
83
|
+
exit(1)
|
84
|
+
end
|
85
|
+
end
|
data/lib/async/bus.rb
CHANGED
@@ -1,12 +1,47 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
class Async::Bus
|
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
|
16
|
+
|
17
|
+
# BLOCKING unless subscribers run in tasks
|
18
|
+
def publish(name, *args, **params)
|
19
|
+
@w.register_event(name)
|
20
|
+
@w.publish(name, payload: (args.first || params))
|
21
|
+
rescue StandardError => e
|
22
|
+
log_error(name, e)
|
23
|
+
end
|
24
|
+
|
25
|
+
# NON-BLOCKING
|
26
|
+
def subscribe(name)
|
27
|
+
@w.register_event(name)
|
28
|
+
@w.subscribe(name) { yield(_1[:payload]) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# NON-BLOCKING, runs subscriber in a task
|
32
|
+
def async_subscribe(name, parent: Async::Task.current)
|
33
|
+
subscribe(name) do |event|
|
34
|
+
parent.async do
|
35
|
+
yield(event)
|
36
|
+
rescue StandardError => e
|
37
|
+
log_error(name, e)
|
38
|
+
end
|
10
39
|
end
|
11
40
|
end
|
41
|
+
|
42
|
+
def convert(from_event, to_event) = subscribe(from_event) { publish(to_event, **yield(_1)) }
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def log_error(name, e) = warn("Subscriber for #{name.inspect} failed with exception.", e)
|
12
47
|
end
|
data/lib/async/logger.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Async::Logger
|
4
|
+
[:debug, :info, :warn, :error, :fatal].each do |name|
|
5
|
+
define_method(name) do |*args, &block|
|
6
|
+
info = respond_to?(:logger_info, true) ? logger_info : nil
|
7
|
+
|
8
|
+
Console.logger.public_send(name, self, info, *args, &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/async/tools/version.rb
CHANGED
data/lib/async/tools.rb
CHANGED
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.1
|
4
|
+
version: 0.2.1
|
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-
|
11
|
+
date: 2023-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async
|
@@ -60,9 +60,10 @@ files:
|
|
60
60
|
- async-tools.gemspec
|
61
61
|
- bin/console
|
62
62
|
- bin/setup
|
63
|
+
- lib/async/app.rb
|
63
64
|
- lib/async/bus.rb
|
64
|
-
- lib/async/bus/bus.rb
|
65
65
|
- lib/async/channel.rb
|
66
|
+
- lib/async/logger.rb
|
66
67
|
- lib/async/q.rb
|
67
68
|
- lib/async/result_notification.rb
|
68
69
|
- lib/async/timer.rb
|
data/lib/async/bus/bus.rb
DELETED
@@ -1,95 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Async::Bus::Bus
|
4
|
-
attr_reader :name
|
5
|
-
|
6
|
-
include Console
|
7
|
-
|
8
|
-
def initialize(name: :default, limit: 10, parent: Async::Task.current)
|
9
|
-
@name = name
|
10
|
-
@limit = limit
|
11
|
-
@parent = parent
|
12
|
-
|
13
|
-
@closed = false
|
14
|
-
|
15
|
-
@subscribers = Hash.new { |hash, key| hash[key] = [] }
|
16
|
-
end
|
17
|
-
|
18
|
-
# Blocks if any of output channels is full!
|
19
|
-
def publish(nameable_or_event, **payload)
|
20
|
-
check_if_open!
|
21
|
-
name, payload = normalize(nameable_or_event, payload)
|
22
|
-
|
23
|
-
subs = @subscribers[name]
|
24
|
-
return if subs.empty?
|
25
|
-
|
26
|
-
subs.each do |chan|
|
27
|
-
publishing_blocked if chan.full?
|
28
|
-
chan << [name, payload]
|
29
|
-
end
|
30
|
-
Async::Task.current.yield
|
31
|
-
end
|
32
|
-
|
33
|
-
# Blocks!
|
34
|
-
def subscribe(nameable, callable = nil, &block)
|
35
|
-
check_if_open!
|
36
|
-
callable ||= block
|
37
|
-
unless callable.respond_to?(:call)
|
38
|
-
raise ArgumentError, "callable or block must be provided. callable must respond to :call"
|
39
|
-
end
|
40
|
-
|
41
|
-
event_name = normalize(nameable).first
|
42
|
-
|
43
|
-
chan = Async::Channel.new(@limit)
|
44
|
-
@subscribers[event_name] << chan
|
45
|
-
serve(chan, event_name, callable)
|
46
|
-
end
|
47
|
-
|
48
|
-
def async_subscribe(*, **, &) = @parent.async { subscribe(*, **, &) }
|
49
|
-
def on_event(&block) = @on_event_callback = block
|
50
|
-
|
51
|
-
def close
|
52
|
-
return if @closed
|
53
|
-
|
54
|
-
@closed = true
|
55
|
-
|
56
|
-
@subscribers.values.flatten.each(&:close)
|
57
|
-
@subscribers.clear
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def normalize(nameable, payload = nil)
|
63
|
-
return [nameable, payload] if nameable.is_a?(Symbol)
|
64
|
-
return [nameable.to_sym, payload] if nameable.is_a?(String)
|
65
|
-
return [nameable.event_name.to_sym, nameable] if nameable.respond_to?(:event_name)
|
66
|
-
|
67
|
-
n = nameable[:event_name] || nameable["event_name"]
|
68
|
-
return [n.to_sym, nameable] if n
|
69
|
-
|
70
|
-
raise ArgumentError, "cannot infer event name from #{nameable.inspect}"
|
71
|
-
end
|
72
|
-
|
73
|
-
def serve(chan, event_name, callable)
|
74
|
-
stopped = false
|
75
|
-
unsub = lambda {
|
76
|
-
chan.close
|
77
|
-
stopped = true
|
78
|
-
@subscribers[event_name].delete(chan)
|
79
|
-
}
|
80
|
-
|
81
|
-
chan.each do |name, payload|
|
82
|
-
@on_event_callback&.call(wrapper)
|
83
|
-
callable.call(payload, unsub:, meta: { bus: self })
|
84
|
-
break if stopped
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def publishing_blocked
|
89
|
-
logger.warn(self) { "One of the subscribers is slow, blocking publishing. Event name: #{name}" }
|
90
|
-
end
|
91
|
-
|
92
|
-
def check_if_open!
|
93
|
-
raise "Bus is closed" if @closed
|
94
|
-
end
|
95
|
-
end
|