message_bus 3.3.3 → 3.3.7
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/.eslintrc.js +21 -0
- data/.github/workflows/ci.yml +71 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +3 -1
- data/CHANGELOG +36 -8
- data/DEV.md +7 -0
- data/Gemfile +0 -25
- data/LICENSE +1 -1
- data/README.md +34 -15
- data/Rakefile +13 -8
- data/assets/message-bus-ajax.js +4 -10
- data/assets/message-bus.js +69 -76
- data/bench/codecs/all_codecs.rb +39 -0
- data/bench/codecs/marshal.rb +11 -0
- data/bench/codecs/packed_string.rb +67 -0
- data/bench/codecs/string_hack.rb +47 -0
- data/bench/codecs_large_user_list.rb +29 -0
- data/bench/codecs_standard_message.rb +29 -0
- data/examples/bench/bench.lua +2 -2
- data/lib/message_bus/backends/base.rb +3 -5
- data/lib/message_bus/backends/memory.rb +0 -2
- data/lib/message_bus/backends/postgres.rb +7 -5
- data/lib/message_bus/backends/redis.rb +3 -5
- data/lib/message_bus/client.rb +3 -7
- data/lib/message_bus/codec/base.rb +18 -0
- data/lib/message_bus/codec/json.rb +15 -0
- data/lib/message_bus/codec/oj.rb +21 -0
- data/lib/message_bus/connection_manager.rb +1 -1
- data/lib/message_bus/distributed_cache.rb +2 -1
- data/lib/message_bus/http_client.rb +2 -2
- data/lib/message_bus/rack/diagnostics.rb +30 -8
- data/lib/message_bus/rack/middleware.rb +22 -16
- data/lib/message_bus/rack/thin_ext.rb +1 -1
- data/lib/message_bus/version.rb +1 -1
- data/lib/message_bus.rb +38 -23
- data/message_bus.gemspec +20 -5
- data/package-lock.json +3744 -0
- data/package.json +14 -4
- data/spec/assets/SpecHelper.js +6 -5
- data/spec/assets/message-bus.spec.js +9 -6
- data/spec/helpers.rb +17 -6
- data/spec/integration/http_client_spec.rb +1 -1
- data/spec/lib/message_bus/backend_spec.rb +12 -44
- data/spec/lib/message_bus/client_spec.rb +6 -6
- data/spec/lib/message_bus/distributed_cache_spec.rb +5 -7
- data/spec/lib/message_bus/multi_process_spec.rb +1 -1
- data/spec/lib/message_bus/rack/middleware_spec.rb +16 -5
- data/spec/lib/message_bus_spec.rb +18 -7
- data/spec/spec_helper.rb +8 -9
- data/spec/support/jasmine-browser.json +16 -0
- metadata +230 -13
- data/.travis.yml +0 -17
- data/lib/message_bus/em_ext.rb +0 -6
- data/spec/assets/support/jasmine.yml +0 -126
- data/spec/assets/support/jasmine_helper.rb +0 -11
- data/vendor/assets/javascripts/message-bus-ajax.js +0 -44
- data/vendor/assets/javascripts/message-bus.js +0 -556
data/assets/message-bus.js
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
/*
|
1
|
+
/*global define, jQuery*/
|
2
2
|
|
3
3
|
(function (root, factory) {
|
4
|
-
if (typeof define ===
|
4
|
+
if (typeof define === "function" && define.amd) {
|
5
5
|
// AMD. Register as an anonymous module.
|
6
|
-
define([], function (
|
6
|
+
define([], function () {
|
7
7
|
// Also create a global in case some scripts
|
8
8
|
// that are loaded still are looking for
|
9
9
|
// a global even when an AMD loader is in use.
|
@@ -13,12 +13,12 @@
|
|
13
13
|
// Browser globals
|
14
14
|
root.MessageBus = factory();
|
15
15
|
}
|
16
|
-
}(typeof self !==
|
16
|
+
})(typeof self !== "undefined" ? self : this, function () {
|
17
17
|
"use strict";
|
18
18
|
|
19
19
|
// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
|
20
|
-
var uniqueId = function() {
|
21
|
-
return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function(c) {
|
20
|
+
var uniqueId = function () {
|
21
|
+
return "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
22
22
|
var r = (Math.random() * 16) | 0;
|
23
23
|
var v = c === "x" ? r : (r & 0x3) | 0x8;
|
24
24
|
return v.toString(16);
|
@@ -31,8 +31,6 @@
|
|
31
31
|
var started = false;
|
32
32
|
var clientId = uniqueId();
|
33
33
|
var callbacks = [];
|
34
|
-
var queue = [];
|
35
|
-
var interval = null;
|
36
34
|
var failCount = 0;
|
37
35
|
var baseUrl = "/";
|
38
36
|
var paused = false;
|
@@ -44,7 +42,7 @@
|
|
44
42
|
var totalAjaxCalls = 0;
|
45
43
|
var lastAjax;
|
46
44
|
|
47
|
-
var isHidden = (function() {
|
45
|
+
var isHidden = (function () {
|
48
46
|
var prefixes = ["", "webkit", "ms", "moz"];
|
49
47
|
var hiddenProperty;
|
50
48
|
for (var i = 0; i < prefixes.length; i++) {
|
@@ -55,7 +53,7 @@
|
|
55
53
|
}
|
56
54
|
}
|
57
55
|
|
58
|
-
return function() {
|
56
|
+
return function () {
|
59
57
|
if (hiddenProperty !== undefined) {
|
60
58
|
return document[hiddenProperty];
|
61
59
|
} else {
|
@@ -64,7 +62,7 @@
|
|
64
62
|
};
|
65
63
|
})();
|
66
64
|
|
67
|
-
var hasLocalStorage = (function() {
|
65
|
+
var hasLocalStorage = (function () {
|
68
66
|
try {
|
69
67
|
localStorage.setItem("mbTestLocalStorage", Date.now());
|
70
68
|
localStorage.removeItem("mbTestLocalStorage");
|
@@ -74,13 +72,13 @@
|
|
74
72
|
}
|
75
73
|
})();
|
76
74
|
|
77
|
-
var updateLastAjax = function() {
|
75
|
+
var updateLastAjax = function () {
|
78
76
|
if (hasLocalStorage) {
|
79
77
|
localStorage.setItem("__mbLastAjax", Date.now());
|
80
78
|
}
|
81
79
|
};
|
82
80
|
|
83
|
-
var hiddenTabShouldWait = function() {
|
81
|
+
var hiddenTabShouldWait = function () {
|
84
82
|
if (hasLocalStorage && isHidden()) {
|
85
83
|
var lastAjaxCall = parseInt(localStorage.getItem("__mbLastAjax"), 10);
|
86
84
|
var deltaAjax = Date.now() - lastAjaxCall;
|
@@ -91,20 +89,21 @@
|
|
91
89
|
};
|
92
90
|
|
93
91
|
var hasonprogress = new XMLHttpRequest().onprogress === null;
|
94
|
-
var allowChunked = function() {
|
92
|
+
var allowChunked = function () {
|
95
93
|
return me.enableChunkedEncoding && hasonprogress;
|
96
94
|
};
|
97
95
|
|
98
|
-
var shouldLongPoll = function() {
|
96
|
+
var shouldLongPoll = function () {
|
99
97
|
return (
|
100
98
|
me.alwaysLongPoll ||
|
101
99
|
(me.shouldLongPollCallback ? me.shouldLongPollCallback() : !isHidden())
|
102
100
|
);
|
103
101
|
};
|
104
102
|
|
105
|
-
var processMessages = function(messages) {
|
106
|
-
|
107
|
-
|
103
|
+
var processMessages = function (messages) {
|
104
|
+
if (!messages || messages.length === 0) {
|
105
|
+
return false;
|
106
|
+
}
|
108
107
|
|
109
108
|
for (var i = 0; i < messages.length; i++) {
|
110
109
|
var message = messages[i];
|
@@ -136,7 +135,7 @@
|
|
136
135
|
return true;
|
137
136
|
};
|
138
137
|
|
139
|
-
var reqSuccess = function(messages) {
|
138
|
+
var reqSuccess = function (messages) {
|
140
139
|
failCount = 0;
|
141
140
|
if (paused) {
|
142
141
|
if (messages) {
|
@@ -150,7 +149,7 @@
|
|
150
149
|
return false;
|
151
150
|
};
|
152
151
|
|
153
|
-
var longPoller = function(poll, data) {
|
152
|
+
var longPoller = function (poll, data) {
|
154
153
|
if (ajaxInProgress) {
|
155
154
|
// never allow concurrent ajax reqs
|
156
155
|
return;
|
@@ -183,7 +182,7 @@
|
|
183
182
|
|
184
183
|
var dataType = chunked ? "text" : "json";
|
185
184
|
|
186
|
-
var handle_progress = function(payload, position) {
|
185
|
+
var handle_progress = function (payload, position) {
|
187
186
|
var separator = "\r\n|\r\n";
|
188
187
|
var endChunk = payload.indexOf(separator, position);
|
189
188
|
|
@@ -206,31 +205,13 @@
|
|
206
205
|
return handle_progress(payload, endChunk + separator.length);
|
207
206
|
};
|
208
207
|
|
209
|
-
var disableChunked = function() {
|
208
|
+
var disableChunked = function () {
|
210
209
|
if (me.longPoll) {
|
211
210
|
me.longPoll.abort();
|
212
211
|
chunkedBackoff = 30;
|
213
212
|
}
|
214
213
|
};
|
215
214
|
|
216
|
-
var setOnProgressListener = function(xhr) {
|
217
|
-
var position = 0;
|
218
|
-
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
219
|
-
// this is messing with us, so just backoff from using chunked for now
|
220
|
-
var chunkedTimeout = setTimeout(disableChunked, 3000);
|
221
|
-
xhr.onprogress = function() {
|
222
|
-
clearTimeout(chunkedTimeout);
|
223
|
-
if (
|
224
|
-
xhr.getResponseHeader("Content-Type") ===
|
225
|
-
"application/json; charset=utf-8"
|
226
|
-
) {
|
227
|
-
// not chunked we are sending json back
|
228
|
-
chunked = false;
|
229
|
-
return;
|
230
|
-
}
|
231
|
-
position = handle_progress(xhr.responseText, position);
|
232
|
-
};
|
233
|
-
};
|
234
215
|
if (!me.ajax) {
|
235
216
|
throw new Error("Either jQuery or the ajax adapter must be loaded");
|
236
217
|
}
|
@@ -246,19 +227,18 @@
|
|
246
227
|
"/poll" +
|
247
228
|
(!longPoll ? "?dlp=t" : ""),
|
248
229
|
data: data,
|
249
|
-
cache: false,
|
250
230
|
async: true,
|
251
231
|
dataType: dataType,
|
252
232
|
type: "POST",
|
253
233
|
headers: headers,
|
254
234
|
messageBus: {
|
255
235
|
chunked: chunked,
|
256
|
-
onProgressListener: function(xhr) {
|
236
|
+
onProgressListener: function (xhr) {
|
257
237
|
var position = 0;
|
258
238
|
// if it takes longer than 3000 ms to get first chunk, we have some proxy
|
259
239
|
// this is messing with us, so just backoff from using chunked for now
|
260
240
|
var chunkedTimeout = setTimeout(disableChunked, 3000);
|
261
|
-
return (xhr.onprogress = function() {
|
241
|
+
return (xhr.onprogress = function () {
|
262
242
|
clearTimeout(chunkedTimeout);
|
263
243
|
if (
|
264
244
|
xhr.getResponseHeader("Content-Type") ===
|
@@ -269,9 +249,9 @@
|
|
269
249
|
position = handle_progress(xhr.responseText, position);
|
270
250
|
}
|
271
251
|
});
|
272
|
-
}
|
252
|
+
},
|
273
253
|
},
|
274
|
-
xhr: function() {
|
254
|
+
xhr: function () {
|
275
255
|
var xhr = jQuery.ajaxSettings.xhr();
|
276
256
|
if (!chunked) {
|
277
257
|
return xhr;
|
@@ -279,7 +259,7 @@
|
|
279
259
|
this.messageBus.onProgressListener(xhr);
|
280
260
|
return xhr;
|
281
261
|
},
|
282
|
-
success: function(messages) {
|
262
|
+
success: function (messages) {
|
283
263
|
if (!chunked) {
|
284
264
|
// we may have requested text so jQuery will not parse
|
285
265
|
if (typeof messages === "string") {
|
@@ -288,7 +268,7 @@
|
|
288
268
|
gotData = reqSuccess(messages);
|
289
269
|
}
|
290
270
|
},
|
291
|
-
error: function(xhr, textStatus
|
271
|
+
error: function (xhr, textStatus) {
|
292
272
|
if (xhr.status === 429) {
|
293
273
|
var tryAfter =
|
294
274
|
parseInt(
|
@@ -307,7 +287,7 @@
|
|
307
287
|
totalAjaxFailures += 1;
|
308
288
|
}
|
309
289
|
},
|
310
|
-
complete: function() {
|
290
|
+
complete: function () {
|
311
291
|
ajaxInProgress = false;
|
312
292
|
|
313
293
|
var interval;
|
@@ -345,14 +325,14 @@
|
|
345
325
|
}
|
346
326
|
|
347
327
|
if (started) {
|
348
|
-
pollTimeout = setTimeout(function() {
|
328
|
+
pollTimeout = setTimeout(function () {
|
349
329
|
pollTimeout = null;
|
350
330
|
poll();
|
351
331
|
}, interval);
|
352
332
|
}
|
353
333
|
|
354
334
|
me.longPoll = null;
|
355
|
-
}
|
335
|
+
},
|
356
336
|
});
|
357
337
|
|
358
338
|
return req;
|
@@ -374,7 +354,7 @@
|
|
374
354
|
baseUrl: baseUrl,
|
375
355
|
headers: {},
|
376
356
|
ajax: typeof jQuery !== "undefined" && jQuery.ajax,
|
377
|
-
diagnostics: function() {
|
357
|
+
diagnostics: function () {
|
378
358
|
console.log("Stopped: " + stopped + " Started: " + started);
|
379
359
|
console.log("Current callbacks");
|
380
360
|
console.log(callbacks);
|
@@ -391,42 +371,50 @@
|
|
391
371
|
);
|
392
372
|
},
|
393
373
|
|
394
|
-
pause: function() {
|
374
|
+
pause: function () {
|
395
375
|
paused = true;
|
396
376
|
},
|
397
377
|
|
398
|
-
resume: function() {
|
378
|
+
resume: function () {
|
399
379
|
paused = false;
|
400
380
|
processMessages(later);
|
401
381
|
later = [];
|
402
382
|
},
|
403
383
|
|
404
|
-
stop: function() {
|
384
|
+
stop: function () {
|
405
385
|
stopped = true;
|
406
386
|
started = false;
|
407
387
|
if (delayPollTimeout) {
|
408
388
|
clearTimeout(delayPollTimeout);
|
409
389
|
delayPollTimeout = null;
|
410
390
|
}
|
391
|
+
if (pollTimeout) {
|
392
|
+
clearTimeout(pollTimeout);
|
393
|
+
pollTimeout = null;
|
394
|
+
}
|
411
395
|
if (me.longPoll) {
|
412
396
|
me.longPoll.abort();
|
413
397
|
}
|
398
|
+
if (me.onVisibilityChange) {
|
399
|
+
document.removeEventListener("visibilitychange", me.onVisibilityChange);
|
400
|
+
me.onVisibilityChange = null;
|
401
|
+
}
|
414
402
|
},
|
415
403
|
|
416
404
|
// Start polling
|
417
|
-
start: function() {
|
405
|
+
start: function () {
|
418
406
|
if (started) return;
|
419
407
|
started = true;
|
420
408
|
stopped = false;
|
421
409
|
|
422
|
-
var poll = function() {
|
410
|
+
var poll = function () {
|
423
411
|
if (stopped) {
|
424
412
|
return;
|
425
413
|
}
|
426
414
|
|
427
415
|
if (callbacks.length === 0 || hiddenTabShouldWait()) {
|
428
416
|
if (!delayPollTimeout) {
|
429
|
-
delayPollTimeout = setTimeout(function() {
|
417
|
+
delayPollTimeout = setTimeout(function () {
|
430
418
|
delayPollTimeout = null;
|
431
419
|
poll();
|
432
420
|
}, parseInt(500 + Math.random() * 500));
|
@@ -448,25 +436,28 @@
|
|
448
436
|
|
449
437
|
// monitor visibility, issue a new long poll when the page shows
|
450
438
|
if (document.addEventListener && "hidden" in document) {
|
451
|
-
me.
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
439
|
+
me.onVisibilityChange = function () {
|
440
|
+
if (
|
441
|
+
!document.hidden &&
|
442
|
+
!me.longPoll &&
|
443
|
+
(pollTimeout || delayPollTimeout)
|
444
|
+
) {
|
445
|
+
clearTimeout(pollTimeout);
|
446
|
+
clearTimeout(delayPollTimeout);
|
447
|
+
|
448
|
+
delayPollTimeout = null;
|
449
|
+
pollTimeout = null;
|
450
|
+
poll();
|
462
451
|
}
|
463
|
-
|
452
|
+
};
|
453
|
+
|
454
|
+
document.addEventListener("visibilitychange", me.onVisibilityChange);
|
464
455
|
}
|
465
456
|
|
466
457
|
poll();
|
467
458
|
},
|
468
459
|
|
469
|
-
status: function() {
|
460
|
+
status: function () {
|
470
461
|
if (paused) {
|
471
462
|
return "paused";
|
472
463
|
} else if (started) {
|
@@ -485,7 +476,7 @@
|
|
485
476
|
// -2 will recieve last message + all new messages
|
486
477
|
// -3 will recieve last 2 messages + all new messages
|
487
478
|
// if undefined will default to -1
|
488
|
-
subscribe: function(channel, func, lastId) {
|
479
|
+
subscribe: function (channel, func, lastId) {
|
489
480
|
if (!started && !stopped) {
|
490
481
|
me.start();
|
491
482
|
}
|
@@ -493,7 +484,9 @@
|
|
493
484
|
if (lastId === null || typeof lastId === "undefined") {
|
494
485
|
lastId = -1;
|
495
486
|
} else if (typeof lastId !== "number") {
|
496
|
-
throw
|
487
|
+
throw (
|
488
|
+
"lastId has type " + typeof lastId + " but a number was expected."
|
489
|
+
);
|
497
490
|
}
|
498
491
|
|
499
492
|
if (typeof channel !== "string") {
|
@@ -503,7 +496,7 @@
|
|
503
496
|
callbacks.push({
|
504
497
|
channel: channel,
|
505
498
|
func: func,
|
506
|
-
last_id: lastId
|
499
|
+
last_id: lastId,
|
507
500
|
});
|
508
501
|
if (me.longPoll) {
|
509
502
|
me.longPoll.abort();
|
@@ -513,7 +506,7 @@
|
|
513
506
|
},
|
514
507
|
|
515
508
|
// Unsubscribe from a channel
|
516
|
-
unsubscribe: function(channel, func) {
|
509
|
+
unsubscribe: function (channel, func) {
|
517
510
|
// TODO allow for globbing in the middle of a channel name
|
518
511
|
// like /something/*/something
|
519
512
|
// at the moment we only support globbing /something/*
|
@@ -550,7 +543,7 @@
|
|
550
543
|
}
|
551
544
|
|
552
545
|
return removed;
|
553
|
-
}
|
546
|
+
},
|
554
547
|
};
|
555
548
|
return me;
|
556
|
-
})
|
549
|
+
});
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './packed_string'
|
4
|
+
require_relative './string_hack'
|
5
|
+
require_relative './marshal'
|
6
|
+
|
7
|
+
def all_codecs
|
8
|
+
{
|
9
|
+
json: MessageBus::Codec::Json.new,
|
10
|
+
oj: MessageBus::Codec::Oj.new,
|
11
|
+
marshal: MarshalCodec.new,
|
12
|
+
packed_string_4_bytes: PackedString.new("V"),
|
13
|
+
packed_string_8_bytes: PackedString.new("Q"),
|
14
|
+
string_hack: StringHack.new
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def bench_decode(hash, user_needle)
|
19
|
+
encoded_data = all_codecs.map do |name, codec|
|
20
|
+
[
|
21
|
+
name, codec, codec.encode(hash.dup)
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
Benchmark.ips do |x|
|
26
|
+
|
27
|
+
encoded_data.each do |name, codec, encoded|
|
28
|
+
x.report(name) do |n|
|
29
|
+
while n > 0
|
30
|
+
decoded = codec.decode(encoded)
|
31
|
+
decoded["user_ids"].include?(user_needle)
|
32
|
+
n -= 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
x.compare!
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PackedString
|
4
|
+
class FastIdList
|
5
|
+
def self.from_array(array, pack_with)
|
6
|
+
new(array.sort.pack("#{pack_with}*"), pack_with)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.from_string(string, pack_with)
|
10
|
+
new(string, pack_with)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(packed, pack_with)
|
14
|
+
raise "unknown pack format, expecting Q or V" if pack_with != "V" && pack_with != "Q"
|
15
|
+
@packed = packed
|
16
|
+
@pack_with = pack_with
|
17
|
+
@slot_size = pack_with == "V" ? 4 : 8
|
18
|
+
end
|
19
|
+
|
20
|
+
def include?(id)
|
21
|
+
found = (0...length).bsearch do |index|
|
22
|
+
@packed.byteslice(index * @slot_size, @slot_size).unpack1(@pack_with) >= id
|
23
|
+
end
|
24
|
+
|
25
|
+
found && @packed.byteslice(found * @slot_size, @slot_size).unpack1(@pack_with) == id
|
26
|
+
end
|
27
|
+
|
28
|
+
def length
|
29
|
+
@length ||= @packed.bytesize / @slot_size
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_a
|
33
|
+
@packed.unpack("#{@pack_with}*")
|
34
|
+
end
|
35
|
+
|
36
|
+
def to_s
|
37
|
+
@packed
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(pack_with = "V")
|
42
|
+
@pack_with = pack_with
|
43
|
+
@oj_options = { mode: :compat }
|
44
|
+
end
|
45
|
+
|
46
|
+
def encode(hash)
|
47
|
+
|
48
|
+
if user_ids = hash["user_ids"]
|
49
|
+
hash["user_ids"] = FastIdList.from_array(hash["user_ids"], @pack_with).to_s
|
50
|
+
end
|
51
|
+
|
52
|
+
hash["data"] = ::Oj.dump(hash["data"], @oj_options)
|
53
|
+
|
54
|
+
Marshal.dump(hash)
|
55
|
+
end
|
56
|
+
|
57
|
+
def decode(payload)
|
58
|
+
result = Marshal.load(payload) # rubocop:disable Security/MarshalLoad
|
59
|
+
result["data"] = ::Oj.load(result["data"], @oj_options)
|
60
|
+
|
61
|
+
if str = result["user_ids"]
|
62
|
+
result["user_ids"] = FastIdList.from_string(str, @pack_with)
|
63
|
+
end
|
64
|
+
|
65
|
+
result
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class StringHack
|
4
|
+
class FastIdList
|
5
|
+
def self.from_array(array)
|
6
|
+
new(",#{array.join(",")},")
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.from_string(string)
|
10
|
+
new(string)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(packed)
|
14
|
+
@packed = packed
|
15
|
+
end
|
16
|
+
|
17
|
+
def include?(id)
|
18
|
+
@packed.include?(",#{id},")
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@packed
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@oj_options = { mode: :compat }
|
28
|
+
end
|
29
|
+
|
30
|
+
def encode(hash)
|
31
|
+
if user_ids = hash["user_ids"]
|
32
|
+
hash["user_ids"] = FastIdList.from_array(user_ids).to_s
|
33
|
+
end
|
34
|
+
|
35
|
+
::Oj.dump(hash, @oj_options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def decode(payload)
|
39
|
+
result = ::Oj.load(payload, @oj_options)
|
40
|
+
|
41
|
+
if str = result["user_ids"]
|
42
|
+
result["user_ids"] = FastIdList.from_string(str)
|
43
|
+
end
|
44
|
+
|
45
|
+
result
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'message_bus', path: '../'
|
8
|
+
gem 'benchmark-ips'
|
9
|
+
gem 'oj'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'benchmark/ips'
|
13
|
+
require 'message_bus'
|
14
|
+
require_relative 'codecs/all_codecs'
|
15
|
+
|
16
|
+
bench_decode({
|
17
|
+
"data" => "hello world",
|
18
|
+
"user_ids" => (1..10000).to_a,
|
19
|
+
"group_ids" => nil,
|
20
|
+
"client_ids" => nil
|
21
|
+
}, 5000
|
22
|
+
)
|
23
|
+
|
24
|
+
# packed_string_4_bytes: 127176.1 i/s
|
25
|
+
# packed_string_8_bytes: 94494.6 i/s - 1.35x (± 0.00) slower
|
26
|
+
# string_hack: 26403.4 i/s - 4.82x (± 0.00) slower
|
27
|
+
# marshal: 4985.5 i/s - 25.51x (± 0.00) slower
|
28
|
+
# oj: 3072.9 i/s - 41.39x (± 0.00) slower
|
29
|
+
# json: 2222.7 i/s - 57.22x (± 0.00) slower
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'message_bus', path: '../'
|
8
|
+
gem 'benchmark-ips'
|
9
|
+
gem 'oj'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'benchmark/ips'
|
13
|
+
require 'message_bus'
|
14
|
+
require_relative 'codecs/all_codecs'
|
15
|
+
|
16
|
+
bench_decode({
|
17
|
+
"data" => { amazing: "hello world this is an amazing message hello there!!!", another_key: [2, 3, 4] },
|
18
|
+
"user_ids" => [1, 2, 3],
|
19
|
+
"group_ids" => [1],
|
20
|
+
"client_ids" => nil
|
21
|
+
}, 2
|
22
|
+
)
|
23
|
+
|
24
|
+
# marshal: 504885.6 i/s
|
25
|
+
# json: 401050.9 i/s - 1.26x (± 0.00) slower
|
26
|
+
# oj: 340847.4 i/s - 1.48x (± 0.00) slower
|
27
|
+
# string_hack: 296741.6 i/s - 1.70x (± 0.00) slower
|
28
|
+
# packed_string_4_bytes: 207942.6 i/s - 2.43x (± 0.00) slower
|
29
|
+
# packed_string_8_bytes: 206093.0 i/s - 2.45x (± 0.00) slower
|
data/examples/bench/bench.lua
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
-- wrk returns lots of read errors, this is unavoidable cause
|
2
2
|
--
|
3
|
-
-- 1. There is no internal
|
3
|
+
-- 1. There is no internal implementation of chunked encoding in wrk (which would be ideal)
|
4
4
|
--
|
5
5
|
-- 2. MessageBus gem does not provide http keepalive (by design), and can not provide content length
|
6
|
-
-- if MessageBus provided keepalive it would have to be able to
|
6
|
+
-- if MessageBus provided keepalive it would have to be able to re-dispatch requests to rack, something
|
7
7
|
-- that is not supported by the underlying rack hijack protocol, once a req is hijacked it can not be
|
8
8
|
-- returned
|
9
9
|
--
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "message_bus/backends"
|
4
|
-
|
5
3
|
module MessageBus
|
6
4
|
module Backends
|
7
5
|
# Backends provide a consistent API over a variety of options for persisting
|
@@ -64,7 +62,7 @@ module MessageBus
|
|
64
62
|
attr_accessor :max_backlog_size
|
65
63
|
# @return [Integer] the largest permitted size (number of messages) for the global backlog; beyond this capacity, old messages will be dropped.
|
66
64
|
attr_accessor :max_global_backlog_size
|
67
|
-
# @return [Integer] the longest amount of time a message may live in a backlog before
|
65
|
+
# @return [Integer] the longest amount of time a message may live in a backlog before being removed, in seconds.
|
68
66
|
attr_accessor :max_backlog_age
|
69
67
|
# Typically, backlogs are trimmed whenever we publish to them. This setting allows some tolerance in order to improve performance.
|
70
68
|
# @return [Integer] the interval of publications between which the backlog will not be cleared.
|
@@ -76,7 +74,7 @@ module MessageBus
|
|
76
74
|
# @param [Integer] max_backlog_size the largest permitted size (number of messages) for per-channel backlogs; beyond this capacity, old messages will be dropped.
|
77
75
|
def initialize(config = {}, max_backlog_size = 1000); end
|
78
76
|
|
79
|
-
# Performs routines specific to the backend that are necessary after a process fork, typically
|
77
|
+
# Performs routines specific to the backend that are necessary after a process fork, typically triggered by a forking webserver. Typically this re-opens sockets to the backend.
|
80
78
|
def after_fork
|
81
79
|
raise ConcreteClassMustImplementError
|
82
80
|
end
|
@@ -98,7 +96,7 @@ module MessageBus
|
|
98
96
|
# @param [JSON] data some data to publish to the channel. Must be an object that can be encoded as JSON
|
99
97
|
# @param [Hash] opts
|
100
98
|
# @option opts [Boolean] :queue_in_memory (true) whether or not to hold the message in an in-memory buffer if publication fails, to be re-tried later
|
101
|
-
# @option opts [Integer] :max_backlog_age (`self.max_backlog_age`) the longest amount of time a message may live in a backlog before
|
99
|
+
# @option opts [Integer] :max_backlog_age (`self.max_backlog_age`) the longest amount of time a message may live in a backlog before being removed, in seconds
|
102
100
|
# @option opts [Integer] :max_backlog_size (`self.max_backlog_size`) the largest permitted size (number of messages) for the channel backlog; beyond this capacity, old messages will be dropped
|
103
101
|
#
|
104
102
|
# @return [Integer] the channel-specific ID the message was given
|