@hocuspocus/extension-redis 3.2.0 → 3.2.2
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.
- package/dist/hocuspocus-redis.cjs +37 -32
- package/dist/hocuspocus-redis.cjs.map +1 -1
- package/dist/hocuspocus-redis.esm.js +36 -31
- package/dist/hocuspocus-redis.esm.js.map +1 -1
- package/dist/packages/extension-redis/src/Redis.d.ts +12 -8
- package/dist/packages/provider/src/HocuspocusProviderWebsocket.d.ts +1 -2
- package/package.json +4 -5
- package/src/Redis.ts +42 -54
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var server = require('@hocuspocus/server');
|
|
4
|
+
var redlock = require('@sesamecare-oss/redlock');
|
|
4
5
|
var RedisClient = require('ioredis');
|
|
5
|
-
var Redlock = require('redlock');
|
|
6
6
|
var uuid = require('uuid');
|
|
7
7
|
|
|
8
8
|
class Redis {
|
|
@@ -47,7 +47,7 @@ class Redis {
|
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
new server.MessageReceiver(message, this.redisTransactionOrigin).apply(document, undefined, (reply) => {
|
|
50
|
-
return this.pub.
|
|
50
|
+
return this.pub.publish(this.pubKey(document.name), this.encodeMessage(reply));
|
|
51
51
|
});
|
|
52
52
|
};
|
|
53
53
|
/**
|
|
@@ -64,7 +64,7 @@ class Redis {
|
|
|
64
64
|
const document = this.instance.documents.get(documentName);
|
|
65
65
|
this.pendingDisconnects.delete(documentName);
|
|
66
66
|
// Do nothing, when other users are still connected to the document.
|
|
67
|
-
if (
|
|
67
|
+
if (document && document.getConnectionsCount() > 0) {
|
|
68
68
|
return;
|
|
69
69
|
}
|
|
70
70
|
// Time to end the subscription on the document channel.
|
|
@@ -73,7 +73,9 @@ class Redis {
|
|
|
73
73
|
console.error(error);
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
|
-
|
|
76
|
+
if (document) {
|
|
77
|
+
this.instance.unloadDocument(document);
|
|
78
|
+
}
|
|
77
79
|
};
|
|
78
80
|
// Delay the disconnect procedure to allow last minute syncs to happen
|
|
79
81
|
const timeout = setTimeout(disconnect, this.configuration.disconnectDelay);
|
|
@@ -102,8 +104,8 @@ class Redis {
|
|
|
102
104
|
this.sub = new RedisClient(port, host, options !== null && options !== void 0 ? options : {});
|
|
103
105
|
}
|
|
104
106
|
this.sub.on("messageBuffer", this.handleIncomingMessage);
|
|
105
|
-
this.redlock = new Redlock([this.pub], {
|
|
106
|
-
|
|
107
|
+
this.redlock = new redlock.Redlock([this.pub], {
|
|
108
|
+
driftFactor: 0.1
|
|
107
109
|
});
|
|
108
110
|
const identifierBuffer = Buffer.from(this.configuration.identifier, "utf-8");
|
|
109
111
|
this.messagePrefix = Buffer.concat([
|
|
@@ -159,14 +161,14 @@ class Redis {
|
|
|
159
161
|
const syncMessage = new server.OutgoingMessage(documentName)
|
|
160
162
|
.createSyncMessage()
|
|
161
163
|
.writeFirstSyncStepFor(document);
|
|
162
|
-
return this.pub.
|
|
164
|
+
return this.pub.publish(this.pubKey(documentName), this.encodeMessage(syncMessage.toUint8Array()));
|
|
163
165
|
}
|
|
164
166
|
/**
|
|
165
167
|
* Let’s ask Redis who is connected already.
|
|
166
168
|
*/
|
|
167
169
|
async requestAwarenessFromOtherInstances(documentName) {
|
|
168
170
|
const awarenessMessage = new server.OutgoingMessage(documentName).writeQueryAwareness();
|
|
169
|
-
return this.pub.
|
|
171
|
+
return this.pub.publish(this.pubKey(documentName), this.encodeMessage(awarenessMessage.toUint8Array()));
|
|
170
172
|
}
|
|
171
173
|
/**
|
|
172
174
|
* Before the document is stored, make sure to set a lock in Redis.
|
|
@@ -175,32 +177,35 @@ class Redis {
|
|
|
175
177
|
async onStoreDocument({ documentName }) {
|
|
176
178
|
// Attempt to acquire a lock and read lastReceivedTimestamp from Redis,
|
|
177
179
|
// to avoid conflict with other instances storing the same document.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
this.locks.set(this.lockKey(documentName), lock);
|
|
188
|
-
resolve(undefined);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
180
|
+
const resource = this.lockKey(documentName);
|
|
181
|
+
const ttl = this.configuration.lockTimeout;
|
|
182
|
+
const lock = await this.redlock.acquire([resource], ttl);
|
|
183
|
+
const oldLock = this.locks.get(resource);
|
|
184
|
+
if (oldLock) {
|
|
185
|
+
await oldLock.release;
|
|
186
|
+
}
|
|
187
|
+
this.locks.set(resource, { lock });
|
|
191
188
|
}
|
|
192
189
|
/**
|
|
193
190
|
* Release the Redis lock, so other instances can store documents.
|
|
194
191
|
*/
|
|
195
|
-
async afterStoreDocument({ documentName, socketId
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
192
|
+
async afterStoreDocument({ documentName, socketId }) {
|
|
193
|
+
const lockKey = this.lockKey(documentName);
|
|
194
|
+
const lock = this.locks.get(lockKey);
|
|
195
|
+
if (!lock) {
|
|
196
|
+
throw new Error(`Lock created in onStoreDocument not found in afterStoreDocument: ${lockKey}`);
|
|
197
|
+
}
|
|
198
|
+
try {
|
|
199
|
+
// Always try to unlock and clean up the lock
|
|
200
|
+
lock.release = lock.lock.release();
|
|
201
|
+
await lock.release;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Lock will expire on its own after timeout
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
this.locks.delete(lockKey);
|
|
208
|
+
}
|
|
204
209
|
// if the change was initiated by a directConnection, we need to delay this hook to make sure sync can finish first.
|
|
205
210
|
// for provider connections, this usually happens in the onDisconnect hook
|
|
206
211
|
if (socketId === "server") {
|
|
@@ -231,7 +236,7 @@ class Redis {
|
|
|
231
236
|
async onAwarenessUpdate({ documentName, awareness, added, updated, removed, }) {
|
|
232
237
|
const changedClients = added.concat(updated, removed);
|
|
233
238
|
const message = new server.OutgoingMessage(documentName).createAwarenessUpdateMessage(awareness, changedClients);
|
|
234
|
-
return this.pub.
|
|
239
|
+
return this.pub.publish(this.pubKey(documentName), this.encodeMessage(message.toUint8Array()));
|
|
235
240
|
}
|
|
236
241
|
/**
|
|
237
242
|
* if the ydoc changed, we'll need to inform other Hocuspocus servers about it.
|
|
@@ -243,7 +248,7 @@ class Redis {
|
|
|
243
248
|
}
|
|
244
249
|
async beforeBroadcastStateless(data) {
|
|
245
250
|
const message = new server.OutgoingMessage(data.documentName).writeBroadcastStateless(data.payload);
|
|
246
|
-
return this.pub.
|
|
251
|
+
return this.pub.publish(this.pubKey(data.documentName), this.encodeMessage(message.toUint8Array()));
|
|
247
252
|
}
|
|
248
253
|
/**
|
|
249
254
|
* Kill the Redlock connection immediately.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hocuspocus-redis.cjs","sources":["../src/Redis.ts"],"sourcesContent":[null],"names":["uuid","IncomingMessage","MessageReceiver","OutgoingMessage"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"hocuspocus-redis.cjs","sources":["../src/Redis.ts"],"sourcesContent":[null],"names":["uuid","IncomingMessage","MessageReceiver","Redlock","OutgoingMessage"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IncomingMessage, MessageReceiver, OutgoingMessage } from '@hocuspocus/server';
|
|
2
|
+
import { Redlock } from '@sesamecare-oss/redlock';
|
|
2
3
|
import RedisClient from 'ioredis';
|
|
3
|
-
import Redlock from 'redlock';
|
|
4
4
|
import { v4 } from 'uuid';
|
|
5
5
|
|
|
6
6
|
class Redis {
|
|
@@ -45,7 +45,7 @@ class Redis {
|
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
new MessageReceiver(message, this.redisTransactionOrigin).apply(document, undefined, (reply) => {
|
|
48
|
-
return this.pub.
|
|
48
|
+
return this.pub.publish(this.pubKey(document.name), this.encodeMessage(reply));
|
|
49
49
|
});
|
|
50
50
|
};
|
|
51
51
|
/**
|
|
@@ -62,7 +62,7 @@ class Redis {
|
|
|
62
62
|
const document = this.instance.documents.get(documentName);
|
|
63
63
|
this.pendingDisconnects.delete(documentName);
|
|
64
64
|
// Do nothing, when other users are still connected to the document.
|
|
65
|
-
if (
|
|
65
|
+
if (document && document.getConnectionsCount() > 0) {
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
// Time to end the subscription on the document channel.
|
|
@@ -71,7 +71,9 @@ class Redis {
|
|
|
71
71
|
console.error(error);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
|
-
|
|
74
|
+
if (document) {
|
|
75
|
+
this.instance.unloadDocument(document);
|
|
76
|
+
}
|
|
75
77
|
};
|
|
76
78
|
// Delay the disconnect procedure to allow last minute syncs to happen
|
|
77
79
|
const timeout = setTimeout(disconnect, this.configuration.disconnectDelay);
|
|
@@ -101,7 +103,7 @@ class Redis {
|
|
|
101
103
|
}
|
|
102
104
|
this.sub.on("messageBuffer", this.handleIncomingMessage);
|
|
103
105
|
this.redlock = new Redlock([this.pub], {
|
|
104
|
-
|
|
106
|
+
driftFactor: 0.1
|
|
105
107
|
});
|
|
106
108
|
const identifierBuffer = Buffer.from(this.configuration.identifier, "utf-8");
|
|
107
109
|
this.messagePrefix = Buffer.concat([
|
|
@@ -157,14 +159,14 @@ class Redis {
|
|
|
157
159
|
const syncMessage = new OutgoingMessage(documentName)
|
|
158
160
|
.createSyncMessage()
|
|
159
161
|
.writeFirstSyncStepFor(document);
|
|
160
|
-
return this.pub.
|
|
162
|
+
return this.pub.publish(this.pubKey(documentName), this.encodeMessage(syncMessage.toUint8Array()));
|
|
161
163
|
}
|
|
162
164
|
/**
|
|
163
165
|
* Let’s ask Redis who is connected already.
|
|
164
166
|
*/
|
|
165
167
|
async requestAwarenessFromOtherInstances(documentName) {
|
|
166
168
|
const awarenessMessage = new OutgoingMessage(documentName).writeQueryAwareness();
|
|
167
|
-
return this.pub.
|
|
169
|
+
return this.pub.publish(this.pubKey(documentName), this.encodeMessage(awarenessMessage.toUint8Array()));
|
|
168
170
|
}
|
|
169
171
|
/**
|
|
170
172
|
* Before the document is stored, make sure to set a lock in Redis.
|
|
@@ -173,32 +175,35 @@ class Redis {
|
|
|
173
175
|
async onStoreDocument({ documentName }) {
|
|
174
176
|
// Attempt to acquire a lock and read lastReceivedTimestamp from Redis,
|
|
175
177
|
// to avoid conflict with other instances storing the same document.
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
this.locks.set(this.lockKey(documentName), lock);
|
|
186
|
-
resolve(undefined);
|
|
187
|
-
});
|
|
188
|
-
});
|
|
178
|
+
const resource = this.lockKey(documentName);
|
|
179
|
+
const ttl = this.configuration.lockTimeout;
|
|
180
|
+
const lock = await this.redlock.acquire([resource], ttl);
|
|
181
|
+
const oldLock = this.locks.get(resource);
|
|
182
|
+
if (oldLock) {
|
|
183
|
+
await oldLock.release;
|
|
184
|
+
}
|
|
185
|
+
this.locks.set(resource, { lock });
|
|
189
186
|
}
|
|
190
187
|
/**
|
|
191
188
|
* Release the Redis lock, so other instances can store documents.
|
|
192
189
|
*/
|
|
193
|
-
async afterStoreDocument({ documentName, socketId
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
190
|
+
async afterStoreDocument({ documentName, socketId }) {
|
|
191
|
+
const lockKey = this.lockKey(documentName);
|
|
192
|
+
const lock = this.locks.get(lockKey);
|
|
193
|
+
if (!lock) {
|
|
194
|
+
throw new Error(`Lock created in onStoreDocument not found in afterStoreDocument: ${lockKey}`);
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
// Always try to unlock and clean up the lock
|
|
198
|
+
lock.release = lock.lock.release();
|
|
199
|
+
await lock.release;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
// Lock will expire on its own after timeout
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
this.locks.delete(lockKey);
|
|
206
|
+
}
|
|
202
207
|
// if the change was initiated by a directConnection, we need to delay this hook to make sure sync can finish first.
|
|
203
208
|
// for provider connections, this usually happens in the onDisconnect hook
|
|
204
209
|
if (socketId === "server") {
|
|
@@ -229,7 +234,7 @@ class Redis {
|
|
|
229
234
|
async onAwarenessUpdate({ documentName, awareness, added, updated, removed, }) {
|
|
230
235
|
const changedClients = added.concat(updated, removed);
|
|
231
236
|
const message = new OutgoingMessage(documentName).createAwarenessUpdateMessage(awareness, changedClients);
|
|
232
|
-
return this.pub.
|
|
237
|
+
return this.pub.publish(this.pubKey(documentName), this.encodeMessage(message.toUint8Array()));
|
|
233
238
|
}
|
|
234
239
|
/**
|
|
235
240
|
* if the ydoc changed, we'll need to inform other Hocuspocus servers about it.
|
|
@@ -241,7 +246,7 @@ class Redis {
|
|
|
241
246
|
}
|
|
242
247
|
async beforeBroadcastStateless(data) {
|
|
243
248
|
const message = new OutgoingMessage(data.documentName).writeBroadcastStateless(data.payload);
|
|
244
|
-
return this.pub.
|
|
249
|
+
return this.pub.publish(this.pubKey(data.documentName), this.encodeMessage(message.toUint8Array()));
|
|
245
250
|
}
|
|
246
251
|
/**
|
|
247
252
|
* Kill the Redlock connection immediately.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hocuspocus-redis.esm.js","sources":["../src/Redis.ts"],"sourcesContent":[null],"names":["uuid"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"hocuspocus-redis.esm.js","sources":["../src/Redis.ts"],"sourcesContent":[null],"names":["uuid"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Extension, Hocuspocus, afterLoadDocumentPayload, afterStoreDocumentPayload, beforeBroadcastStatelessPayload, onAwarenessUpdatePayload, onChangePayload, onConfigurePayload, onDisconnectPayload, onStoreDocumentPayload } from "@hocuspocus/server";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
2
|
+
import { Redlock, type ExecutionResult, type Lock } from '@sesamecare-oss/redlock';
|
|
3
|
+
import type { Cluster, ClusterNode, ClusterOptions, RedisOptions } from "ioredis";
|
|
4
|
+
import RedisClient from "ioredis";
|
|
5
|
+
export type RedisInstance = RedisClient | Cluster;
|
|
5
6
|
export interface Configuration {
|
|
6
7
|
/**
|
|
7
8
|
* Redis port
|
|
@@ -61,7 +62,10 @@ export declare class Redis implements Extension {
|
|
|
61
62
|
sub: RedisInstance;
|
|
62
63
|
instance: Hocuspocus;
|
|
63
64
|
redlock: Redlock;
|
|
64
|
-
locks: Map<string,
|
|
65
|
+
locks: Map<string, {
|
|
66
|
+
lock: Lock;
|
|
67
|
+
release?: Promise<ExecutionResult>;
|
|
68
|
+
}>;
|
|
65
69
|
messagePrefix: Buffer;
|
|
66
70
|
/**
|
|
67
71
|
* When we have a high frequency of updates to a document we don't need tons of setTimeouts
|
|
@@ -93,15 +97,15 @@ export declare class Redis implements Extension {
|
|
|
93
97
|
* Before the document is stored, make sure to set a lock in Redis.
|
|
94
98
|
* That’s meant to avoid conflicts with other instances trying to store the document.
|
|
95
99
|
*/
|
|
96
|
-
onStoreDocument({ documentName }: onStoreDocumentPayload): Promise<
|
|
100
|
+
onStoreDocument({ documentName }: onStoreDocumentPayload): Promise<void>;
|
|
97
101
|
/**
|
|
98
102
|
* Release the Redis lock, so other instances can store documents.
|
|
99
103
|
*/
|
|
100
|
-
afterStoreDocument({ documentName, socketId
|
|
104
|
+
afterStoreDocument({ documentName, socketId }: afterStoreDocumentPayload): Promise<void>;
|
|
101
105
|
/**
|
|
102
106
|
* Handle awareness update messages received directly by this Hocuspocus instance.
|
|
103
107
|
*/
|
|
104
|
-
onAwarenessUpdate({ documentName, awareness, added, updated, removed, }: onAwarenessUpdatePayload): Promise<
|
|
108
|
+
onAwarenessUpdate({ documentName, awareness, added, updated, removed, }: onAwarenessUpdatePayload): Promise<number>;
|
|
105
109
|
/**
|
|
106
110
|
* Handle incoming messages published on subscribed document channels.
|
|
107
111
|
* Note that this will also include messages from ourselves as it is not possible
|
|
@@ -117,7 +121,7 @@ export declare class Redis implements Extension {
|
|
|
117
121
|
* no one connected anymore.
|
|
118
122
|
*/
|
|
119
123
|
onDisconnect: ({ documentName }: onDisconnectPayload) => Promise<void>;
|
|
120
|
-
beforeBroadcastStateless(data: beforeBroadcastStatelessPayload): Promise<
|
|
124
|
+
beforeBroadcastStateless(data: beforeBroadcastStatelessPayload): Promise<number>;
|
|
121
125
|
/**
|
|
122
126
|
* Kill the Redlock connection immediately.
|
|
123
127
|
*/
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { Event, MessageEvent } from "ws";
|
|
2
2
|
import EventEmitter from "./EventEmitter.ts";
|
|
3
3
|
import type { HocuspocusProvider } from "./HocuspocusProvider.ts";
|
|
4
|
-
import type
|
|
5
|
-
import { WebSocketStatus } from "./types.ts";
|
|
4
|
+
import { WebSocketStatus, type onAwarenessChangeParameters, type onAwarenessUpdateParameters, type onCloseParameters, type onDisconnectParameters, type onMessageParameters, type onOpenParameters, type onOutgoingMessageParameters, type onStatusParameters } from "./types.ts";
|
|
6
5
|
export type HocusPocusWebSocket = WebSocket & {
|
|
7
6
|
identifier: string;
|
|
8
7
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hocuspocus/extension-redis",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "Scale Hocuspocus horizontally with Redis",
|
|
5
5
|
"homepage": "https://hocuspocus.dev",
|
|
6
6
|
"keywords": [
|
|
@@ -28,15 +28,14 @@
|
|
|
28
28
|
"dist"
|
|
29
29
|
],
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@types/lodash.debounce": "^4.0.6"
|
|
32
|
-
"@types/redlock": "^4.0.3"
|
|
31
|
+
"@types/lodash.debounce": "^4.0.6"
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
|
-
"@hocuspocus/server": "^3.2.
|
|
34
|
+
"@hocuspocus/server": "^3.2.2",
|
|
35
|
+
"@sesamecare-oss/redlock": "^1.4.0",
|
|
36
36
|
"ioredis": "^5.6.1",
|
|
37
37
|
"kleur": "^4.1.4",
|
|
38
38
|
"lodash.debounce": "^4.0.8",
|
|
39
|
-
"redlock": "^4.2.0",
|
|
40
39
|
"uuid": "^11.0.3"
|
|
41
40
|
},
|
|
42
41
|
"peerDependencies": {
|
package/src/Redis.ts
CHANGED
|
@@ -16,13 +16,11 @@ import {
|
|
|
16
16
|
MessageReceiver,
|
|
17
17
|
OutgoingMessage,
|
|
18
18
|
} from "@hocuspocus/server";
|
|
19
|
-
import
|
|
19
|
+
import {Redlock, type ExecutionResult, type Lock} from '@sesamecare-oss/redlock';
|
|
20
|
+
import type {Cluster, ClusterNode, ClusterOptions, RedisOptions} from "ioredis";
|
|
20
21
|
import RedisClient from "ioredis";
|
|
21
|
-
import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
export type RedisInstance = RedisClient.Cluster | RedisClient.Redis;
|
|
25
|
-
|
|
22
|
+
import {v4 as uuid} from "uuid";
|
|
23
|
+
export type RedisInstance = RedisClient | Cluster
|
|
26
24
|
export interface Configuration {
|
|
27
25
|
/**
|
|
28
26
|
* Redis port
|
|
@@ -97,7 +95,7 @@ export class Redis implements Extension {
|
|
|
97
95
|
|
|
98
96
|
redlock: Redlock;
|
|
99
97
|
|
|
100
|
-
locks = new Map<string,
|
|
98
|
+
locks = new Map<string, {lock: Lock; release?: Promise<ExecutionResult>}>();
|
|
101
99
|
|
|
102
100
|
messagePrefix: Buffer;
|
|
103
101
|
|
|
@@ -138,7 +136,7 @@ export class Redis implements Extension {
|
|
|
138
136
|
this.sub.on("messageBuffer", this.handleIncomingMessage);
|
|
139
137
|
|
|
140
138
|
this.redlock = new Redlock([this.pub], {
|
|
141
|
-
|
|
139
|
+
driftFactor: 0.1
|
|
142
140
|
});
|
|
143
141
|
|
|
144
142
|
const identifierBuffer = Buffer.from(
|
|
@@ -214,7 +212,7 @@ export class Redis implements Extension {
|
|
|
214
212
|
.createSyncMessage()
|
|
215
213
|
.writeFirstSyncStepFor(document);
|
|
216
214
|
|
|
217
|
-
return this.pub.
|
|
215
|
+
return this.pub.publish(
|
|
218
216
|
this.pubKey(documentName),
|
|
219
217
|
this.encodeMessage(syncMessage.toUint8Array()),
|
|
220
218
|
);
|
|
@@ -228,7 +226,7 @@ export class Redis implements Extension {
|
|
|
228
226
|
documentName,
|
|
229
227
|
).writeQueryAwareness();
|
|
230
228
|
|
|
231
|
-
return this.pub.
|
|
229
|
+
return this.pub.publish(
|
|
232
230
|
this.pubKey(documentName),
|
|
233
231
|
this.encodeMessage(awarenessMessage.toUint8Array()),
|
|
234
232
|
);
|
|
@@ -238,49 +236,37 @@ export class Redis implements Extension {
|
|
|
238
236
|
* Before the document is stored, make sure to set a lock in Redis.
|
|
239
237
|
* That’s meant to avoid conflicts with other instances trying to store the document.
|
|
240
238
|
*/
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
console.log("unable to acquire lock");
|
|
254
|
-
reject();
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
this.locks.set(this.lockKey(documentName), lock);
|
|
259
|
-
|
|
260
|
-
resolve(undefined);
|
|
261
|
-
},
|
|
262
|
-
);
|
|
263
|
-
});
|
|
264
|
-
}
|
|
239
|
+
async onStoreDocument({documentName}: onStoreDocumentPayload) {
|
|
240
|
+
// Attempt to acquire a lock and read lastReceivedTimestamp from Redis,
|
|
241
|
+
// to avoid conflict with other instances storing the same document.
|
|
242
|
+
const resource = this.lockKey(documentName)
|
|
243
|
+
const ttl = this.configuration.lockTimeout
|
|
244
|
+
const lock = await this.redlock.acquire([resource], ttl)
|
|
245
|
+
const oldLock = this.locks.get(resource)
|
|
246
|
+
if (oldLock) {
|
|
247
|
+
await oldLock.release
|
|
248
|
+
}
|
|
249
|
+
this.locks.set(resource, {lock})
|
|
250
|
+
}
|
|
265
251
|
|
|
266
252
|
/**
|
|
267
253
|
* Release the Redis lock, so other instances can store documents.
|
|
268
254
|
*/
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
255
|
+
async afterStoreDocument({documentName, socketId}: afterStoreDocumentPayload) {
|
|
256
|
+
const lockKey = this.lockKey(documentName)
|
|
257
|
+
const lock = this.locks.get(lockKey)
|
|
258
|
+
if (!lock) {
|
|
259
|
+
throw new Error(`Lock created in onStoreDocument not found in afterStoreDocument: ${lockKey}`)
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
// Always try to unlock and clean up the lock
|
|
263
|
+
lock.release = lock.lock.release()
|
|
264
|
+
await lock.release
|
|
265
|
+
} catch {
|
|
266
|
+
// Lock will expire on its own after timeout
|
|
267
|
+
} finally {
|
|
268
|
+
this.locks.delete(lockKey)
|
|
269
|
+
}
|
|
284
270
|
// if the change was initiated by a directConnection, we need to delay this hook to make sure sync can finish first.
|
|
285
271
|
// for provider connections, this usually happens in the onDisconnect hook
|
|
286
272
|
if (socketId === "server") {
|
|
@@ -326,7 +312,7 @@ export class Redis implements Extension {
|
|
|
326
312
|
documentName,
|
|
327
313
|
).createAwarenessUpdateMessage(awareness, changedClients);
|
|
328
314
|
|
|
329
|
-
return this.pub.
|
|
315
|
+
return this.pub.publish(
|
|
330
316
|
this.pubKey(documentName),
|
|
331
317
|
this.encodeMessage(message.toUint8Array()),
|
|
332
318
|
);
|
|
@@ -358,7 +344,7 @@ export class Redis implements Extension {
|
|
|
358
344
|
document,
|
|
359
345
|
undefined,
|
|
360
346
|
(reply) => {
|
|
361
|
-
return this.pub.
|
|
347
|
+
return this.pub.publish(
|
|
362
348
|
this.pubKey(document.name),
|
|
363
349
|
this.encodeMessage(reply),
|
|
364
350
|
);
|
|
@@ -393,7 +379,7 @@ export class Redis implements Extension {
|
|
|
393
379
|
this.pendingDisconnects.delete(documentName);
|
|
394
380
|
|
|
395
381
|
// Do nothing, when other users are still connected to the document.
|
|
396
|
-
if (
|
|
382
|
+
if (document && document.getConnectionsCount() > 0) {
|
|
397
383
|
return;
|
|
398
384
|
}
|
|
399
385
|
|
|
@@ -404,7 +390,9 @@ export class Redis implements Extension {
|
|
|
404
390
|
}
|
|
405
391
|
});
|
|
406
392
|
|
|
407
|
-
|
|
393
|
+
if(document) {
|
|
394
|
+
this.instance.unloadDocument(document);
|
|
395
|
+
}
|
|
408
396
|
};
|
|
409
397
|
// Delay the disconnect procedure to allow last minute syncs to happen
|
|
410
398
|
const timeout = setTimeout(disconnect, this.configuration.disconnectDelay);
|
|
@@ -416,7 +404,7 @@ export class Redis implements Extension {
|
|
|
416
404
|
data.documentName,
|
|
417
405
|
).writeBroadcastStateless(data.payload);
|
|
418
406
|
|
|
419
|
-
return this.pub.
|
|
407
|
+
return this.pub.publish(
|
|
420
408
|
this.pubKey(data.documentName),
|
|
421
409
|
this.encodeMessage(message.toUint8Array()),
|
|
422
410
|
);
|