faye-redis-ng 1.0.4 → 1.0.5

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: fde6220c9baee883a47eced86a4cd7245ecca48a05a55c906660a5286260c401
4
- data.tar.gz: dd3e0d6190cc019070da513d57ac076537b7d8aa1626e29b17c1f7fb91910b79
3
+ metadata.gz: 02a75e3e557cd2916224537b1d688333c2661e1006a836ca92afde12f59c04bb
4
+ data.tar.gz: 33a2a61187282c3801de1ac648f4d55fa0ad2a46d5ef1c1f964f1e5db5df7ed7
5
5
  SHA512:
6
- metadata.gz: 968761695fa0cc17df7c810a345068bf0de18437fd909eba1ed803f784daff107734f846106f67154cfe1c5a3295905c61f1863de40a59a5304199147144fd80
7
- data.tar.gz: 8137b865b357b20024edbbb870ff51b76b44779eebe3db18e77167ef326a64322adae9beec35adad04c54028236f6b30c87736fa67642b2684ac9ef73a951c13
6
+ metadata.gz: 79f1cdaeb24197a454fbcf9ffa2a2a0678ebe2e09c0ab82a694e9e6b643fba4a43fa14d836c1cb3eeca91c9f4091d63faec489d60c7237e8652b42679c8a811f
7
+ data.tar.gz: d96e691b5bb66c86c64852474b4bbb9cb1797420adfed71210f64c27f0e695b5489dec71c18d6636daa3ecdcea6e6a1580f90cba850f140e1c7ebc5734d615b8
data/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.5] - 2025-10-30
11
+
12
+ ### Fixed
13
+ - **Memory Leak**: Fixed critical memory leak where subscription keys were never cleaned up after client disconnection
14
+ - Orphaned `subscriptions:{client_id}` keys remained permanently in Redis
15
+ - Orphaned `subscription:{client_id}:{channel}` hash keys accumulated over time
16
+ - Orphaned client IDs remained in `channels:{channel}` sets
17
+ - Message queues for disconnected clients were not cleaned up
18
+ - Could result in hundreds of MB memory leak in production environments
19
+
20
+ ### Added
21
+ - **`cleanup_expired` Method**: New public method to clean up expired clients and orphaned data
22
+ - Automatically detects and removes orphaned subscription keys
23
+ - Cleans up message queues for disconnected clients
24
+ - Removes stale client IDs from channel subscriber lists
25
+ - Uses Redis SCAN to avoid blocking operations
26
+ - Batch deletion using pipelining for efficiency
27
+ - Can be called manually or scheduled as periodic task
28
+
29
+ ### Changed
30
+ - **Improved Cleanup Strategy**: Enhanced cleanup process now handles orphaned data
31
+ - `cleanup_expired` now cleans both expired clients AND orphaned subscriptions
32
+ - Works even when no expired clients are found
33
+ - Prevents memory leaks from abnormal client disconnections
34
+
35
+ ### Technical Details
36
+ Memory leak scenario (before fix):
37
+ - 10,000 abnormally disconnected clients × 5 channels each = 50,000+ orphaned keys
38
+ - Estimated memory waste: 100-500 MB
39
+ - Keys remained permanently without TTL
40
+
41
+ After fix:
42
+ - All orphaned keys cleaned up automatically
43
+ - Memory usage remains stable
44
+ - Production environments can schedule periodic cleanup
45
+
10
46
  ## [1.0.4] - 2025-10-15
11
47
 
12
48
  ### Performance
@@ -90,7 +126,8 @@ For 100 subscribers receiving one message:
90
126
  ### Security
91
127
  - Client and message IDs now use `SecureRandom.uuid` instead of predictable time-based generation
92
128
 
93
- [Unreleased]: https://github.com/7a6163/faye-redis-ng/compare/v1.0.4...HEAD
129
+ [Unreleased]: https://github.com/7a6163/faye-redis-ng/compare/v1.0.5...HEAD
130
+ [1.0.5]: https://github.com/7a6163/faye-redis-ng/compare/v1.0.4...v1.0.5
94
131
  [1.0.4]: https://github.com/7a6163/faye-redis-ng/compare/v1.0.3...v1.0.4
95
132
  [1.0.3]: https://github.com/7a6163/faye-redis-ng/compare/v1.0.2...v1.0.3
96
133
  [1.0.2]: https://github.com/7a6163/faye-redis-ng/compare/v1.0.1...v1.0.2
data/README.md CHANGED
@@ -234,6 +234,109 @@ The CI/CD pipeline will automatically:
234
234
  - Add `RUBYGEMS_API_KEY` to GitHub repository secrets
235
235
  - The tag must start with 'v' (e.g., v0.1.0, v1.2.3)
236
236
 
237
+ ## Memory Management
238
+
239
+ ### Cleaning Up Expired Clients
240
+
241
+ To prevent memory leaks from orphaned subscription keys, you should periodically clean up expired clients:
242
+
243
+ #### Manual Cleanup
244
+
245
+ ```ruby
246
+ # Get the engine instance
247
+ engine = bayeux.get_engine
248
+
249
+ # Clean up expired clients and orphaned data
250
+ engine.cleanup_expired do |expired_count|
251
+ puts "Cleaned up #{expired_count} expired clients"
252
+ end
253
+ ```
254
+
255
+ #### Automatic Periodic Cleanup (Recommended)
256
+
257
+ Add this to your Faye server setup:
258
+
259
+ ```ruby
260
+ require 'eventmachine'
261
+ require 'faye'
262
+ require 'faye-redis-ng'
263
+
264
+ bayeux = Faye::RackAdapter.new(app, {
265
+ mount: '/faye',
266
+ timeout: 25,
267
+ engine: {
268
+ type: Faye::Redis,
269
+ host: 'localhost',
270
+ port: 6379,
271
+ namespace: 'my-app'
272
+ }
273
+ })
274
+
275
+ # Schedule automatic cleanup every 5 minutes
276
+ EM.add_periodic_timer(300) do
277
+ bayeux.get_engine.cleanup_expired do |count|
278
+ puts "[#{Time.now}] Cleaned up #{count} expired clients" if count > 0
279
+ end
280
+ end
281
+
282
+ run bayeux
283
+ ```
284
+
285
+ #### Using Rake Task
286
+
287
+ Create a Rake task for manual or scheduled cleanup:
288
+
289
+ ```ruby
290
+ # lib/tasks/faye_cleanup.rake
291
+ namespace :faye do
292
+ desc "Clean up expired Faye clients and orphaned subscriptions"
293
+ task cleanup: :environment do
294
+ require 'eventmachine'
295
+
296
+ EM.run do
297
+ engine = Faye::Redis.new(
298
+ nil,
299
+ host: ENV['REDIS_HOST'] || 'localhost',
300
+ port: ENV['REDIS_PORT']&.to_i || 6379,
301
+ namespace: 'my-app'
302
+ )
303
+
304
+ engine.cleanup_expired do |count|
305
+ puts "✅ Cleaned up #{count} expired clients"
306
+ engine.disconnect
307
+ EM.stop
308
+ end
309
+ end
310
+ end
311
+ end
312
+ ```
313
+
314
+ Then schedule it with cron:
315
+
316
+ ```bash
317
+ # Run cleanup every hour
318
+ 0 * * * * cd /path/to/app && bundle exec rake faye:cleanup
319
+ ```
320
+
321
+ ### What Gets Cleaned Up
322
+
323
+ The `cleanup_expired` method removes:
324
+
325
+ 1. **Expired client keys** (`clients:{client_id}`)
326
+ 2. **Orphaned subscription lists** (`subscriptions:{client_id}`)
327
+ 3. **Orphaned subscription metadata** (`subscription:{client_id}:{channel}`)
328
+ 4. **Stale client IDs from channel subscribers** (`channels:{channel}`)
329
+ 5. **Orphaned message queues** (`messages:{client_id}`)
330
+
331
+ ### Memory Leak Prevention
332
+
333
+ Without periodic cleanup, abnormal client disconnections (crashes, network failures, etc.) can cause orphaned keys to accumulate:
334
+
335
+ - **Before fix**: 10,000 orphaned clients × 5 channels = 50,000+ keys = 100-500 MB leaked
336
+ - **After fix**: All orphaned keys are cleaned up automatically
337
+
338
+ **Recommendation**: Schedule cleanup every 5-10 minutes in production environments.
339
+
237
340
  ## Troubleshooting
