message_bus 4.2.0 → 4.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/package.json CHANGED
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "homepage": "https://github.com/discourse/message_bus#readme",
22
22
  "devDependencies": {
23
- "eslint": "^7.27.0",
23
+ "eslint": "^8.31.0",
24
24
  "jasmine-browser-runner": "^0.10.0",
25
25
  "jasmine-core": "^3.10.1"
26
26
  }
@@ -33,8 +33,7 @@ beforeEach(function () {
33
33
  MockedXMLHttpRequest.prototype.send = function(){
34
34
  this.readyState = 4
35
35
  this.responseText = encodeChunks(this, spec.responseChunks);
36
- this.statusText = 'OK';
37
- this.status = 200;
36
+ this.status = spec.responseStatus;
38
37
  if (this.onprogress){ this.onprogress(); }
39
38
  this.onreadystatechange()
40
39
  }
@@ -53,8 +52,8 @@ beforeEach(function () {
53
52
  this.headers[k] = v;
54
53
  }
55
54
 
56
- MockedXMLHttpRequest.prototype.getResponseHeader = function(){
57
- return 'text/plain; charset=utf-8';
55
+ MockedXMLHttpRequest.prototype.getResponseHeader = function(headerName){
56
+ return spec.responseHeaders[headerName];
58
57
  }
59
58
 
60
59
  MessageBus.xhrImplementation = MockedXMLHttpRequest
@@ -64,12 +63,19 @@ beforeEach(function () {
64
63
  {channel: '/test', data: {password: 'MessageBusRocks!'}}
65
64
  ];
66
65
 
66
+ this.responseStatus = 200;
67
+ this.responseHeaders = {
68
+ "Content-Type": 'text/plain; charset=utf-8',
69
+ };
70
+
67
71
  MessageBus.start();
68
72
  });
69
73
 
70
74
  afterEach(function(){
71
75
  MessageBus.stop()
72
76
  MessageBus.callbacks.splice(0, MessageBus.callbacks.length)
77
+ MessageBus.shouldLongPollCallback = null;
78
+ MessageBus.enableChunkedEncoding = true;
73
79
  });
74
80
 
75
81
  window.testMB = function(description, testFn, path, data){
@@ -1,7 +1,11 @@
1
+ /* eslint-env es2022 */
1
2
  /* global describe, it, spyOn, MessageBus, expect, jasmine, testMB */
2
3
 
3
- describe("Messagebus", function() {
4
+ function approxEqual(valueOne, valueTwo) {
5
+ return Math.abs(valueOne - valueTwo) < 500;
6
+ }
4
7
 
8
+ describe("Messagebus", function () {
5
9
  it("submits change requests", function(done){
6
10
  spyOn(this.MockedXMLHttpRequest.prototype, 'send').and.callThrough();
7
11
  var spec = this;
@@ -97,4 +101,69 @@ describe("Messagebus", function() {
97
101
  })
98
102
  });
99
103
 
104
+ it("respects Retry-After response header when larger than 15 seconds", async function () {
105
+ spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
106
+ spyOn(window, "setTimeout").and.callThrough();
107
+
108
+ this.responseStatus = 429;
109
+ this.responseHeaders["Retry-After"] = "23";
110
+
111
+ await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
112
+
113
+ const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
114
+ expect(nextPollScheduledIn).toEqual(23000);
115
+ });
116
+
117
+ it("retries after 15s for lower retry-after values", async function () {
118
+ spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
119
+ spyOn(window, "setTimeout").and.callThrough();
120
+
121
+ this.responseStatus = 429;
122
+ this.responseHeaders["Retry-After"] = "13";
123
+
124
+ await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
125
+
126
+ const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
127
+ expect(nextPollScheduledIn).toEqual(15000);
128
+ });
129
+
130
+ it("waits for callbackInterval after receiving data in chunked long-poll mode", async function () {
131
+ // The callbackInterval is equal to the length of the server response in chunked long-poll mode, so
132
+ // this ultimately ends up being a continuous stream of requests
133
+
134
+ spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
135
+ spyOn(window, "setTimeout").and.callThrough();
136
+
137
+ await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
138
+
139
+ const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
140
+ expect(
141
+ approxEqual(nextPollScheduledIn, MessageBus.callbackInterval)
142
+ ).toEqual(true);
143
+ });
144
+
145
+ it("waits for backgroundCallbackInterval after receiving data in non-long-poll mode", async function () {
146
+ spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
147
+ spyOn(window, "setTimeout").and.callThrough();
148
+ MessageBus.shouldLongPollCallback = () => false;
149
+ MessageBus.enableChunkedEncoding = false;
150
+
151
+ await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
152
+
153
+ const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
154
+ expect(
155
+ approxEqual(nextPollScheduledIn, MessageBus.backgroundCallbackInterval)
156
+ ).toEqual(true);
157
+ });
158
+
159
+ it("re-polls immediately after receiving data in non-chunked long-poll mode", async function () {
160
+ spyOn(this.MockedXMLHttpRequest.prototype, "send").and.callThrough();
161
+ spyOn(window, "setTimeout").and.callThrough();
162
+ MessageBus.enableChunkedEncoding = false;
163
+
164
+ await new Promise((resolve) => MessageBus.subscribe("/test", resolve));
165
+
166
+ const nextPollScheduledIn = window.setTimeout.calls.mostRecent().args[1];
167
+ expect(nextPollScheduledIn).toEqual(MessageBus.minPollInterval);
168
+ });
100
169
  });
@@ -392,11 +392,4 @@ describe BACKEND_CLASS do
392
392
  got.map { |m| m.data }.must_equal ["12"]
393
393
  end
394
394
 
395
- it 'should not lose redis config' do
396
- test_only :redis
397
- redis_config = { connector: Redis::Client::Connector }
398
- @bus.instance_variable_set(:@redis_config, redis_config)
399
- @bus.send(:new_redis_connection)
400
- expect(@bus.instance_variable_get(:@redis_config)[:connector]).must_equal Redis::Client::Connector
401
- end
402
395
  end
@@ -17,26 +17,6 @@ describe MessageBus do
17
17
  @bus.destroy
18
18
  end
19
19
 
20
- it "can be turned off" do
21
- @bus.off
22
-
23
- @bus.off?.must_equal true
24
- end
25
-
26
- it "can call destroy multiple times" do
27
- @bus.destroy
28
- @bus.destroy
29
- @bus.destroy
30
- end
31
-
32
- it "can be turned on after destroy" do
33
- @bus.destroy
34
-
35
- @bus.on
36
-
37
- @bus.after_fork
38
- end
39
-
40
20
  it "destroying immediately after `after_fork` does not lock" do
41
21
  10.times do
42
22
  @bus.on
@@ -216,6 +196,58 @@ describe MessageBus do
216
196
  @bus.backlog("/chuck").map { |i| i.data }.to_a.must_equal ['foo']
217
197
  end
218
198
 
199
+ it "can be turned off" do
200
+ @bus.off
201
+
202
+ @bus.off?.must_equal true
203
+
204
+ @bus.publish("/chuck", "norris")
205
+
206
+ @bus.backlog("/chuck").to_a.must_equal []
207
+ end
208
+
209
+ it "can be turned off only for subscriptions" do
210
+ @bus.off(disable_publish: false)
211
+
212
+ @bus.off?.must_equal true
213
+
214
+ data = []
215
+
216
+ @bus.subscribe("/chuck") do |msg|
217
+ data << msg.data
218
+ end
219
+
220
+ @bus.publish("/chuck", "norris")
221
+
222
+ @bus.on
223
+
224
+ @bus.subscribe("/chuck") do |msg|
225
+ data << msg.data
226
+ end
227
+
228
+ @bus.publish("/chuck", "berry")
229
+
230
+ wait_for(2000) { data.length > 0 }
231
+
232
+ data.must_equal ["berry"]
233
+
234
+ @bus.backlog("/chuck").map(&:data).to_a.must_equal ["norris", "berry"]
235
+ end
236
+
237
+ it "can call destroy multiple times" do
238
+ @bus.destroy
239
+ @bus.destroy
240
+ @bus.destroy
241
+ end
242
+
243
+ it "can be turned on after destroy" do
244
+ @bus.destroy
245
+
246
+ @bus.on
247
+
248
+ @bus.after_fork
249
+ end
250
+
219
251
  it "allows you to look up last_message" do
220
252
  @bus.publish("/bob", "dylan")
221
253
  @bus.publish("/bob", "marley")
@@ -40,7 +40,7 @@
40
40
  var pollTimeout = null;
41
41
  var totalAjaxFailures = 0;
42
42
  var totalAjaxCalls = 0;
43
- var lastAjax;
43
+ var lastAjaxStartedAt;
44
44
 
45
45
  var isHidden = (function () {
46
46
  var prefixes = ["", "webkit", "ms", "moz"];
@@ -156,11 +156,11 @@
156
156
  }
157
157
 
158
158
  var gotData = false;
159
- var aborted = false;
159
+ var abortedByClient = false;
160
160
  var rateLimited = false;
161
161
  var rateLimitedSeconds;
162
162
 
163
- lastAjax = new Date();
163
+ lastAjaxStartedAt = new Date();
164
164
  totalAjaxCalls += 1;
165
165
  data.__seq = totalAjaxCalls;
166
166
 
@@ -281,7 +281,7 @@
281
281
  rateLimitedSeconds = tryAfter;
282
282
  rateLimited = true;
283
283
  } else if (textStatus === "abort") {
284
- aborted = true;
284
+ abortedByClient = true;
285
285
  } else {
286
286
  failCount += 1;
287
287
  totalAjaxFailures += 1;
@@ -290,27 +290,40 @@
290
290
  complete: function () {
291
291
  ajaxInProgress = false;
292
292
 
293
- var interval;
293
+ var inLongPollingMode = shouldLongPoll();
294
+ var startNextRequestAfter;
294
295
  try {
295
296
  if (rateLimited) {
296
- interval = Math.max(me.minPollInterval, rateLimitedSeconds * 1000);
297
- } else if (gotData || aborted) {
298
- interval = me.minPollInterval;
297
+ // Respect `Retry-After` header
298
+ startNextRequestAfter = Math.max(
299
+ me.minPollInterval,
300
+ rateLimitedSeconds * 1000
301
+ );
302
+ } else if (abortedByClient) {
303
+ // Immediately trigger another poll
304
+ startNextRequestAfter = me.minPollInterval;
305
+ } else if (failCount > 2) {
306
+ // Linear backoff up to maxPollInterval
307
+ startNextRequestAfter = Math.min(
308
+ me.callbackInterval * failCount,
309
+ me.maxPollInterval
310
+ );
311
+ } else if (inLongPollingMode && gotData) {
312
+ // Immediately trigger another poll
313
+ startNextRequestAfter = me.minPollInterval;
299
314
  } else {
300
- interval = me.callbackInterval;
301
- if (failCount > 2) {
302
- interval = interval * failCount;
303
- } else if (!shouldLongPoll()) {
304
- interval = me.backgroundCallbackInterval;
305
- }
306
- if (interval > me.maxPollInterval) {
307
- interval = me.maxPollInterval;
308
- }
315
+ // Trigger next poll N seconds after the last one **started**
316
+ var targetRequestInterval = inLongPollingMode
317
+ ? me.callbackInterval
318
+ : me.backgroundCallbackInterval;
319
+
320
+ var elapsedSinceLastAjaxStarted = new Date() - lastAjaxStartedAt;
309
321
 
310
- interval -= new Date() - lastAjax;
322
+ startNextRequestAfter =
323
+ targetRequestInterval - elapsedSinceLastAjaxStarted;
311
324
 
312
- if (interval < 100) {
313
- interval = 100;
325
+ if (startNextRequestAfter < 100) {
326
+ startNextRequestAfter = 100;
314
327
  }
315
328
  }
316
329
  } catch (e) {
@@ -328,7 +341,7 @@
328
341
  pollTimeout = setTimeout(function () {
329
342
  pollTimeout = null;
330
343
  poll();
331
- }, interval);
344
+ }, startNextRequestAfter);
332
345
  }
333
346
 
334
347
  me.longPoll = null;
@@ -367,7 +380,9 @@
367
380
  totalAjaxFailures
368
381
  );
369
382
  console.log(
370
- "Last ajax call: " + (new Date() - lastAjax) / 1000 + " seconds ago"
383
+ "Last ajax call: " +
384
+ (new Date() - lastAjaxStartedAt) / 1000 +
385
+ " seconds ago"
371
386
  );
372
387
  },
373
388
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: message_bus
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.0
4
+ version: 4.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-22 00:00:00.000000000 Z
11
+ date: 2023-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack