onyxcord 1.1.0 → 1.1.3

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: f5d2ba7d4563edb0e1a0e1e19706759c987f775d31bf75e5e21600fa99b9fb7c
4
- data.tar.gz: 92a280220aff31be1a36e526309193a4269437f2845fc409e3fba6cedef19d81
3
+ metadata.gz: 86ca3fde0fa78c2cee74a2d481d82aaa7f70d4988cb16d9af7dec046162e93cd
4
+ data.tar.gz: c7a9ced4d4f29a8d4a03a765bea9f9779ab3662c55bf23872fc75b1abbe1d0a3
5
5
  SHA512:
6
- metadata.gz: 580c7ae99d795b2c12a823f03d0e1097f83834141c180321994e6b7ae903e57ad0fc24b5d797f46c8431ce1816af3f35d7ba9a5aa0a2fa137cab0a5e0f4780c2
7
- data.tar.gz: 051cabf28b31f5b92c93a6fd28397f8b171510cd25d946c9f9ac55913a4cfe282bf2753d7881f6712f0ffe35cb56127388b8cec12f508b542c27d4ceb0daed74
6
+ metadata.gz: a095fe07369dde14d2a59d2d3562f7a04a6ee8665a0dcd00f629c19dd85065a029a2c02838d2e2acc106536f89734281551dc5b89533209bd51ab080183e89e4
7
+ data.tar.gz: 9086e3c2e05157280aa58d65c5f177f1e1916c1861aa666badf2aa30fe6df8768e89ac8f4f22edc02441ee5067a1cc78370ac13ea087e4323fb9a0705e877d27
data/CHANGELOG.md CHANGED
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ ## 1.1.3 - 2026-06-23
4
+
5
+ ### Melhorias
6
+
7
+ - Alterado o modo padrao do bot para `:hybrid`, mantendo suporte a handlers raw e eventos em objeto sem configuracao extra.
8
+ - Adicionado `event_queue_size` para permitir fila de eventos com limite via `SizedQueue`.
9
+ - Application commands agora usam o `EventExecutor`, evitando criacao direta de threads por interaction.
10
+ - Adicionados `runtime_stats`, `cache_stats`, `prune_cache!` e `OnyxCord::API.rate_limiter_stats` para diagnostico de memoria e runtime.
11
+ - Rate limiter REST agora expoe `stats`, `prune!` e limpeza automatica de bookkeeping antigo.
12
+ - Voice recebeu limpeza mais segura de UDP, WebSocket, threads e leitura DCA com fechamento garantido de arquivo.
13
+ - Dependencias principais receberam upper bounds conservadores para reduzir risco de quebra em releases futuras.
14
+
15
+ ### Correcoes
16
+
17
+ - Corrigido retry de respostas REST `202` para reutilizar a rota e o major parameter originais.
18
+ - Removidos logs temporarios de interaction no caminho quente de dispatch.
19
+ - Corrigido warning de spec causado por expectativa em cache de usuarios nulo.
20
+
21
+ ### Validacao
22
+
23
+ - `bundle exec rspec`: 456 exemplos, 0 falhas, 3 pendentes.
24
+ - RuboCop nos arquivos alterados: sem offenses.
25
+ - `gem build onyxcord.gemspec`: sucesso.
26
+ - `gem build onyxcord-webhooks.gemspec`: sucesso.
data/README.md CHANGED
@@ -103,8 +103,21 @@ end
103
103
  bot.run
