mongodb-livedata-server 0.1.2 → 0.1.4

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.
Files changed (91) hide show
  1. package/dist/livedata_server.d.ts +4 -4
  2. package/dist/livedata_server.js +11 -11
  3. package/dist/meteor/binary-heap/max_heap.d.ts +31 -31
  4. package/dist/meteor/binary-heap/max_heap.js +186 -186
  5. package/dist/meteor/binary-heap/min_heap.d.ts +6 -6
  6. package/dist/meteor/binary-heap/min_heap.js +17 -17
  7. package/dist/meteor/binary-heap/min_max_heap.d.ts +11 -11
  8. package/dist/meteor/binary-heap/min_max_heap.js +48 -48
  9. package/dist/meteor/callback-hook/hook.d.ts +11 -11
  10. package/dist/meteor/callback-hook/hook.js +78 -78
  11. package/dist/meteor/ddp/crossbar.d.ts +15 -15
  12. package/dist/meteor/ddp/crossbar.js +136 -136
  13. package/dist/meteor/ddp/heartbeat.d.ts +19 -19
  14. package/dist/meteor/ddp/heartbeat.js +77 -77
  15. package/dist/meteor/ddp/livedata_server.d.ts +141 -142
  16. package/dist/meteor/ddp/livedata_server.js +403 -403
  17. package/dist/meteor/ddp/method-invocation.d.ts +35 -35
  18. package/dist/meteor/ddp/method-invocation.js +72 -72
  19. package/dist/meteor/ddp/random-stream.d.ts +8 -8
  20. package/dist/meteor/ddp/random-stream.js +100 -100
  21. package/dist/meteor/ddp/session-collection-view.d.ts +20 -20
  22. package/dist/meteor/ddp/session-collection-view.js +106 -106
  23. package/dist/meteor/ddp/session-document-view.d.ts +8 -8
  24. package/dist/meteor/ddp/session-document-view.js +82 -82
  25. package/dist/meteor/ddp/session.d.ts +75 -74
  26. package/dist/meteor/ddp/session.js +590 -589
  27. package/dist/meteor/ddp/stream_server.d.ts +20 -21
  28. package/dist/meteor/ddp/stream_server.js +181 -181
  29. package/dist/meteor/ddp/subscription.d.ts +94 -94
  30. package/dist/meteor/ddp/subscription.js +370 -370
  31. package/dist/meteor/ddp/utils.d.ts +8 -8
  32. package/dist/meteor/ddp/utils.js +104 -104
  33. package/dist/meteor/ddp/writefence.d.ts +20 -20
  34. package/dist/meteor/ddp/writefence.js +111 -111
  35. package/dist/meteor/diff-sequence/diff.d.ts +17 -17
  36. package/dist/meteor/diff-sequence/diff.js +257 -257
  37. package/dist/meteor/ejson/ejson.d.ts +82 -82
  38. package/dist/meteor/ejson/ejson.js +568 -569
  39. package/dist/meteor/ejson/stringify.d.ts +2 -2
  40. package/dist/meteor/ejson/stringify.js +119 -119
  41. package/dist/meteor/ejson/utils.d.ts +12 -12
  42. package/dist/meteor/ejson/utils.js +42 -42
  43. package/dist/meteor/mongo/caching_change_observer.d.ts +16 -16
  44. package/dist/meteor/mongo/caching_change_observer.js +63 -63
  45. package/dist/meteor/mongo/doc_fetcher.d.ts +7 -7
  46. package/dist/meteor/mongo/doc_fetcher.js +53 -53
  47. package/dist/meteor/mongo/geojson_utils.d.ts +3 -3
  48. package/dist/meteor/mongo/geojson_utils.js +40 -41
  49. package/dist/meteor/mongo/live_connection.d.ts +28 -28
  50. package/dist/meteor/mongo/live_connection.js +264 -264
  51. package/dist/meteor/mongo/live_cursor.d.ts +25 -25
  52. package/dist/meteor/mongo/live_cursor.js +60 -60
  53. package/dist/meteor/mongo/minimongo_common.d.ts +84 -84
  54. package/dist/meteor/mongo/minimongo_common.js +1998 -2002
  55. package/dist/meteor/mongo/minimongo_matcher.d.ts +23 -23
  56. package/dist/meteor/mongo/minimongo_matcher.js +283 -283
  57. package/dist/meteor/mongo/minimongo_sorter.d.ts +16 -16
  58. package/dist/meteor/mongo/minimongo_sorter.js +268 -268
  59. package/dist/meteor/mongo/observe_driver_utils.d.ts +9 -9
  60. package/dist/meteor/mongo/observe_driver_utils.js +72 -73
  61. package/dist/meteor/mongo/observe_multiplexer.d.ts +46 -46
  62. package/dist/meteor/mongo/observe_multiplexer.js +203 -203
  63. package/dist/meteor/mongo/oplog-observe-driver.d.ts +68 -68
  64. package/dist/meteor/mongo/oplog-observe-driver.js +918 -918
  65. package/dist/meteor/mongo/oplog_tailing.d.ts +35 -35
  66. package/dist/meteor/mongo/oplog_tailing.js +352 -352
  67. package/dist/meteor/mongo/oplog_v2_converter.d.ts +1 -1
  68. package/dist/meteor/mongo/oplog_v2_converter.js +125 -126
  69. package/dist/meteor/mongo/polling_observe_driver.d.ts +30 -30
  70. package/dist/meteor/mongo/polling_observe_driver.js +216 -221
  71. package/dist/meteor/mongo/synchronous-cursor.d.ts +17 -17
  72. package/dist/meteor/mongo/synchronous-cursor.js +261 -261
  73. package/dist/meteor/mongo/synchronous-queue.d.ts +13 -13
  74. package/dist/meteor/mongo/synchronous-queue.js +110 -110
  75. package/dist/meteor/ordered-dict/ordered_dict.d.ts +31 -31
  76. package/dist/meteor/ordered-dict/ordered_dict.js +198 -198
  77. package/dist/meteor/random/AbstractRandomGenerator.d.ts +42 -42
  78. package/dist/meteor/random/AbstractRandomGenerator.js +92 -92
  79. package/dist/meteor/random/AleaRandomGenerator.d.ts +13 -13
  80. package/dist/meteor/random/AleaRandomGenerator.js +90 -90
  81. package/dist/meteor/random/NodeRandomGenerator.d.ts +16 -16
  82. package/dist/meteor/random/NodeRandomGenerator.js +42 -42
  83. package/dist/meteor/random/createAleaGenerator.d.ts +2 -2
  84. package/dist/meteor/random/createAleaGenerator.js +32 -32
  85. package/dist/meteor/random/createRandom.d.ts +1 -1
  86. package/dist/meteor/random/createRandom.js +22 -22
  87. package/dist/meteor/random/main.d.ts +1 -1
  88. package/dist/meteor/random/main.js +12 -12
  89. package/dist/meteor/types.d.ts +1 -1
  90. package/dist/meteor/types.js +2 -2
  91. package/package.json +6 -5
@@ -1,352 +1,352 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports._escapeRegExp = exports._sleepForMs = exports.OplogHandle = exports.idForOp = exports.OPLOG_COLLECTION = void 0;
7
- const mongodb_1 = require("mongodb");
8
- const hook_1 = require("../callback-hook/hook");
9
- const crossbar_1 = require("../ddp/crossbar");
10
- const double_ended_queue_1 = __importDefault(require("double-ended-queue"));
11
- const ejson_1 = require("../ejson/ejson");
12
- const live_connection_1 = require("./live_connection");
13
- const live_cursor_1 = require("./live_cursor");
14
- exports.OPLOG_COLLECTION = 'oplog.rs';
15
- var TOO_FAR_BEHIND = +process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000;
16
- var TAIL_TIMEOUT = +process.env.METEOR_OPLOG_TAIL_TIMEOUT || 30000;
17
- var showTS = function (ts) {
18
- return "Timestamp(" + ts.getHighBits() + ", " + ts.getLowBits() + ")";
19
- };
20
- function idForOp(op) {
21
- if (op.op === 'd')
22
- return op.o._id;
23
- else if (op.op === 'i')
24
- return op.o._id;
25
- else if (op.op === 'u')
26
- return op.o2._id;
27
- else if (op.op === 'c')
28
- throw Error("Operator 'c' doesn't supply an object with id: " +
29
- (0, ejson_1.stringify)(op));
30
- else
31
- throw Error("Unknown op: " + (0, ejson_1.stringify)(op));
32
- }
33
- exports.idForOp = idForOp;
34
- ;
35
- class OplogHandle {
36
- constructor(oplogUrl, dbName) {
37
- var self = this;
38
- self._readyFuture = { promise: undefined, resolve: undefined };
39
- self._readyFuture.promise = new Promise(r => self._readyFuture.resolve = r);
40
- self._oplogUrl = oplogUrl;
41
- self._dbName = dbName;
42
- self._oplogLastEntryConnection = null;
43
- self._oplogTailConnection = null;
44
- self._stopped = false;
45
- self._tailHandle = null;
46
- self._crossbar = new crossbar_1._Crossbar({
47
- factPackage: "mongo-livedata", factName: "oplog-watchers"
48
- });
49
- self._baseOplogSelector = {
50
- ns: new RegExp("^(?:" + _escapeRegExp(self._dbName) + "\\.|admin\\.$cmd)"),
51
- $or: [
52
- { op: { $in: ['i', 'u', 'd'] } },
53
- // drop collection
54
- { op: 'c', 'o.drop': { $exists: true } },
55
- { op: 'c', 'o.dropDatabase': 1 },
56
- { op: 'c', 'o.applyOps': { $exists: true } },
57
- ]
58
- };
59
- // Data structures to support waitUntilCaughtUp(). Each oplog entry has a
60
- // MongoTimestamp object on it (which is not the same as a Date --- it's a
61
- // combination of time and an incrementing counter; see
62
- // http://docs.mongodb.org/manual/reference/bson-types/#timestamps).
63
- //
64
- // _catchingUpFutures is an array of {ts: MongoTimestamp, future: Future}
65
- // objects, sorted by ascending timestamp. _lastProcessedTS is the
66
- // MongoTimestamp of the last oplog entry we've processed.
67
- //
68
- // Each time we call waitUntilCaughtUp, we take a peek at the final oplog
69
- // entry in the db. If we've already processed it (ie, it is not greater than
70
- // _lastProcessedTS), waitUntilCaughtUp immediately returns. Otherwise,
71
- // waitUntilCaughtUp makes a new Future and inserts it along with the final
72
- // timestamp entry that it read, into _catchingUpFutures. waitUntilCaughtUp
73
- // then waits on that future, which is resolved once _lastProcessedTS is
74
- // incremented to be past its timestamp by the worker fiber.
75
- //
76
- // XXX use a priority queue or something else that's faster than an array
77
- self._catchingUpFutures = [];
78
- self._lastProcessedTS = null;
79
- self._onSkippedEntriesHook = new hook_1.Hook({
80
- debugPrintExceptions: "onSkippedEntries callback"
81
- });
82
- self._entryQueue = new double_ended_queue_1.default();
83
- self._workerActive = false;
84
- self._startTailing();
85
- }
86
- ;
87
- stop() {
88
- var self = this;
89
- if (self._stopped)
90
- return;
91
- self._stopped = true;
92
- if (self._tailHandle)
93
- self._tailHandle.stop();
94
- // XXX should close connections too
95
- }
96
- async onOplogEntry(trigger, callback) {
97
- var self = this;
98
- if (self._stopped)
99
- throw new Error("Called onOplogEntry on stopped handle!");
100
- // Calling onOplogEntry requires us to wait for the tailing to be ready.
101
- await self._readyFuture.promise;
102
- var originalCallback = callback;
103
- callback = function (notification) {
104
- try {
105
- originalCallback(notification);
106
- }
107
- catch (err) {
108
- console.error("Error in oplog callback", err);
109
- }
110
- };
111
- var listenHandle = self._crossbar.listen(trigger, callback);
112
- return {
113
- stop: function () {
114
- listenHandle.stop();
115
- }
116
- };
117
- }
118
- // Register a callback to be invoked any time we skip oplog entries (eg,
119
- // because we are too far behind).
120
- onSkippedEntries(callback) {
121
- var self = this;
122
- if (self._stopped)
123
- throw new Error("Called onSkippedEntries on stopped handle!");
124
- return self._onSkippedEntriesHook.register(callback);
125
- }
126
- // Calls `callback` once the oplog has been processed up to a point that is
127
- // roughly "now": specifically, once we've processed all ops that are
128
- // currently visible.
129
- // XXX become convinced that this is actually safe even if oplogConnection
130
- // is some kind of pool
131
- async waitUntilCaughtUp() {
132
- var self = this;
133
- if (self._stopped)
134
- throw new Error("Called waitUntilCaughtUp on stopped handle!");
135
- // Calling waitUntilCaughtUp requries us to wait for the oplog connection to
136
- // be ready.
137
- await self._readyFuture.promise;
138
- var lastEntry;
139
- while (!self._stopped) {
140
- // We need to make the selector at least as restrictive as the actual
141
- // tailing selector (ie, we need to specify the DB name) or else we might
142
- // find a TS that won't show up in the actual tail stream.
143
- try {
144
- lastEntry = await self._oplogLastEntryConnection.db.collection(exports.OPLOG_COLLECTION).findOne(self._baseOplogSelector, { projection: { ts: 1 }, sort: { $natural: -1 } });
145
- break;
146
- }
147
- catch (e) {
148
- // During failover (eg) if we get an exception we should log and retry
149
- // instead of crashing.
150
- console.warn("Got exception while reading last entry", e);
151
- await _sleepForMs(100);
152
- }
153
- }
154
- if (self._stopped)
155
- return;
156
- if (!lastEntry) {
157
- // Really, nothing in the oplog? Well, we've processed everything.
158
- return;
159
- }
160
- var ts = lastEntry.ts;
161
- if (!ts)
162
- throw Error("oplog entry without ts: " + (0, ejson_1.stringify)(lastEntry));
163
- if (self._lastProcessedTS && ts.lessThanOrEqual(self._lastProcessedTS)) {
164
- // We've already caught up to here.
165
- return;
166
- }
167
- // Insert the future into our list. Almost always, this will be at the end,
168
- // but it's conceivable that if we fail over from one primary to another,
169
- // the oplog entries we see will go backwards.
170
- var insertAfter = self._catchingUpFutures.length;
171
- while (insertAfter - 1 > 0 && self._catchingUpFutures[insertAfter - 1].ts.greaterThan(ts)) {
172
- insertAfter--;
173
- }
174
- var f = { promise: null, resolve: null };
175
- f.promise = new Promise((r) => f.resolve = r);
176
- self._catchingUpFutures.splice(insertAfter, 0, { ts: ts, future: f });
177
- await f.promise;
178
- }
179
- async _startTailing() {
180
- var self = this;
181
- // First, make sure that we're talking to the local database.
182
- if (self._oplogUrl.indexOf("/local?") === -1 && !self._oplogUrl.endsWith("/local")) {
183
- throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " +
184
- "a Mongo replica set");
185
- }
186
- // We make two separate connections to Mongo. The Node Mongo driver
187
- // implements a naive round-robin connection pool: each "connection" is a
188
- // pool of several (5 by default) TCP connections, and each request is
189
- // rotated through the pools. Tailable cursor queries block on the server
190
- // until there is some data to return (or until a few seconds have
191
- // passed). So if the connection pool used for tailing cursors is the same
192
- // pool used for other queries, the other queries will be delayed by seconds
193
- // 1/5 of the time.
194
- //
195
- // The tail connection will only ever be running a single tail command, so
196
- // it only needs to make one underlying TCP connection.
197
- self._oplogTailConnection = new live_connection_1.LiveMongoConnection(self._oplogUrl, { maxPoolSize: 1 });
198
- // XXX better docs, but: it's to get monotonic results
199
- // XXX is it safe to say "if there's an in flight query, just use its
200
- // results"? I don't think so but should consider that
201
- self._oplogLastEntryConnection = new live_connection_1.LiveMongoConnection(self._oplogUrl, { maxPoolSize: 1 });
202
- // Now, make sure that there actually is a repl set here. If not, oplog
203
- // tailing won't ever find anything!
204
- // More on the isMasterDoc
205
- // https://docs.mongodb.com/manual/reference/command/isMaster/
206
- const isMasterDoc = await self._oplogLastEntryConnection.db.admin().command({ ismaster: 1 });
207
- if (!(isMasterDoc && isMasterDoc.setName)) {
208
- throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " +
209
- "a Mongo replica set");
210
- }
211
- // Find the last oplog entry.
212
- var lastOplogEntry = await self._oplogLastEntryConnection.db.collection(exports.OPLOG_COLLECTION).findOne({}, { sort: { $natural: -1 }, projection: { ts: 1 } });
213
- var oplogSelector = Object.assign({}, self._baseOplogSelector);
214
- if (lastOplogEntry) {
215
- // Start after the last entry that currently exists.
216
- oplogSelector.ts = { $gt: lastOplogEntry.ts };
217
- // If there are any calls to callWhenProcessedLatest before any other
218
- // oplog entries show up, allow callWhenProcessedLatest to call its
219
- // callback immediately.
220
- self._lastProcessedTS = lastOplogEntry.ts;
221
- }
222
- var cursorDescription = new live_cursor_1.CursorDescription(exports.OPLOG_COLLECTION, oplogSelector, { tailable: true });
223
- // Start tailing the oplog.
224
- //
225
- // We restart the low-level oplog query every 30 seconds if we didn't get a
226
- // doc. This is a workaround for #8598: the Node Mongo driver has at least
227
- // one bug that can lead to query callbacks never getting called (even with
228
- // an error) when leadership failover occur.
229
- self._tailHandle = self._oplogTailConnection.tail(cursorDescription, function (doc) {
230
- self._entryQueue.push(doc);
231
- self._maybeStartWorker();
232
- }, TAIL_TIMEOUT);
233
- self._readyFuture.resolve();
234
- }
235
- _maybeStartWorker() {
236
- var self = this;
237
- if (self._workerActive)
238
- return;
239
- self._workerActive = true;
240
- setImmediate(function () {
241
- // May be called recursively in case of transactions.
242
- function handleDoc(doc) {
243
- if (doc.ns === "admin.$cmd") {
244
- if (doc.o.applyOps) {
245
- // This was a successful transaction, so we need to apply the
246
- // operations that were involved.
247
- let nextTimestamp = doc.ts;
248
- doc.o.applyOps.forEach(op => {
249
- // See https://github.com/meteor/meteor/issues/10420.
250
- if (!op.ts) {
251
- op.ts = nextTimestamp;
252
- nextTimestamp = nextTimestamp.add(mongodb_1.Long.ONE);
253
- }
254
- handleDoc(op);
255
- });
256
- return;
257
- }
258
- throw new Error("Unknown command " + (0, ejson_1.stringify)(doc));
259
- }
260
- const trigger = {
261
- dropCollection: false,
262
- dropDatabase: false,
263
- op: doc,
264
- collection: undefined,
265
- id: undefined
266
- };
267
- if (typeof doc.ns === "string" &&
268
- doc.ns.startsWith(self._dbName + ".")) {
269
- trigger.collection = doc.ns.slice(self._dbName.length + 1);
270
- }
271
- // Is it a special command and the collection name is hidden
272
- // somewhere in operator?
273
- if (trigger.collection === "$cmd") {
274
- if (doc.o.dropDatabase) {
275
- delete trigger.collection;
276
- trigger.dropDatabase = true;
277
- }
278
- else if (doc.o.hasOwnProperty("drop")) {
279
- trigger.collection = doc.o.drop;
280
- trigger.dropCollection = true;
281
- trigger.id = null;
282
- }
283
- else {
284
- throw Error("Unknown command " + (0, ejson_1.stringify)(doc));
285
- }
286
- }
287
- else {
288
- // All other ops have an id.
289
- trigger.id = idForOp(doc);
290
- }
291
- self._crossbar.fire(trigger);
292
- }
293
- try {
294
- while (!self._stopped &&
295
- !self._entryQueue.isEmpty()) {
296
- // Are we too far behind? Just tell our observers that they need to
297
- // repoll, and drop our queue.
298
- if (self._entryQueue.length > TOO_FAR_BEHIND) {
299
- var lastEntry = self._entryQueue.pop();
300
- self._entryQueue.clear();
301
- self._onSkippedEntriesHook.each(function (callback) {
302
- callback();
303
- return true;
304
- });
305
- // Free any waitUntilCaughtUp() calls that were waiting for us to
306
- // pass something that we just skipped.
307
- self._setLastProcessedTS(lastEntry.ts);
308
- continue;
309
- }
310
- const doc = self._entryQueue.shift();
311
- // Fire trigger(s) for this doc.
312
- handleDoc(doc);
313
- // Now that we've processed this operation, process pending
314
- // sequencers.
315
- if (doc.ts) {
316
- self._setLastProcessedTS(doc.ts);
317
- }
318
- else {
319
- throw Error("oplog entry without ts: " + (0, ejson_1.stringify)(doc));
320
- }
321
- }
322
- }
323
- finally {
324
- self._workerActive = false;
325
- }
326
- });
327
- }
328
- _setLastProcessedTS(ts) {
329
- var self = this;
330
- self._lastProcessedTS = ts;
331
- while (self._catchingUpFutures.length > 0 && self._catchingUpFutures[0].ts.lessThanOrEqual(self._lastProcessedTS)) {
332
- var sequencer = self._catchingUpFutures.shift();
333
- sequencer.future.resolve();
334
- }
335
- }
336
- //Methods used on tests to dinamically change TOO_FAR_BEHIND
337
- _defineTooFarBehind(value) {
338
- TOO_FAR_BEHIND = value;
339
- }
340
- _resetTooFarBehind() {
341
- TOO_FAR_BEHIND = +process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000;
342
- }
343
- }
344
- exports.OplogHandle = OplogHandle;
345
- async function _sleepForMs(ms) {
346
- return new Promise((resolve) => setTimeout(resolve, ms));
347
- }
348
- exports._sleepForMs = _sleepForMs;
349
- function _escapeRegExp(s) {
350
- return s.replace(/[\.*+?^$\\|(){}[\]]/g, '\\$&');
351
- }
352
- exports._escapeRegExp = _escapeRegExp;
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OplogHandle = exports.OPLOG_COLLECTION = void 0;
7
+ exports.idForOp = idForOp;
8
+ exports._sleepForMs = _sleepForMs;
9
+ exports._escapeRegExp = _escapeRegExp;
10
+ const mongodb_1 = require("mongodb");
11
+ const hook_1 = require("../callback-hook/hook");
12
+ const crossbar_1 = require("../ddp/crossbar");
13
+ const double_ended_queue_1 = __importDefault(require("double-ended-queue"));
14
+ const ejson_1 = require("../ejson/ejson");
15
+ const live_connection_1 = require("./live_connection");
16
+ const live_cursor_1 = require("./live_cursor");
17
+ exports.OPLOG_COLLECTION = 'oplog.rs';
18
+ var TOO_FAR_BEHIND = +process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000;
19
+ var TAIL_TIMEOUT = +process.env.METEOR_OPLOG_TAIL_TIMEOUT || 30000;
20
+ var showTS = function (ts) {
21
+ return "Timestamp(" + ts.getHighBits() + ", " + ts.getLowBits() + ")";
22
+ };
23
+ function idForOp(op) {
24
+ if (op.op === 'd')
25
+ return op.o._id;
26
+ else if (op.op === 'i')
27
+ return op.o._id;
28
+ else if (op.op === 'u')
29
+ return op.o2._id;
30
+ else if (op.op === 'c')
31
+ throw Error("Operator 'c' doesn't supply an object with id: " +
32
+ (0, ejson_1.stringify)(op));
33
+ else
34
+ throw Error("Unknown op: " + (0, ejson_1.stringify)(op));
35
+ }
36
+ ;
37
+ class OplogHandle {
38
+ constructor(oplogUrl, dbName) {
39
+ var self = this;
40
+ self._readyFuture = { promise: undefined, resolve: undefined };
41
+ self._readyFuture.promise = new Promise(r => self._readyFuture.resolve = r);
42
+ self._oplogUrl = oplogUrl;
43
+ self._dbName = dbName;
44
+ self._oplogLastEntryConnection = null;
45
+ self._oplogTailConnection = null;
46
+ self._stopped = false;
47
+ self._tailHandle = null;
48
+ self._crossbar = new crossbar_1._Crossbar({
49
+ factPackage: "mongo-livedata", factName: "oplog-watchers"
50
+ });
51
+ self._baseOplogSelector = {
52
+ ns: new RegExp("^(?:" + _escapeRegExp(self._dbName) + "\\.|admin\\.$cmd)"),
53
+ $or: [
54
+ { op: { $in: ['i', 'u', 'd'] } },
55
+ // drop collection
56
+ { op: 'c', 'o.drop': { $exists: true } },
57
+ { op: 'c', 'o.dropDatabase': 1 },
58
+ { op: 'c', 'o.applyOps': { $exists: true } },
59
+ ]
60
+ };
61
+ // Data structures to support waitUntilCaughtUp(). Each oplog entry has a
62
+ // MongoTimestamp object on it (which is not the same as a Date --- it's a
63
+ // combination of time and an incrementing counter; see
64
+ // http://docs.mongodb.org/manual/reference/bson-types/#timestamps).
65
+ //
66
+ // _catchingUpFutures is an array of {ts: MongoTimestamp, future: Future}
67
+ // objects, sorted by ascending timestamp. _lastProcessedTS is the
68
+ // MongoTimestamp of the last oplog entry we've processed.
69
+ //
70
+ // Each time we call waitUntilCaughtUp, we take a peek at the final oplog
71
+ // entry in the db. If we've already processed it (ie, it is not greater than
72
+ // _lastProcessedTS), waitUntilCaughtUp immediately returns. Otherwise,
73
+ // waitUntilCaughtUp makes a new Future and inserts it along with the final
74
+ // timestamp entry that it read, into _catchingUpFutures. waitUntilCaughtUp
75
+ // then waits on that future, which is resolved once _lastProcessedTS is
76
+ // incremented to be past its timestamp by the worker fiber.
77
+ //
78
+ // XXX use a priority queue or something else that's faster than an array
79
+ self._catchingUpFutures = [];
80
+ self._lastProcessedTS = null;
81
+ self._onSkippedEntriesHook = new hook_1.Hook({
82
+ debugPrintExceptions: "onSkippedEntries callback"
83
+ });
84
+ self._entryQueue = new double_ended_queue_1.default();
85
+ self._workerActive = false;
86
+ self._startTailing();
87
+ }
88
+ ;
89
+ stop() {
90
+ var self = this;
91
+ if (self._stopped)
92
+ return;
93
+ self._stopped = true;
94
+ if (self._tailHandle)
95
+ self._tailHandle.stop();
96
+ // XXX should close connections too
97
+ }
98
+ async onOplogEntry(trigger, callback) {
99
+ var self = this;
100
+ if (self._stopped)
101
+ throw new Error("Called onOplogEntry on stopped handle!");
102
+ // Calling onOplogEntry requires us to wait for the tailing to be ready.
103
+ await self._readyFuture.promise;
104
+ var originalCallback = callback;
105
+ callback = function (notification) {
106
+ try {
107
+ originalCallback(notification);
108
+ }
109
+ catch (err) {
110
+ console.error("Error in oplog callback", err);
111
+ }
112
+ };
113
+ var listenHandle = self._crossbar.listen(trigger, callback);
114
+ return {
115
+ stop: function () {
116
+ listenHandle.stop();
117
+ }
118
+ };
119
+ }
120
+ // Register a callback to be invoked any time we skip oplog entries (eg,
121
+ // because we are too far behind).
122
+ onSkippedEntries(callback) {
123
+ var self = this;
124
+ if (self._stopped)
125
+ throw new Error("Called onSkippedEntries on stopped handle!");
126
+ return self._onSkippedEntriesHook.register(callback);
127
+ }
128
+ // Calls `callback` once the oplog has been processed up to a point that is
129
+ // roughly "now": specifically, once we've processed all ops that are
130
+ // currently visible.
131
+ // XXX become convinced that this is actually safe even if oplogConnection
132
+ // is some kind of pool
133
+ async waitUntilCaughtUp() {
134
+ var self = this;
135
+ if (self._stopped)
136
+ throw new Error("Called waitUntilCaughtUp on stopped handle!");
137
+ // Calling waitUntilCaughtUp requries us to wait for the oplog connection to
138
+ // be ready.
139
+ await self._readyFuture.promise;
140
+ var lastEntry;
141
+ while (!self._stopped) {
142
+ // We need to make the selector at least as restrictive as the actual
143
+ // tailing selector (ie, we need to specify the DB name) or else we might
144
+ // find a TS that won't show up in the actual tail stream.
145
+ try {
146
+ lastEntry = await self._oplogLastEntryConnection.db.collection(exports.OPLOG_COLLECTION).findOne(self._baseOplogSelector, { projection: { ts: 1 }, sort: { $natural: -1 } });
147
+ break;
148
+ }
149
+ catch (e) {
150
+ // During failover (eg) if we get an exception we should log and retry
151
+ // instead of crashing.
152
+ console.warn("Got exception while reading last entry", e);
153
+ await _sleepForMs(100);
154
+ }
155
+ }
156
+ if (self._stopped)
157
+ return;
158
+ if (!lastEntry) {
159
+ // Really, nothing in the oplog? Well, we've processed everything.
160
+ return;
161
+ }
162
+ var ts = lastEntry.ts;
163
+ if (!ts)
164
+ throw Error("oplog entry without ts: " + (0, ejson_1.stringify)(lastEntry));
165
+ if (self._lastProcessedTS && ts.lessThanOrEqual(self._lastProcessedTS)) {
166
+ // We've already caught up to here.
167
+ return;
168
+ }
169
+ // Insert the future into our list. Almost always, this will be at the end,
170
+ // but it's conceivable that if we fail over from one primary to another,
171
+ // the oplog entries we see will go backwards.
172
+ var insertAfter = self._catchingUpFutures.length;
173
+ while (insertAfter - 1 > 0 && self._catchingUpFutures[insertAfter - 1].ts.greaterThan(ts)) {
174
+ insertAfter--;
175
+ }
176
+ var f = { promise: null, resolve: null };
177
+ f.promise = new Promise((r) => f.resolve = r);
178
+ self._catchingUpFutures.splice(insertAfter, 0, { ts: ts, future: f });
179
+ await f.promise;
180
+ }
181
+ async _startTailing() {
182
+ var self = this;
183
+ // First, make sure that we're talking to the local database.
184
+ if (self._oplogUrl.indexOf("/local?") === -1 && !self._oplogUrl.endsWith("/local")) {
185
+ throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " +
186
+ "a Mongo replica set");
187
+ }
188
+ // We make two separate connections to Mongo. The Node Mongo driver
189
+ // implements a naive round-robin connection pool: each "connection" is a
190
+ // pool of several (5 by default) TCP connections, and each request is
191
+ // rotated through the pools. Tailable cursor queries block on the server
192
+ // until there is some data to return (or until a few seconds have
193
+ // passed). So if the connection pool used for tailing cursors is the same
194
+ // pool used for other queries, the other queries will be delayed by seconds
195
+ // 1/5 of the time.
196
+ //
197
+ // The tail connection will only ever be running a single tail command, so
198
+ // it only needs to make one underlying TCP connection.
199
+ self._oplogTailConnection = new live_connection_1.LiveMongoConnection(self._oplogUrl, { maxPoolSize: 1 });
200
+ // XXX better docs, but: it's to get monotonic results
201
+ // XXX is it safe to say "if there's an in flight query, just use its
202
+ // results"? I don't think so but should consider that
203
+ self._oplogLastEntryConnection = new live_connection_1.LiveMongoConnection(self._oplogUrl, { maxPoolSize: 1 });
204
+ // Now, make sure that there actually is a repl set here. If not, oplog
205
+ // tailing won't ever find anything!
206
+ // More on the isMasterDoc
207
+ // https://docs.mongodb.com/manual/reference/command/isMaster/
208
+ const isMasterDoc = await self._oplogLastEntryConnection.db.admin().command({ ismaster: 1 });
209
+ if (!(isMasterDoc && isMasterDoc.setName)) {
210
+ throw Error("$MONGO_OPLOG_URL must be set to the 'local' database of " +
211
+ "a Mongo replica set");
212
+ }
213
+ // Find the last oplog entry.
214
+ var lastOplogEntry = await self._oplogLastEntryConnection.db.collection(exports.OPLOG_COLLECTION).findOne({}, { sort: { $natural: -1 }, projection: { ts: 1 } });
215
+ var oplogSelector = Object.assign({}, self._baseOplogSelector);
216
+ if (lastOplogEntry) {
217
+ // Start after the last entry that currently exists.
218
+ oplogSelector.ts = { $gt: lastOplogEntry.ts };
219
+ // If there are any calls to callWhenProcessedLatest before any other
220
+ // oplog entries show up, allow callWhenProcessedLatest to call its
221
+ // callback immediately.
222
+ self._lastProcessedTS = lastOplogEntry.ts;
223
+ }
224
+ var cursorDescription = new live_cursor_1.CursorDescription(exports.OPLOG_COLLECTION, oplogSelector, { tailable: true });
225
+ // Start tailing the oplog.
226
+ //
227
+ // We restart the low-level oplog query every 30 seconds if we didn't get a
228
+ // doc. This is a workaround for #8598: the Node Mongo driver has at least
229
+ // one bug that can lead to query callbacks never getting called (even with
230
+ // an error) when leadership failover occur.
231
+ self._tailHandle = self._oplogTailConnection.tail(cursorDescription, function (doc) {
232
+ self._entryQueue.push(doc);
233
+ self._maybeStartWorker();
234
+ }, TAIL_TIMEOUT);
235
+ self._readyFuture.resolve();
236
+ }
237
+ _maybeStartWorker() {
238
+ var self = this;
239
+ if (self._workerActive)
240
+ return;
241
+ self._workerActive = true;
242
+ setImmediate(function () {
243
+ // May be called recursively in case of transactions.
244
+ function handleDoc(doc) {
245
+ if (doc.ns === "admin.$cmd") {
246
+ if (doc.o.applyOps) {
247
+ // This was a successful transaction, so we need to apply the
248
+ // operations that were involved.
249
+ let nextTimestamp = doc.ts;
250
+ doc.o.applyOps.forEach(op => {
251
+ // See https://github.com/meteor/meteor/issues/10420.
252
+ if (!op.ts) {
253
+ op.ts = nextTimestamp;
254
+ nextTimestamp = nextTimestamp.add(mongodb_1.Long.ONE);
255
+ }
256
+ handleDoc(op);
257
+ });
258
+ return;
259
+ }
260
+ throw new Error("Unknown command " + (0, ejson_1.stringify)(doc));
261
+ }
262
+ const trigger = {
263
+ dropCollection: false,
264
+ dropDatabase: false,
265
+ op: doc,
266
+ collection: undefined,
267
+ id: undefined
268
+ };
269
+ if (typeof doc.ns === "string" &&
270
+ doc.ns.startsWith(self._dbName + ".")) {
271
+ trigger.collection = doc.ns.slice(self._dbName.length + 1);
272
+ }
273
+ // Is it a special command and the collection name is hidden
274
+ // somewhere in operator?
275
+ if (trigger.collection === "$cmd") {
276
+ if (doc.o.dropDatabase) {
277
+ delete trigger.collection;
278
+ trigger.dropDatabase = true;
279
+ }
280
+ else if (doc.o.hasOwnProperty("drop")) {
281
+ trigger.collection = doc.o.drop;
282
+ trigger.dropCollection = true;
283
+ trigger.id = null;
284
+ }
285
+ else {
286
+ throw Error("Unknown command " + (0, ejson_1.stringify)(doc));
287
+ }
288
+ }
289
+ else {
290
+ // All other ops have an id.
291
+ trigger.id = idForOp(doc);
292
+ }
293
+ self._crossbar.fire(trigger);
294
+ }
295
+ try {
296
+ while (!self._stopped &&
297
+ !self._entryQueue.isEmpty()) {
298
+ // Are we too far behind? Just tell our observers that they need to
299
+ // repoll, and drop our queue.
300
+ if (self._entryQueue.length > TOO_FAR_BEHIND) {
301
+ var lastEntry = self._entryQueue.pop();
302
+ self._entryQueue.clear();
303
+ self._onSkippedEntriesHook.each(function (callback) {
304
+ callback();
305
+ return true;
306
+ });
307
+ // Free any waitUntilCaughtUp() calls that were waiting for us to
308
+ // pass something that we just skipped.
309
+ self._setLastProcessedTS(lastEntry.ts);
310
+ continue;
311
+ }
312
+ const doc = self._entryQueue.shift();
313
+ // Fire trigger(s) for this doc.
314
+ handleDoc(doc);
315
+ // Now that we've processed this operation, process pending
316
+ // sequencers.
317
+ if (doc.ts) {
318
+ self._setLastProcessedTS(doc.ts);
319
+ }
320
+ else {
321
+ throw Error("oplog entry without ts: " + (0, ejson_1.stringify)(doc));
322
+ }
323
+ }
324
+ }
325
+ finally {
326
+ self._workerActive = false;
327
+ }
328
+ });
329
+ }
330
+ _setLastProcessedTS(ts) {
331
+ var self = this;
332
+ self._lastProcessedTS = ts;
333
+ while (self._catchingUpFutures.length > 0 && self._catchingUpFutures[0].ts.lessThanOrEqual(self._lastProcessedTS)) {
334
+ var sequencer = self._catchingUpFutures.shift();
335
+ sequencer.future.resolve();
336
+ }
337
+ }
338
+ //Methods used on tests to dinamically change TOO_FAR_BEHIND
339
+ _defineTooFarBehind(value) {
340
+ TOO_FAR_BEHIND = value;
341
+ }
342
+ _resetTooFarBehind() {
343
+ TOO_FAR_BEHIND = +process.env.METEOR_OPLOG_TOO_FAR_BEHIND || 2000;
344
+ }
345
+ }
346
+ exports.OplogHandle = OplogHandle;
347
+ async function _sleepForMs(ms) {
348
+ return new Promise((resolve) => setTimeout(resolve, ms));
349
+ }
350
+ function _escapeRegExp(s) {
351
+ return s.replace(/[\.*+?^$\\|(){}[\]]/g, '\\$&');
352
+ }