anycable-rails-core 1.4.1 → 1.4.2

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: d59e7b74ab037c4e2dfdf18b5fb289fad3f7eac9d1a08df798f656ed35dda95c
4
- data.tar.gz: a1304fbd9db3f26c92ef5008b196135a2f09a5922bde5137d7e604e652250354
3
+ metadata.gz: 713258efa713cd94cb2aa57891bf52f2483732f6e84b918a90d77924bc9d78e2
4
+ data.tar.gz: 45151b26a79c7ab05ba42629fcbb2c64a147142a39fe8d9eaff54dbce3b905c6
5
5
  SHA512:
6
- metadata.gz: 8a7eebcf805f8c72c300b734fe52a7ce952b60527b8644f004ca7bb3414cc04da49b8ee2ce36d84f6f7d8bf0438450dfdffb019610b091894c3dcf7e65ec4897
7
- data.tar.gz: 488065254235777ef04b781ef98f1c20c8c12f2f6c268c6044fb683e4a9774f1ae5abbdcbec53199065e57ec8df1df15d13f85c152471a5a54ceb23e6ddbd832
6
+ metadata.gz: d98ee91a26f73b7e9ff7c60820211ddebf53566520903f6ca26be838f4c27fed6f0a2a36787fe3207b569f32881c83efd263517eaa71a5f74bdee00dd61c63ff
7
+ data.tar.gz: e0a86e5ccbef6d7735f71663a5a9511dc9dd3a7a7df8fa1ffd4e3cccbf653184c0048cbbd22007f21614f4d78f30ee5aa4bb55b275964f1b9b690e3927c27069
data/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.4.2 (2023-10-15)
6
+
7
+ - Print warning if the database pool size is less than RPC pool size. ([@palkan][])
8
+
9
+ - Add support for broadcast options (e.g., `exclude_socket`) and `broadcast ..., to_others: true`. ([@palkan][])
10
+
11
+ - Add `batch_broadcasts` option to automatically batch broadcasts for code wrapped in Rails executor. ([@palkan][])
12
+
13
+ - Fix broadcast logging in Rails 7.1. ([@iuri-gg][])
14
+
5
15
  ## 1.4.1 (2023-09-27)
6
16
 
7
17
  - Fix compatibility with Rails 7.1. ([@palkan][])
@@ -18,8 +18,11 @@ module ActionCable
18
18
  def initialize(*)
19
19
  end
20
20
 
21
- def broadcast(channel, payload)
22
- ::AnyCable.broadcast(channel, payload)
21
+ def broadcast(channel, payload, **options)
22
+ options.merge!(::AnyCable::Rails.current_broadcast_options || {})
23
+ to_others = options.delete(:to_others)
24
+ options[:exclude_socket] ||= ::AnyCable::Rails.current_socket_id if to_others
25
+ ::AnyCable.broadcast(channel, payload, **options.compact)
23
26
  end
24
27
 
25
28
  def subscribe(*)
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_cable"
4
+
5
+ ActionCable::Server::Base.prepend(Module.new do
6
+ def broadcast(channel, payload, **options)
7
+ return super if options.empty?
8
+
9
+ AnyCable::Rails.with_broadcast_options(**options) do
10
+ super(channel, payload)
11
+ end
12
+ end
13
+ end)
14
+
15
+ ActionCable::Channel::Base.singleton_class.prepend(Module.new do
16
+ def broadcast_to(target, payload, **options)
17
+ return super if options.empty?
18
+
19
+ AnyCable::Rails.with_broadcast_options(**options) do
20
+ super(target, payload)
21
+ end
22
+ end
23
+ end)
@@ -10,10 +10,14 @@ require "anyway/rails"
10
10
  # - `persistent_session_enabled` (defaults to false) — whether to store session changes in the connection state
11
11
  # - `embedded` (defaults to false) — whether to run RPC server inside a Rails server process
12
12
  # - `http_rpc_mount_path` (default to nil) — path to mount HTTP RPC server
13
+ # - `batch_broadcasts` (defaults to false) — whether to batch broadcasts automatically for code wrapped with Rails executor
13
14
  AnyCable::Config.attr_config(
14
15
  access_logs_disabled: true,
15
16
  persistent_session_enabled: false,
16
17
  embedded: false,
17
- http_rpc_mount_path: nil
18
+ http_rpc_mount_path: nil,
19
+ batch_broadcasts: false,
20
+ socket_id_header: "X-Socket-ID",
21
+ disable_rpc_pool_size_warning: false
18
22
  )
19
23
  AnyCable::Config.ignore_options :access_logs_disabled, :persistent_session_enabled
@@ -13,16 +13,16 @@ module AnyCable
13
13
  end
14
14
 
15
15
  def call(method, message, metadata)
16
+ sid = metadata["sid"]
17
+
16
18
  if ::Rails.respond_to?(:error)
17
19
  executor.wrap do
18
- sid = metadata["sid"]
19
-
20
20
  ::Rails.error.record(context: {method: method, payload: message.to_h, sid: sid}) do
21
- yield
21
+ Rails.with_socket_id(sid) { yield }
22
22
  end
23
23
  end
24
24
  else
25
- executor.wrap { yield }
25
+ executor.wrap { Rails.with_socket_id(sid) { yield } }
26
26
  end
27
27
  end
28
28
  end
@@ -3,6 +3,7 @@
3
3
  require "anycable/rails/action_cable_ext/connection"
4
4
  require "anycable/rails/action_cable_ext/channel"
5
5
  require "anycable/rails/action_cable_ext/remote_connections"
6
+ require "anycable/rails/action_cable_ext/broadcast_options"
6
7
 
7
8
  require "anycable/rails/channel_state"
8
9
  require "anycable/rails/connection_factory"
@@ -30,8 +31,8 @@ module AnyCable
30
31
  console.level = ::Rails.logger.level if ::Rails.logger.respond_to?(:level)
31
32
 
32
33
  # Rails 7.1+
33
- if defined?(ActiveSupport::BroadcastLogger)
34
- AnyCable.logger = ActiveSupport::BroadcastLogger.new(AnyCable.logger, console)
34
+ if AnyCable.logger.respond_to?(:broadcast_to)
35
+ AnyCable.logger.broadcast_to(console)
35
36
  else
36
37
  AnyCable.logger.extend(ActiveSupport::Logger.broadcast(console))
37
38
  end
@@ -59,6 +60,27 @@ module AnyCable
59
60
  ::Rails.error.report(ex, handled: false, context: {method: method.to_sym, payload: message})
60
61
  end
61
62
  end
63
+
64
+ if AnyCable.config.batch_broadcasts?
65
+ if AnyCable.broadcast_adapter.respond_to?(:start_batching)
66
+ app.executor.to_run { AnyCable.broadcast_adapter.start_batching }
67
+ app.executor.to_complete { AnyCable.broadcast_adapter.finish_batching }
68
+ else
69
+ warn "[AnyCable] Auto-batching is enabled for broadcasts but your anycable version doesn't support it. Please, upgrade"
70
+ end
71
+ end
72
+ end
73
+
74
+ initializer "anycable.socket_id_tracking" do
75
+ ActiveSupport.on_load(:action_controller) do
76
+ require "anycable/rails/socket_id_tracking"
77
+ include AnyCable::Rails::SocketIdTrackingController
78
+ end
79
+
80
+ ActiveSupport.on_load(:active_job) do
81
+ require "anycable/rails/socket_id_tracking"
82
+ include AnyCable::Rails::SocketIdTrackingJob
83
+ end
62
84
  end
63
85
 
64
86
  initializer "anycable.connection_factory", after: "action_cable.set_configs" do |app|
@@ -86,6 +108,27 @@ module AnyCable
86
108
  end
87
109
  end
88
110
 
111
+ initializer "anycable.verify_pool_sizes" do
112
+ next if AnyCable.config.disable_rpc_pool_size_warning?
113
+ # Skip if non-gRPC server is used
114
+ next unless AnyCable.config.respond_to?(:rpc_pool_size)
115
+
116
+ # Log current db vs. gRPC pool sizes
117
+ AnyCable.configure_server do
118
+ ActiveSupport.on_load(:active_record) do
119
+ db_pool_size = ::ActiveRecord::Base.connection_pool.size
120
+ rpc_pool_size = AnyCable.config.rpc_pool_size
121
+
122
+ if rpc_pool_size > db_pool_size
123
+ ::Kernel.warn(
124
+ "\n⛔️ WARNING: AnyCable RPC pool size (#{rpc_pool_size}) is greater than DB pool size (#{db_pool_size})\n" \
125
+ "Please, consider adjusting the database pool size to avoid connection wait times and increase throughput of your RPC server\n\n"
126
+ )
127
+ end
128
+ end
129
+ end
130
+ end
131
+
89
132
  # Since Rails 6.1
90
133
  if respond_to?(:server)
91
134
  server do
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCable
4
+ module Rails
5
+ module SocketIdTrackingController
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ around_action :anycable_tracking_socket_id
10
+ end
11
+
12
+ private
13
+
14
+ def anycable_tracking_socket_id(&block)
15
+ Rails.with_socket_id(request.headers[AnyCable.config.socket_id_header], &block)
16
+ end
17
+ end
18
+
19
+ module SocketIdTrackingJob
20
+ extend ActiveSupport::Concern
21
+
22
+ attr_accessor :cable_socket_id
23
+
24
+ def serialize
25
+ return super unless Rails.current_socket_id
26
+
27
+ super.merge("cable_socket_id" => Rails.current_socket_id)
28
+ end
29
+
30
+ def deserialize(job_data)
31
+ super
32
+ self.cable_socket_id = job_data["cable_socket_id"]
33
+ end
34
+
35
+ included do
36
+ around_perform do |job, block|
37
+ Rails.with_socket_id(job.cable_socket_id, &block)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AnyCable
4
4
  module Rails
5
- VERSION = "1.4.1"
5
+ VERSION = "1.4.2"
6
6
  end
7
7
  end
@@ -6,6 +6,7 @@ require "anycable/rails/config"
6
6
  require "anycable/rails/rack"
7
7
 
8
8
  require "globalid"
9
+ require "active_support/core_ext/module/attribute_accessors_per_thread"
9
10
 
10
11
  module AnyCable
11
12
  # Rails handler for AnyCable
@@ -14,6 +15,9 @@ module AnyCable
14
15
 
15
16
  ADAPTER_ALIASES = %w[any_cable anycable].freeze
16
17
 
18
+ thread_mattr_accessor :current_socket_id
19
+ thread_mattr_accessor :current_broadcast_options
20
+
17
21
  class << self
18
22
  def enabled?
19
23
  adapter = ::ActionCable.server.config.cable&.fetch("adapter", nil)
@@ -24,6 +28,29 @@ module AnyCable
24
28
  ADAPTER_ALIASES.include?(adapter)
25
29
  end
26
30
 
31
+ def with_socket_id(socket_id)
32
+ old_socket_id, self.current_socket_id = current_socket_id, socket_id
33
+ yield
34
+ ensure
35
+ self.current_socket_id = old_socket_id
36
+ end
37
+
38
+ def with_broadcast_options(**options)
39
+ old_options = current_broadcast_options
40
+ self.current_broadcast_options = options.reverse_merge(old_options || {})
41
+ yield
42
+ ensure
43
+ self.current_broadcast_options = old_options
44
+ end
45
+
46
+ def broadcasting_to_others(socket_id: nil, &block)
47
+ if socket_id
48
+ with_socket_id(socket_id) { with_broadcast_options(to_others: true, &block) }
49
+ else
50
+ with_broadcast_options(to_others: true, &block)
51
+ end
52
+ end
53
+
27
54
  # Serialize connection/channel state variable to string
28
55
  # using GlobalID where possible or JSON (if json: true)
29
56
  def serialize(obj, json: false)
@@ -50,9 +77,9 @@ module AnyCable
50
77
  end
51
78
 
52
79
  module Extension
53
- def broadcast(channel, payload)
80
+ def broadcast(...)
54
81
  super
55
- ::AnyCable.broadcast(channel, payload)
82
+ ::AnyCable.broadcast(...)
56
83
  end
57
84
  end
58
85
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anycable-rails-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - palkan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-28 00:00:00.000000000 Z
11
+ date: 2023-10-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: anycable-core
@@ -66,6 +66,7 @@ files:
66
66
  - lib/action_cable/subscription_adapter/anycable.rb
67
67
  - lib/anycable-rails.rb
68
68
  - lib/anycable/rails.rb
69
+ - lib/anycable/rails/action_cable_ext/broadcast_options.rb
69
70
  - lib/anycable/rails/action_cable_ext/channel.rb
70
71
  - lib/anycable/rails/action_cable_ext/connection.rb
71
72
  - lib/anycable/rails/action_cable_ext/remote_connections.rb
@@ -86,6 +87,7 @@ files:
86
87
  - lib/anycable/rails/middlewares/log_tagging.rb
87
88
  - lib/anycable/rails/rack.rb
88
89
  - lib/anycable/rails/railtie.rb
90
+ - lib/anycable/rails/socket_id_tracking.rb
89
91
  - lib/anycable/rails/version.rb
90
92
  - lib/generators/anycable/download/USAGE
91
93
  - lib/generators/anycable/download/download_generator.rb