104
104
  ```
105
105
 
106
- Use `mode: :raw` quando quiser evitar a criação de objetos pesados por padrão e processar apenas os pacotes de Gateway diretamente.
107
- **Importante:** O `OnyxCord` roda em `:raw` por padrão. Se você quiser usar os comandos de aplicação normais (`application_command`), modais e outros eventos em objeto, **você deve inicializar o bot com `mode: :hybrid`** ou `mode: :object`!
106
+ Use `mode: :raw` quando quiser evitar a criação de objetos pesados e processar apenas os pacotes de Gateway diretamente.
107
+ **Importante:** O `OnyxCord` roda em `:hybrid` por padrão, entao comandos de aplicação normais (`application_command`), modais e outros eventos em objeto ja funcionam sem precisar informar `mode: :hybrid`.
108
+
109
+ ## Performance e memória
110
+
111
+ Para limitar crescimento de fila quando handlers ficam lentos, configure um tamanho maximo para a fila do executor:
112
+
113
+ ```ruby
114
+ OnyxCord.configure do |config|
115
+ config.event_workers = 4
116
+ config.event_queue_size = 1_000
117
+ end
118
+ ```
119
+
120
+ Em bots grandes, use `bot.runtime_stats`, `bot.cache_stats` e `bot.prune_cache!` para acompanhar e limpar caches em runtime.
108
121
 
109
122
  ## Components V2
110
123
 
data/lib/onyxcord/api.rb CHANGED
@@ -67,6 +67,10 @@ module OnyxCord::API
67
67
  @rate_limiter ||= OnyxCord::RateLimiter::Rest.new
68
68
  end
69
69
 
70
+ def rate_limiter_stats
71
+ rate_limiter.stats
72
+ end
73
+
70
74
  # Wait a specified amount of time synchronised with the specified mutex.
71
75
  def sync_wait(time, mutex)
72
76
  mutex.synchronize { sleep time }
@@ -151,7 +155,7 @@ module OnyxCord::API
151
155
  sleep(body['retry_after'])
152
156
  end
153
157
 
154
- return request(*key, type, *attributes)
158
+ return request(key, major_parameter, type, *attributes)
155
159
  end
156
160
  end
157
161
 
data/lib/onyxcord/bot.rb CHANGED
@@ -132,13 +132,14 @@ module OnyxCord
132
132
  type: nil, name: '', fancy_log: false, suppress_ready: false, parse_self: false,
133
133
  shard_id: nil, num_shards: nil, redact_token: true, ignore_bots: false,
134
134
  compress_mode: :large, intents: :minimal,
135
- mode: nil, cache: nil, event_executor: nil, event_workers: nil
135
+ mode: nil, cache: nil, event_executor: nil, event_workers: nil, event_queue_size: nil
136
136
  )
137
137
  config = OnyxCord.configuration
138
138
  @mode = config.normalize_mode(mode)
139
139
  @cache_policy = config.normalize_cache(cache)
140
140
  executor_type = config.normalize_event_executor(event_executor)
141
141
  executor_workers = config.normalize_event_workers(event_workers)
142
+ executor_queue_size = config.normalize_event_queue_size(event_queue_size)
142
143
 
143
144
  LOGGER.mode = log_mode
144
145
  LOGGER.token = token if redact_token
@@ -185,7 +186,7 @@ module OnyxCord
185
186
 
186
187
  @current_thread = 0
187
188
  @current_thread_mutex = Mutex.new
188
- @event_executor = EventExecutor.build(executor_type, workers: executor_workers)
189
+ @event_executor = EventExecutor.build(executor_type, workers: executor_workers, queue_size: executor_queue_size)
189
190
  @event_threads = @event_executor.threads
190
191
 
191
192
  @status = :online
@@ -712,6 +713,16 @@ module OnyxCord
712
713
  LOGGER.mode = new_mode
713
714
  end
714
715
 
716
+ def runtime_stats
717
+ {
718
+ mode: @mode,
719
+ cache: cache_stats,
720
+ event_executor: @event_executor.class.name,
721
+ event_threads: @event_threads&.count(&:alive?) || 0,
722
+ event_queue_size: @event_executor.respond_to?(:queue_size) ? @event_executor.queue_size : 0
723
+ }
724
+ end
725
+
715
726
  # Prevents the READY packet from being printed regardless of debug mode.
716
727
  def suppress_ready_debug
717
728
  @prevent_ready = true
@@ -1677,30 +1688,19 @@ module OnyxCord
1677
1688
 
1678
1689
  raise_event(event)
1679
1690
  when :INTERACTION_CREATE
1680
- OnyxCord::LOGGER.info(">>> INTERACTION_CREATE received inside bot.rb! type: #{data['type']}")
1681
1691
  event = InteractionCreateEvent.new(data, self)
1682
1692
  raise_event(event)
1683
- OnyxCord::LOGGER.info(">>> raised InteractionCreateEvent successfully")
1684
1693
 
1685
1694
  case data['type']
1686
1695
  when Interaction::TYPES[:command]
1687
- OnyxCord::LOGGER.info(">>> creating ApplicationCommandEvent")
1688
1696
  event = ApplicationCommandEvent.new(data, self)
1689
- OnyxCord::LOGGER.info(">>> spawned Thread to execute command")
1690
-
1691
- Thread.new(event) do |evt|
1692
- Thread.current[:onyxcord_name] = "it-#{evt.interaction.id}"
1693
-
1694
- begin
1695
- OnyxCord::LOGGER.info(">>> Executing application command #{evt.command_name}:#{evt.command_id}")
1696
- handler = @application_commands[evt.command_name]
1697
- OnyxCord::LOGGER.info(">>> Handler found? #{!handler.nil?}")
1698
- handler&.call(evt)
1699
- OnyxCord::LOGGER.info(">>> Handler call finished")
1700
- rescue Exception => e
1701
- OnyxCord::LOGGER.error(">>> FATAL EXCEPTION IN THREAD: #{e.class}: #{e.message}")
1702
- log_exception(e)
1703
- end
1697
+
1698
+ @event_executor.post do
1699
+ Thread.current[:onyxcord_name] = next_event_thread_name('it')
1700
+ handler = @application_commands[event.command_name]
1701
+ handler&.call(event)
1702
+ rescue StandardError => e
1703
+ log_exception(e)
1704
1704
  end
1705
1705
  when Interaction::TYPES[:component]
1706
1706
  case data['data']['component_type']
@@ -12,6 +12,17 @@ module OnyxCord
12
12
  # the caching (like, storing the user hashes or making API calls to retrieve things) from the Bot that
13
13
  # actually uses it.
14
14
  module Cache
15
+ CACHE_STORES = {
16
+ users: :@users,
17
+ voice_regions: :@voice_regions,
18
+ servers: :@servers,
19
+ channels: :@channels,
20
+ pm_channels: :@pm_channels,
21
+ thread_members: :@thread_members,
22
+ server_previews: :@server_previews,
23
+ request_members: :@request_members_rl
24
+ }.freeze
25
+
15
26
  # Initializes this cache
16
27
  def init_cache
17
28
  @cache_policy ||= OnyxCord.configuration.normalize_cache(:full)
@@ -32,6 +43,24 @@ module OnyxCord
32
43
  @cache_policy.fetch(key, true)
33
44
  end
34
45
 
46
+ def cache_stats
47
+ CACHE_STORES.each_with_object({}) do |(key, ivar), stats|
48
+ store = instance_variable_get(ivar)
49
+ stats[key] = store.respond_to?(:size) ? store.size : 0
50
+ end
51
+ end
52
+
53
+ def prune_cache!(*keys)
54
+ keys = CACHE_STORES.keys if keys.empty?
55
+
56
+ keys.each_with_object({}) do |key, pruned|
57
+ ivar = CACHE_STORES.fetch(key)
58
+ store = instance_variable_get(ivar)
59
+ pruned[key] = store.respond_to?(:size) ? store.size : 0
60
+ store&.clear
61
+ end
62
+ end
63
+
35
64
  # Returns or caches the available voice regions
36
65
  def voice_regions
37
66
  return fetch_voice_regions unless cache_enabled?(:voice_regions)
@@ -43,13 +43,14 @@ module OnyxCord
43
43
  }
44
44
  }.freeze
45
45
 
46
- attr_accessor :mode, :cache, :event_executor, :event_workers
46
+ attr_accessor :mode, :cache, :event_executor, :event_workers, :event_queue_size
47
47
 
48
48
  def initialize
49
- @mode = :raw
49
+ @mode = :hybrid
50
50
  @cache = :none
51
51
  @event_executor = :pool
52
52
  @event_workers = 4
53
+ @event_queue_size = nil
53
54
  end
54
55
 
55
56
  def dup
@@ -58,6 +59,7 @@ module OnyxCord
58
59
  copy.cache = @cache.is_a?(Hash) ? @cache.dup : @cache
59
60
  copy.event_executor = @event_executor
60
61
  copy.event_workers = @event_workers
62
+ copy.event_queue_size = @event_queue_size
61
63
  copy
62
64
  end
63
65
 
@@ -82,6 +84,15 @@ module OnyxCord
82
84
  workers
83
85
  end
84
86
 
87
+ def normalize_event_queue_size(value = @event_queue_size)
88
+ return nil if value.nil?
89
+
90
+ size = Integer(value)
91
+ raise ArgumentError, 'event_queue_size must be greater than zero' unless size.positive?
92
+
93
+ size
94
+ end
95
+
85
96
  def normalize_cache(value = @cache)
86
97
  cache = value.nil? ? @cache : value
87
98
 
@@ -20,12 +20,12 @@ module OnyxCord
20
20
 
21
21
  # Fixed-size worker pool for event handlers.
22
22
  class Pool
23
- attr_reader :threads
23
+ attr_reader :threads, :queue
24
24
 
25
- def initialize(size:)
25
+ def initialize(size:, queue_size: nil)
26
26
  raise ArgumentError, 'Pool size must be greater than zero' unless size.positive?
27
27
 
28
- @queue = Queue.new
28
+ @queue = queue_size ? SizedQueue.new(queue_size) : Queue.new
29
29
  @closed = false
30
30
  @threads = Array.new(size) do |index|
31
31
  Thread.new do
@@ -42,6 +42,10 @@ module OnyxCord
42
42
  @queue << block
43
43
  end
44
44
 
45
+ def queue_size
46
+ @queue.size
47
+ end
48
+
45
49
  def shutdown
46
50
  return if @closed
47
51
 
@@ -66,12 +70,12 @@ module OnyxCord
66
70
 
67
71
  module_function
68
72
 
69
- def build(type, workers:)
73
+ def build(type, workers:, queue_size: nil)
70
74
  case type
71
75
  when :inline
72
76
  Inline.new
73
77
  when :pool
74
- Pool.new(size: workers)
78
+ Pool.new(size: workers, queue_size: queue_size)
75
79
  else
76
80
  raise ArgumentError, "Unknown event executor: #{type.inspect}"
77
81
  end
@@ -7,10 +7,18 @@ module OnyxCord
7
7
  # Discord REST rate limiter keyed by route/major parameter and remapped to
8
8
  # X-RateLimit-Bucket whenever Discord returns a concrete bucket id.
9
9
  class Rest
10
- def initialize
10
+ DEFAULT_ENTRY_TTL = 3600
11
+ DEFAULT_PRUNE_INTERVAL = 100
12
+
13
+ def initialize(clock: -> { Time.now }, entry_ttl: DEFAULT_ENTRY_TTL, prune_interval: DEFAULT_PRUNE_INTERVAL)
11
14
  @route_buckets = {}
12
15
  @bucket_mutexes = {}
16
+ @bucket_last_used = {}
13
17
  @global_mutex = Mutex.new
18
+ @clock = clock
19
+ @entry_ttl = entry_ttl
20
+ @prune_interval = prune_interval
21
+ @requests_since_prune = 0
14
22
  end
15
23
 
16
24
  def before_request(route, major_parameter)
@@ -21,8 +29,14 @@ module OnyxCord
21
29
  def record_response(route, major_parameter, headers)
22
30
  headers = normalize_headers(headers)
23
31
  bucket = headers[:x_ratelimit_bucket]
32
+ key = route_key(route, major_parameter)
33
+ touch(key)
24
34
 
25
- @route_buckets[route_key(route, major_parameter)] = bucket_key(bucket, major_parameter) if bucket
35
+ if bucket
36
+ bucket = bucket_key(bucket, major_parameter)
37
+ @route_buckets[key] = bucket
38
+ touch(bucket)
39
+ end
26
40
 
27
41
  return unless headers[:x_ratelimit_remaining] == '0'
28
42
 
@@ -40,10 +54,37 @@ module OnyxCord
40
54
  sync_wait(wait_seconds, mutex) if wait_seconds.positive?
41
55
  end
42
56
 
57
+ def stats
58
+ {
59
+ route_buckets: @route_buckets.size,
60
+ bucket_mutexes: @bucket_mutexes.size,
61
+ tracked_keys: @bucket_last_used.size
62
+ }
63
+ end
64
+
65
+ def prune!
66
+ return 0 unless @entry_ttl
67
+
68
+ cutoff = @clock.call - @entry_ttl
69
+ stale_keys = @bucket_last_used.select { |_, last_used| last_used < cutoff }.keys
70
+
71
+ stale_keys.each do |key|
72
+ @bucket_mutexes.delete(key)
73
+ @bucket_last_used.delete(key)
74
+ @route_buckets.delete(key)
75
+ @route_buckets.delete_if { |_, bucket_key| bucket_key == key }
76
+ end
77
+
78
+ @requests_since_prune = 0
79
+ stale_keys.length
80
+ end
81
+
43
82
  private
44
83
 
45
84
  def mutex_for(route, major_parameter)
46
- @bucket_mutexes[resolved_key(route, major_parameter)] ||= Mutex.new
85
+ key = resolved_key(route, major_parameter)
86
+ touch(key)
87
+ @bucket_mutexes[key] ||= Mutex.new
47
88
  end
48
89
 
49
90
  def resolved_key(route, major_parameter)
@@ -76,6 +117,18 @@ module OnyxCord
76
117
  end
77
118
  end
78
119
 
120
+ def touch(key)
121
+ @bucket_last_used[key] = @clock.call
122
+ prune_if_needed
123
+ end
124
+
125
+ def prune_if_needed
126
+ return unless @prune_interval
127
+
128
+ @requests_since_prune += 1
129
+ prune! if @requests_since_prune >= @prune_interval
130
+ end
131
+
79
132
  def sync_wait(time, mutex)
80
133
  mutex.synchronize { sleep time }
81
134
  end
@@ -3,5 +3,5 @@
3
3
  # OnyxCord and all its functionality, in this case only the version.
4
4
  module OnyxCord
5
5
  # The current version of onyxcord.
6
- VERSION = '1.1.0'
6
+ VERSION = '1.1.3'
7
7
  end
@@ -113,6 +113,10 @@ module OnyxCord::Voice
113
113
  send_packet(discovery_packet)
114
114
  end
115
115
 
116
+ def close
117
+ @socket.close unless @socket.closed?
118
+ end
119
+
116
120
  private
117
121
 
118
122
  # Encrypts audio data using libsodium
@@ -339,8 +343,12 @@ module OnyxCord::Voice
339
343
  end
340
344
 
341
345
  # Disconnects the websocket and kills the thread
342
- def destroy
346
+ def destroy(join_timeout = 1)
343
347
  @heartbeat_running = false
348
+ @client&.close
349
+ @udp.close
350
+ @thread&.join(join_timeout)
351
+ @thread&.kill if @thread&.alive?
344
352
  end
345
353
 
346
354
  private
@@ -265,36 +265,37 @@ module OnyxCord::Voice
265
265
  stop_playing(true) if @playing
266
266
 
267
267
  @bot.debug "Reading DCA file #{file}"
268
- input_stream = File.open(file)
269
268
 
270
- magic = input_stream.read(4)
271
- raise ArgumentError, 'Not a DCA1 file! The file might have been corrupted, please recreate it.' unless magic == 'DCA1'
269
+ File.open(file) do |input_stream|
270
+ magic = input_stream.read(4)
271
+ raise ArgumentError, 'Not a DCA1 file! The file might have been corrupted, please recreate it.' unless magic == 'DCA1'
272
272
 
273
- # Read the metadata header, then read the metadata and discard it as we don't care about it
274
- metadata_header = input_stream.read(4).unpack1('l<')
275
- input_stream.read(metadata_header)
273
+ # Read the metadata header, then read the metadata and discard it as we don't care about it
274
+ metadata_header = input_stream.read(4).unpack1('l<')
275
+ input_stream.read(metadata_header)
276
276
 
277
- # Play the data, without re-encoding it to opus
278
- play_internal do
279
- begin
280
- # Read header
281
- header_str = input_stream.read(2)
277
+ # Play the data, without re-encoding it to opus
278
+ play_internal do
279
+ begin
280
+ # Read header
281
+ header_str = input_stream.read(2)
282
+
283
+ unless header_str
284
+ @bot.debug 'Finished DCA parsing (header is nil)'
285
+ next :stop
286
+ end
287
+
288
+ header = header_str.unpack1('s<')
282
289
 
283
- unless header_str
284
- @bot.debug 'Finished DCA parsing (header is nil)'
290
+ raise 'Negative header in DCA file! Your file is likely corrupted.' if header.negative?
291
+ rescue EOFError
292
+ @bot.debug 'Finished DCA parsing (EOFError)'
285
293
  next :stop
286
294
  end
287
295
 
288
- header = header_str.unpack1('s<')
289
-
290
- raise 'Negative header in DCA file! Your file is likely corrupted.' if header.negative?
291
- rescue EOFError
292
- @bot.debug 'Finished DCA parsing (EOFError)'
293
- next :stop
296
+ # Read bytes
297
+ input_stream.read(header)
294
298
  end
295
-
296
- # Read bytes
297
- input_stream.read(header)
298
299
  end
299
300
  end
300
301
 
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
19
  spec.require_paths = ['lib']
20
20
 
21
- spec.add_dependency 'rest-client', '>= 2.0.0'
21
+ spec.add_dependency 'rest-client', '>= 2.0.0', '< 3'
22
22
 
23
23
  spec.required_ruby_version = '>= 3.3'
24
24
  spec.metadata = {
data/onyxcord.gemspec CHANGED
@@ -27,10 +27,10 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ['lib']
28
28
 
29
29
  spec.add_dependency 'base64', '~> 0.2'
30
- spec.add_dependency 'ffi', '>= 1.9.24'
31
- spec.add_dependency 'opus-ruby'
32
- spec.add_dependency 'rest-client', '>= 2.0.0'
33
- spec.add_dependency 'websocket-client-simple', '>= 0.9.0'
30
+ spec.add_dependency 'ffi', '>= 1.9.24', '< 2'
31
+ spec.add_dependency 'opus-ruby', '>= 0', '< 2'
32
+ spec.add_dependency 'rest-client', '>= 2.0.0', '< 3'
33
+ spec.add_dependency 'websocket-client-simple', '>= 0.9.0', '< 1'
34
34
 
35
35
  spec.add_dependency 'onyxcord-webhooks', "~> #{OnyxCord::Webhooks::VERSION}"
36
36
 
data/relator.md ADDED
@@ -0,0 +1,298 @@
1
+ # Relatorio geral da OnyxCord
2
+
3
+ Data da analise: 2026-06-23
4
+
5
+ Escopo analisado:
6
+ - Gem Ruby `onyxcord` e `onyxcord-webhooks`.
7
+ - Areas principais: REST API, gateway WebSocket, cache, executor de eventos, comandos/interactions, voice e webhooks.
8
+ - Teste executado: `bundle exec rspec`.
9
+
10
+ Resultado da validacao atual:
11
+ - `bundle exec rspec`: 456 exemplos, 0 falhas, 3 pendentes.
12
+ - Cobertura reportada pelo SimpleCov: 60,84% de linhas.
13
+ - `gem build onyxcord.gemspec`: sucesso, gerou `onyxcord-1.1.2.gem`.
14
+ - `gem build onyxcord-webhooks.gemspec`: sucesso, gerou `onyxcord-webhooks-1.1.2.gem`.
15
+
16
+ Status das correcoes aplicadas:
17
+ - Corrigido retry REST `202` preservando route e major parameter.
18
+ - Application commands agora executam pelo `EventExecutor`, sem `Thread.new` direto nesse caminho.
19
+ - Adicionado `event_queue_size` com `SizedQueue` opcional.
20
+ - Adicionados `runtime_stats`, `cache_stats`, `prune_cache!` e `OnyxCord::API.rate_limiter_stats`.
21
+ - Rate limiter REST agora tem `stats`, `prune!` e limpeza automatica de bookkeeping antigo.
22
+ - Voice fecha melhor recursos de UDP/WebSocket/thread, e `play_dca` usa `File.open` com bloco.
23
+ - Dependencias principais receberam upper bounds conservadores.
24
+ - Warning de spec em `spec/bot_spec.rb:114` foi corrigido.
25
+
26
+ ## Resumo executivo
27
+
28
+ A lib ja tem algumas decisoes boas para performance:
29
+ - O modo padrao do bot agora e `hybrid`, equilibrando handlers raw com eventos em objeto.
30
+ - O cache padrao global esta em `:none`, o que ajuda a reduzir RAM para bots pequenos.
31
+ - Ja existe `EventExecutor::Pool`, evitando uma thread nova para cada evento comum.
32
+ - O rate limiter REST ja centraliza buckets, global limit e `retry_after`.
33
+ - O gateway usa zlib stream, economizando trafego e CPU em payloads grandes.
34
+
35
+ Os maiores ganhos agora estao em 5 frentes:
36
+ 1. Eliminar threads soltas em interactions e waits.
37
+ 2. Colocar limite/backpressure no executor de eventos.
38
+ 3. Colocar estrategia de limite/limpeza nos caches e nos mapas do rate limiter.
39
+ 4. Corrigir um bug provavel no retry de resposta REST `202`.
40
+ 5. Fechar recursos de voz/arquivos/sockets de forma garantida.
41
+
42
+ ## Prioridade alta
43
+
44
+ ### 1. Corrigir retry de REST `202` em `OnyxCord::API.request`
45
+
46
+ Arquivo: `lib/onyxcord/api.rb:141-154`
47
+
48
+ Problema:
49
+ - Quando Discord retorna `202` com codigo `110000`, o metodo tenta repetir a request.
50
+ - A chamada atual usa `return request(*key, type, *attributes)`.
51
+ - Na maior parte da API, `key` e um `Symbol`, entao `*key` tende a quebrar com `TypeError` ou chamar `request` com parametros errados.
52
+
53
+ Impacto:
54
+ - Endpoints baseados em Elasticsearch podem falhar justamente no fluxo em que deveriam aguardar e tentar de novo.
55
+
56
+ Sugestao:
57
+ - Trocar para `return request(key, major_parameter, type, *attributes)`.
58
+ - Adicionar spec cobrindo response `202` com `retry_after`.
59
+
60
+ ### 2. Application commands criam `Thread.new` fora do executor
61
+
62
+ Arquivo: `lib/onyxcord/bot.rb:1679-1704`
63
+
64
+ Problema:
65
+ - O fluxo de `INTERACTION_CREATE` para command cria uma thread direta por comando.
66
+ - Isso ignora `EventExecutor::Pool`, ignora `event_workers` e remove qualquer controle de concorrencia.
67
+ - Tambem ha logs temporarios com prefixo `>>>` em caminho quente.
68
+ - O rescue usa `rescue Exception`, que captura sinais de sistema e saidas do processo.
69
+
70
+ Impacto:
71
+ - Em pico de interactions, o processo pode criar muitas threads, consumindo RAM e escalonamento de CPU.
72
+ - Logs verbosos em production aumentam I/O e custo de CPU.
73
+
74
+ Sugestao:
75
+ - Executar handler via `@event_executor.post`.
76
+ - Usar `rescue StandardError`.
77
+ - Trocar logs `info` temporarios por `debug` ou remover.
78
+ - Nomear a thread dentro do bloco do executor, como ja acontece em `call_event`.
79
+
80
+ ### 3. Fila de eventos sem limite
81
+
82
+ Arquivo: `lib/onyxcord/event_executor.rb:28-42`
83
+
84
+ Problema:
85
+ - `Queue.new` e ilimitada.
86
+ - Se os handlers forem mais lentos que os eventos recebidos, a fila cresce sem backpressure.
87
+
88
+ Impacto:
89
+ - Pode virar crescimento progressivo de RAM em servidores grandes ou bots com handlers pesados.
90
+
91
+ Sugestao:
92
+ - Adicionar opcao `event_queue_size`, usando `SizedQueue`.
93
+ - Expor comportamento configuravel: bloquear, rejeitar com log, ou executar inline em emergencia.
94
+ - Medir tamanho da fila em debug/telemetria.
95
+
96
+ ## Prioridade media
97
+
98
+ ### 4. Rate limiter guarda mutexes e buckets para sempre
99
+
100
+ Arquivo: `lib/onyxcord/rate_limiter/rest.rb:10-47`
101
+
102
+ Problema:
103
+ - `@route_buckets` e `@bucket_mutexes` crescem conforme novas rotas/major parameters aparecem.
104
+ - Para bots que tocam muitos canais, guilds, mensagens ou webhooks, isso pode acumular.
105
+
106
+ Impacto:
107
+ - RAM pequena por item, mas permanente.
108
+
109
+ Sugestao:
110
+ - Guardar `last_used_at` por bucket e limpar entradas antigas.
111
+ - Alternativa simples: limitar por LRU.
112
+ - Adicionar metodo `prune!` chamado ocasionalmente em `record_response`.
113
+
114
+ ### 5. Cache full pode crescer sem limite
115
+
116
+ Arquivo: `lib/onyxcord/cache.rb:16-29`
117
+
118
+ Problema:
119
+ - Caches de users, channels, pm_channels, thread_members e server_previews sao Hashes sem TTL/max size.
120
+ - O default global e `:none`, mas quem usa `:full` pode segurar muitos objetos.
121
+
122
+ Impacto:
123
+ - Em bots grandes, memoria cresce com o tempo e dificilmente volta.
124
+
125
+ Sugestao:
126
+ - Manter `:none` como default.
127
+ - Adicionar opcoes por cache: `max_users`, `max_channels`, `max_messages`, `ttl`.
128
+ - Oferecer `bot.prune_cache!` e `bot.cache_stats`.
129
+ - Considerar guardar payload cru em modo leve, criando objeto sob demanda.
130
+
131
+ ### 6. `request_chunks` cria buckets por guild sem limpeza
132
+
133
+ Arquivo: `lib/onyxcord/cache.rb:235-253`
134
+
135
+ Problema:
136
+ - `@request_members_rl[id]` guarda mutex/time por guild e nunca remove.
137
+
138
+ Impacto:
139
+ - Baixo por guild, mas permanente em bots que entram/saem de muitos servidores.
140
+
141
+ Sugestao:
142
+ - Remover no evento de saida de guild.
143
+ - Limpar buckets nao usados ha alguns minutos/horas.
144
+
145
+ ### 7. Voice pode deixar arquivo aberto em `play_dca`
146
+
147
+ Arquivo: `lib/onyxcord/voice/voice_bot.rb:264-299`
148
+
149
+ Problema:
150
+ - `File.open(file)` nao usa bloco nem `ensure`.
151
+ - Se erro ocorrer durante validacao ou playback, o descritor pode ficar aberto.
152
+
153
+ Impacto:
154
+ - Vazamento de file descriptor em uso repetido de voz.
155
+
156
+ Sugestao:
157
+ - Usar `File.open(file) do |input_stream| ... end` ou `ensure input_stream&.close`.
158
+
159
+ ### 8. Voice WebSocket nao fecha/junta thread explicitamente
160
+
161
+ Arquivo: `lib/onyxcord/voice/network.rb:321-344`
162
+
163
+ Problema:
164
+ - `destroy` apenas seta `@heartbeat_running = false`.
165
+ - Nao fecha o WebSocket, nao fecha UDP e nao faz join da thread.
166
+
167
+ Impacto:
168
+ - Possivel sobra de thread/socket em reconexoes ou destroy repetido.
169
+
170
+ Sugestao:
171
+ - Implementar close de `@client`, close de UDP socket e `@thread.join` com timeout curto.
172
+ - Adicionar spec com fake socket/client garantindo cleanup.
173
+
174
+ ### 9. Busy wait com `sleep` em pontos sensiveis
175
+
176
+ Arquivos:
177
+ - `lib/onyxcord/voice/network.rb:338`
178
+ - `lib/onyxcord/voice/voice_bot.rb:315`
179
+ - `lib/onyxcord/bot.rb:413`
180
+
181
+ Problema:
182
+ - Loops `sleep until` e `sleep while` sao simples, mas acordam periodicamente sem evento real.
183
+
184
+ Impacto:
185
+ - Baixo em poucos bots, mas piora com muitas conexoes/threads.
186
+
187
+ Sugestao:
188
+ - Usar `ConditionVariable` para readiness/pausa.
189
+ - Para voice playback, manter cuidado para nao prejudicar o timing de audio.
190
+
191
+ ## Prioridade baixa / limpeza
192
+
193
+ ### 10. Webhooks nao usam o rate limiter central
194
+
195
+ Arquivo: `lib/onyxcord/webhooks/client.rb`
196
+
197
+ Problema:
198
+ - Chamadas usam `RestClient.post/patch/delete` direto.
199
+ - Isso e simples, mas nao aproveita `OnyxCord::RateLimiter::Rest`.
200
+
201
+ Impacto:
202
+ - Clientes de webhook intensivos podem bater 429 com menos controle.
203
+
204
+ Sugestao:
205
+ - Criar transport compartilhado leve para webhooks.
206
+ - Ou criar um rate limiter dedicado por webhook URL.
207
+
208
+ ### 11. Dependencias abertas demais
209
+
210
+ Arquivos:
211
+ - `onyxcord.gemspec`
212
+ - `onyxcord-webhooks.gemspec`
213
+
214
+ Problema:
215
+ - Algumas dependencias permitem qualquer versao acima do minimo, como `rest-client >= 2.0.0`, `websocket-client-simple >= 0.9.0`, `ffi >= 1.9.24` e `opus-ruby` sem limite.
216
+
217
+ Impacto:
218
+ - Atualizacao futura pode quebrar performance ou compatibilidade.
219
+
220
+ Sugestao:
221
+ - Definir upper bounds conservadores, por exemplo `< 3` quando fizer sentido.
222
+ - Rodar CI com Ruby 3.3 e 3.4 se a gem prometer suporte moderno.
223
+
224
+ ### 12. Arquivo `bot.rb` esta grande demais
225
+
226
+ Arquivo: `lib/onyxcord/bot.rb` tem cerca de 1971 linhas.
227
+
228
+ Problema:
229
+ - O arquivo mistura boot, REST helpers, dispatch, cache orchestration, interactions, voice e commands.
230
+
231
+ Impacto:
232
+ - Dificulta otimizar sem regressao.
233
+
234
+ Sugestao:
235
+ - Extrair aos poucos:
236
+ - `Bot::Interactions`
237
+ - `Bot::Dispatch`
238
+ - `Bot::Voice`
239
+ - `Bot::ApplicationCommands`
240
+ - Fazer isso depois das correcoes de runtime, para nao misturar refactor com bugfix.
241
+
242
+ ## Otimizacoes praticas sugeridas
243
+
244
+ ### Perfil leve recomendado para usuarios
245
+
246
+ Documentar no README um preset para bots pequenos:
247
+
248
+ ```ruby
249
+ OnyxCord.configure do |config|
250
+ config.mode = :raw
251
+ config.cache = :none
252
+ config.event_executor = :pool
253
+ config.event_workers = 2
254
+ end
255
+ ```
256
+
257
+ Para bots medios:
258
+
259
+ ```ruby
260
+ OnyxCord.configure do |config|
261
+ config.mode = :hybrid
262
+ config.cache = :minimal
263
+ config.event_workers = 4
264
+ end
265
+ ```
266
+
267
+ ### Medir antes/depois
268
+
269
+ Criar specs/benchmarks simples para:
270
+ - `INTERACTION_CREATE` com 1000 commands simulados.
271
+ - `MESSAGE_CREATE` em modo `raw`, `hybrid` e `object`.
272
+ - crescimento de `@users`, `@channels`, `@thread_members`.
273
+ - fila do executor quando handler dorme 50ms.
274
+
275
+ ### Instrumentacao leve
276
+
277
+ Adicionar metodos opcionais:
278
+ - `bot.runtime_stats`
279
+ - `bot.cache_stats`
280
+ - `bot.event_queue_size`
281
+ - `OnyxCord::API.rate_limiter_stats`
282
+
283
+ Isso ajuda a diagnosticar RAM e lentidao sem profiler externo.
284
+
285
+ ## Plano de acao recomendado
286
+
287
+ 1. Corrigir `API.request` no retry `202` e adicionar spec.
288
+ 2. Remover threads soltas dos application commands e usar `@event_executor`.
289
+ 3. Remover logs `>>>` ou rebaixar para `debug`.
290
+ 4. Trocar `Queue` por `SizedQueue` configuravel.
291
+ 5. Adicionar `cache_stats` e `prune_cache!`.
292
+ 6. Fechar corretamente recursos de voice (`File.open`, UDP, WS, thread).
293
+ 7. Adicionar limpeza/LRU no rate limiter REST.
294
+ 8. Depois disso, refatorar `bot.rb` em modulos menores.
295
+
296
+ ## Conclusao
297
+
298
+ A OnyxCord ja esta no caminho certo para ser pratica no modo padrao `hybrid` e ainda leve quando o usuario escolher `raw` com cache `:none`. O maior risco atual nao e um unico algoritmo pesado, e sim crescimento sem limite: threads por interaction, fila ilimitada, caches sem TTL e mapas internos que nao expiram. Corrigir esses pontos deve reduzir RAM em carga real, deixar o bot mais previsivel em pico e facilitar otimizar depois sem mexer na API publica.
data/relatorio2.md ADDED
File without changes
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: onyxcord
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gustavo Silva
@@ -30,6 +30,9 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 1.9.24
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '2'
33
36
  type: :runtime
34
37
  prerelease: false
35
38
  version_requirements: !ruby/object:Gem::Requirement
@@ -37,6 +40,9 @@ dependencies:
37
40
  - - ">="
38
41
  - !ruby/object:Gem::Version
39
42
  version: 1.9.24
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '2'
40
46
  - !ruby/object:Gem::Dependency
41
47
  name: opus-ruby
42
48
  requirement: !ruby/object:Gem::Requirement
@@ -44,6 +50,9 @@ dependencies:
44
50
  - - ">="
45
51
  - !ruby/object:Gem::Version
46
52
  version: '0'
53
+ - - "<"
54
+ - !ruby/object:Gem::Version
55
+ version: '2'
47
56
  type: :runtime
48
57
  prerelease: false
49
58
  version_requirements: !ruby/object:Gem::Requirement
@@ -51,6 +60,9 @@ dependencies:
51
60
  - - ">="
52
61
  - !ruby/object:Gem::Version
53
62
  version: '0'
63
+ - - "<"
64
+ - !ruby/object:Gem::Version
65
+ version: '2'
54
66
  - !ruby/object:Gem::Dependency
55
67
  name: rest-client
56
68
  requirement: !ruby/object:Gem::Requirement
@@ -58,6 +70,9 @@ dependencies:
58
70
  - - ">="
59
71
  - !ruby/object:Gem::Version
60
72
  version: 2.0.0
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: '3'
61
76
  type: :runtime
62
77
  prerelease: false
63
78
  version_requirements: !ruby/object:Gem::Requirement
@@ -65,6 +80,9 @@ dependencies:
65
80
  - - ">="
66
81
  - !ruby/object:Gem::Version
67
82
  version: 2.0.0
83
+ - - "<"
84
+ - !ruby/object:Gem::Version
85
+ version: '3'
68
86
  - !ruby/object:Gem::Dependency
69
87
  name: websocket-client-simple
70
88
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +90,9 @@ dependencies:
72
90
  - - ">="
73
91
  - !ruby/object:Gem::Version
74
92
  version: 0.9.0
93
+ - - "<"
94
+ - !ruby/object:Gem::Version
95
+ version: '1'
75
96
  type: :runtime
76
97
  prerelease: false
77
98
  version_requirements: !ruby/object:Gem::Requirement
@@ -79,20 +100,23 @@ dependencies:
79
100
  - - ">="
80
101
  - !ruby/object:Gem::Version
81
102
  version: 0.9.0
103
+ - - "<"
104
+ - !ruby/object:Gem::Version
105
+ version: '1'
82
106
  - !ruby/object:Gem::Dependency
83
107
  name: onyxcord-webhooks
84
108
  requirement: !ruby/object:Gem::Requirement
85
109
  requirements:
86
110
  - - "~>"
87
111
  - !ruby/object:Gem::Version
88
- version: 1.1.0
112
+ version: 1.1.3
89
113
  type: :runtime
90
114
  prerelease: false
91
115
  version_requirements: !ruby/object:Gem::Requirement
92
116
  requirements:
93
117
  - - "~>"
94
118
  - !ruby/object:Gem::Version
95
- version: 1.1.0
119
+ version: 1.1.3
96
120
  - !ruby/object:Gem::Dependency
97
121
  name: bundler
98
122
  requirement: !ruby/object:Gem::Requirement
@@ -392,6 +416,8 @@ files:
392
416
  - lib/onyxcord/websocket.rb
393
417
  - onyxcord-webhooks.gemspec
394
418
  - onyxcord.gemspec
419
+ - relator.md
420
+ - relatorio2.md
395
421
  homepage: https://github.com/kruldevb/OnyxCord
396
422
  licenses:
397
423
  - MIT