actioncable-next 0.1.2 → 0.2.0

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: b86ee72b0095c7a8ce99b0d354231b2ac29145e07a1482387fa9664f1c9a08b8
4
- data.tar.gz: e72beed883e8789189fa6811d6fb70cdc4e9efbec2ad9b259648c8575196aca3
3
+ metadata.gz: f5831ed0f565c3bc58899a26ec3097bf47ea7cb518f380e0db05a8bad6fcc5dd
4
+ data.tar.gz: 92aae3e36883530c9b51ee7acdb0e7df231aed27d60b5f8a4d31426c7158822e
5
5
  SHA512:
6
- metadata.gz: 955b568fb0d4e01d81ad6871292515c1a3d742eef03067c6322d68b7c6908eeac12947aaef91cb9d4fcbccf2e3790ff84c797ad30e462db15179b04ee6a2d8a3
7
- data.tar.gz: 3cc6151d155e3719b008bad22ecf4fbf0a401a5858a7ef5bd75a941debca5b26ac9d06cb30d781929d6da10399c9f534d06aed6ac7afd2ea7a96490066dc6891
6
+ metadata.gz: ef8e96d02d595e9004acc936acc8c116d1ac846664dbdf3455b0cfb8f872ba3dbbecfd3dfb3772a53b796505c02a592947bf838690d9c5f0b2c844a40e70e7a4
7
+ data.tar.gz: bf36b4a97cd76f02d4162a70d27751ae15333d5c1c23654199bd59214dc061b0b779aa9dd28c73ca080f075fc5bc8972db86d6e98ce137c4b78f330bd3e5540c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## main
4
4
 
5
+ ## 0.2.0
6
+
7
+ - Performance: store connections in a Hash, not an Array.
8
+
9
+ - Add `Connection#broadcast` as an interface for broadcasting from channels.
10
+
11
+ - Add `config.fastlane_broadcasts_enabled` to opt-in for optimized broadcasts (no double JSON encoding).
12
+
5
13
  ## 0.1.2
6
14
 
7
15
  - Added a hack to prevent third-party extensions from changing the methods visibility.
data/README.md CHANGED
@@ -4,6 +4,8 @@ This gem provides the functionality of the _server adapterization_ PR: [rails/ra
4
4
 
5
5
  See the PR description for more information on the purpose of this refactoring.
6
6
 
7
+ There are also some additional improvements added on top of the PR (see below).
8
+
7
9
  ## Usage
8
10
 
9
11
  Add this line to your application's Gemfile **before Rails or Action Cable**:
@@ -26,3 +28,23 @@ by adding the following line to your `spec/rails_helper.rb`:
26
28
  require "rspec/rails"
27
29
  require "action_cable/next/rspec"
28
30
  ```
31
+
32
+ ### New features
33
+
34
+ #### `ActionCable.server.config.fastlane_broadcasts_enabled = true`
35
+
36
+ This option allows you to enable the _fast lane_ for broadcasting messages. When enabled, messages sent via `ActionCable.server.broadcast` are only decoded-encoded from/to JSON once per channel identifier and not for every connected client. This significantly reduces the broadcasting latency (up to 2x faster).
37
+
38
+ #### `Connection#broadcast`
39
+
40
+ You can now broadcast message from connection or channel instances using the connection `#broadcast` method. For example:
41
+
42
+ ```ruby
43
+ class TestChannel < ActionCable::Channel::Base
44
+ def notify_all(data)
45
+ connection.broadcast("testing-#{test_id}", data)
46
+ end
47
+ end
48
+ ```
49
+
50
+ The purpose of this interface is to avoid direct usage of `server` or `ActionCable.server` in channels code.
@@ -157,10 +157,21 @@ module ActionCable
157
157
  # Always wrap the outermost handler to invoke the user handler on the worker
158
158
  # pool rather than blocking the event loop.
159
159
  def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
160
+ if !user_handler && !coder && connection.config.fastlane_broadcasts_enabled
161
+ return opt_worker_pool_stream_handler(broadcasting)
162
+ end
163
+
160
164
  handler = stream_handler(broadcasting, user_handler, coder: coder)
161
165
 
162
166
  -> message do
163
- connection.perform_work handler, :call, message
167
+ connection.perform_work handler, :call, message.data
168
+ end
169
+ end
170
+
171
+ # Optimized stream handler that avoids double JSON encoding/decoding on broadcast
172
+ def opt_worker_pool_stream_handler(broadcasting)
173
+ -> (message) do
174
+ connection.raw_transmit message.encoded_for(@identifier)
164
175
  end
165
176
  end
166
177
 
@@ -62,7 +62,7 @@ module ActionCable
62
62
  attr_reader :subscriptions, :logger
63
63
  private attr_reader :server, :socket
64
64
 
65
- delegate :pubsub, :executor, :config, to: :server
65
+ delegate :pubsub, :executor, :config, :broadcast, to: :server
66
66
  delegate :env, :request, :protocol, :perform_work, to: :socket, allow_nil: true
67
67
 
68
68
  def initialize(server, socket)
@@ -117,6 +117,10 @@ module ActionCable
117
117
  socket.transmit(data)
118
118
  end
119
119
 
120
+ def raw_transmit(data) # :nodoc:
121
+ socket.raw_transmit(data)
122
+ end
123
+
120
124
  # Close the connection.
121
125
  def close(reason: nil, reconnect: true)
122
126
  transmit(
@@ -18,7 +18,7 @@ module ActionCable
18
18
 
19
19
  def subscribe_to_internal_channel
20
20
  if connection_identifier.present?
21
- callback = -> (message) { process_internal_message ActiveSupport::JSON.decode(message) }
21
+ callback = -> (message) { process_internal_message ActiveSupport::JSON.decode(message.data) }
22
22
  @_internal_subscriptions ||= []
23
23
  @_internal_subscriptions << [ internal_channel, callback ]
24
24
 
@@ -19,6 +19,7 @@ module ActionCable
19
19
  attr_accessor :precompile_assets
20
20
  attr_accessor :health_check_path, :health_check_application
21
21
  attr_writer :pubsub_adapter
22
+ attr_accessor :fastlane_broadcasts_enabled
22
23
 
23
24
  def initialize
24
25
  @log_tags = []
@@ -31,6 +32,8 @@ module ActionCable
31
32
  @allow_same_origin_as_host = true
32
33
  @filter_parameters = []
33
34
 
35
+ @fastlane_broadcasts_enabled = false
36
+
34
37
  @health_check_application = ->(env) {
35
38
  [200, { Rack::CONTENT_TYPE => "text/html", "date" => Time.now.httpdate }, []]
36
39
  }
@@ -13,16 +13,18 @@ module ActionCable
13
13
  module Connections # :nodoc:
14
14
  BEAT_INTERVAL = 3
15
15
 
16
- def connections
17
- @connections ||= []
16
+ def connections_map
17
+ @connections_map ||= {}
18
18
  end
19
19
 
20
+ def connections = connections_map.values
21
+
20
22
  def add_connection(connection)
21
- connections << connection
23
+ connections_map[connection.object_id] = connection
22
24
  end
23
25
 
24
26
  def remove_connection(connection)
25
- connections.delete connection
27
+ connections_map.delete connection.object_id
26
28
  end
27
29
 
28
30
  # WebSocket connection implementations differ on when they'll mark a connection
@@ -32,12 +34,12 @@ module ActionCable
32
34
  # disconnect.
33
35
  def setup_heartbeat_timer
34
36
  @heartbeat_timer ||= executor.timer(BEAT_INTERVAL) do
35
- executor.post { connections.each(&:beat) }
37
+ executor.post { connections_map.each_value(&:beat) }
36
38
  end
37
39
  end
38
40
 
39
41
  def open_connections_statistics
40
- connections.map(&:statistics)
42
+ connections_map.each_value.map(&:statistics)
41
43
  end
42
44
  end
43
45
  end
@@ -47,6 +47,12 @@ module ActionCable
47
47
  websocket.transmit encode(cable_message)
48
48
  end
49
49
 
50
+ def raw_transmit(message)
51
+ return unless websocket.alive?
52
+
53
+ websocket.transmit message
54
+ end
55
+
50
56
  # Close the WebSocket connection.
51
57
  def close(...)
52
58
  websocket.close(...) if websocket.alive?
@@ -5,6 +5,19 @@
5
5
  module ActionCable
6
6
  module SubscriptionAdapter
7
7
  class SubscriberMap
8
+ class Message < Struct.new(:data)
9
+ def initialize(...)
10
+ super
11
+ @cache = Concurrent::Map.new
12
+ end
13
+
14
+ def encoded_for(identifier)
15
+ @cache.compute_if_absent(identifier) do
16
+ ActiveSupport::JSON.encode({ identifier: identifier, message: ActiveSupport::JSON.decode(data) })
17
+ end
18
+ end
19
+ end
20
+
8
21
  def initialize
9
22
  @subscribers = Hash.new { |h, k| h[k] = [] }
10
23
  @sync = Mutex.new
@@ -41,8 +54,10 @@ module ActionCable
41
54
  @subscribers[channel].dup
42
55
  end
43
56
 
57
+ msg = Message.new(message)
58
+
44
59
  list.each do |subscriber|
45
- invoke_callback(subscriber, message)
60
+ invoke_callback(subscriber, msg)
46
61
  end
47
62
  end
48
63
 
data/lib/action_cable.rb CHANGED
@@ -23,6 +23,7 @@
23
23
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
24
  #++
25
25
 
26
+ require "logger"
26
27
  require "active_support"
27
28
  require "active_support/rails"
28
29
  require "zeitwerk"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionCableNext
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actioncable-next
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pratik Naik
8
8
  - David Heinemeier Hansson
9
9
  - Vladimir Dementyev
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-01-10 00:00:00.000000000 Z
13
+ date: 2025-02-04 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -167,10 +167,10 @@ licenses:
167
167
  - MIT
168
168
  metadata:
169
169
  bug_tracker_uri: https://github.com/anycable/actioncable-next
170
- changelog_uri: https://github.com/anycable/actioncable-next/blob/v0.1.2/CHANGELOG.md
170
+ changelog_uri: https://github.com/anycable/actioncable-next/blob/v0.2.0/CHANGELOG.md
171
171
  source_code_uri: https://github.com/anycable/actioncable-next
172
172
  rubygems_mfa_required: 'true'
173
- post_install_message:
173
+ post_install_message:
174
174
  rdoc_options: []
175
175
  require_paths:
176
176
  - lib
@@ -186,7 +186,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
186
186
  version: '0'
187
187
  requirements: []
188
188
  rubygems_version: 3.5.22
189
- signing_key:
189
+ signing_key:
190
190
  specification_version: 4
191
191
  summary: Next-gen version of Action Cable
192
192
  test_files: []