238
341
 
239
342
  ### Connection Issues
@@ -1,5 +1,5 @@
1
1
  module Faye
2
2
  class Redis
3
- VERSION = '1.0.4'
3
+ VERSION = '1.0.5'
4
4
  end
5
5
  end
data/lib/faye/redis.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'securerandom'
2
+ require 'set'
2
3
  require_relative 'redis/version'
3
4
  require_relative 'redis/logger'
4
5
  require_relative 'redis/connection'
@@ -139,6 +140,19 @@ module Faye
139
140
  @connection.disconnect
140
141
  end
141
142
 
143
+ # Clean up expired clients and their associated data
144
+ def cleanup_expired(&callback)
145
+ @client_registry.cleanup_expired do |expired_count|
146
+ @logger.info("Cleaned up #{expired_count} expired clients") if expired_count > 0
147
+
148
+ # Always clean up orphaned subscription keys (even if no expired clients)
149
+ # This handles cases where subscriptions were orphaned due to crashes
150
+ cleanup_orphaned_subscriptions do
151
+ callback.call(expired_count) if callback
152
+ end
153
+ end
154
+ end
155
+
142
156
  private
143
157
 
144
158
  def generate_client_id
@@ -171,6 +185,62 @@ module Faye
171
185
  end
172
186
  end
173
187
 
188
+ def cleanup_orphaned_subscriptions(&callback)
189
+ # Get all active client IDs
190
+ @client_registry.all do |active_clients|
191
+ active_set = active_clients.to_set
192
+ namespace = @options[:namespace] || 'faye'
193
+
194
+ # Scan for subscription keys and clean up orphaned ones
195
+ @connection.with_redis do |redis|
196
+ cursor = "0"
197
+ orphaned_keys = []
198
+
199
+ loop do
200
+ cursor, keys = redis.scan(cursor, match: "#{namespace}:subscriptions:*", count: 100)
201
+
202
+ keys.each do |key|
203
+ # Extract client_id from key (format: namespace:subscriptions:client_id)
204
+ client_id = key.split(':').last
205
+ orphaned_keys << client_id unless active_set.include?(client_id)
206
+ end
207
+
208
+ break if cursor == "0"
209
+ end
210
+
211
+ # Clean up orphaned subscription data
212
+ if orphaned_keys.any?
213
+ @logger.info("Cleaning up #{orphaned_keys.size} orphaned subscription sets")
214
+
215
+ orphaned_keys.each do |client_id|
216
+ # Get channels for this orphaned client
217
+ channels = redis.smembers("#{namespace}:subscriptions:#{client_id}")
218
+
219
+ # Remove in batch
220
+ redis.pipelined do |pipeline|
221
+ # Delete client's subscription list
222
+ pipeline.del("#{namespace}:subscriptions:#{client_id}")
223
+
224
+ # Delete each subscription metadata and remove from channel subscribers
225
+ channels.each do |channel|
226
+ pipeline.del("#{namespace}:subscription:#{client_id}:#{channel}")
227
+ pipeline.srem("#{namespace}:channels:#{channel}", client_id)
228
+ end
229
+
230
+ # Delete message queue if exists
231
+ pipeline.del("#{namespace}:messages:#{client_id}")
232
+ end
233
+ end
234
+ end
235
+ end
236
+
237
+ EventMachine.next_tick { callback.call } if callback
238
+ end
239
+ rescue => e
240
+ log_error("Failed to cleanup orphaned subscriptions: #{e.message}")
241
+ EventMachine.next_tick { callback.call } if callback
242
+ end
243
+
174
244
  def setup_message_routing
175
245
  # Subscribe to message events from other servers
176
246
  @pubsub_coordinator.on_message do |channel, message|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: faye-redis-ng
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Zac
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-10-15 00:00:00.000000000 Z
11
+ date: 2025-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis