@dra2020/baseclient 1.0.13 → 1.0.14
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/LICENSE +21 -0
- package/README.md +37 -0
- package/dist/all/all.d.ts +36 -0
- package/dist/all/allclient.d.ts +18 -0
- package/dist/base.js +33010 -0
- package/dist/base.js.map +1 -0
- package/dist/baseclient.js +8991 -0
- package/dist/baseclient.js.map +1 -0
- package/dist/context/all.d.ts +1 -0
- package/dist/context/context.d.ts +13 -0
- package/dist/dbabstract/all.d.ts +1 -0
- package/dist/dbabstract/db.d.ts +83 -0
- package/dist/dbdynamo/all.d.ts +1 -0
- package/dist/dbdynamo/dbdynamo.d.ts +190 -0
- package/dist/filterexpr/all.d.ts +1 -0
- package/dist/filterexpr/filterexpr.d.ts +64 -0
- package/dist/fsm/all.d.ts +1 -0
- package/dist/fsm/fsm.d.ts +118 -0
- package/dist/fsmfile/all.d.ts +1 -0
- package/dist/fsmfile/fsmfile.d.ts +47 -0
- package/dist/jsonstream/all.d.ts +1 -0
- package/dist/jsonstream/jsonstream.d.ts +130 -0
- package/dist/lambda/all.d.ts +1 -0
- package/dist/lambda/env.d.ts +10 -0
- package/dist/lambda/lambda.d.ts +18 -0
- package/dist/logabstract/all.d.ts +1 -0
- package/dist/logabstract/log.d.ts +26 -0
- package/dist/logclient/all.d.ts +1 -0
- package/dist/logclient/log.d.ts +6 -0
- package/dist/logserver/all.d.ts +5 -0
- package/dist/logserver/log.d.ts +11 -0
- package/dist/logserver/logaccum.d.ts +154 -0
- package/dist/logserver/logblob.d.ts +24 -0
- package/dist/logserver/logconcat.d.ts +55 -0
- package/dist/logserver/logkey.d.ts +28 -0
- package/dist/memsqs/all.d.ts +4 -0
- package/dist/memsqs/client.d.ts +13 -0
- package/dist/memsqs/loopback.d.ts +11 -0
- package/dist/memsqs/orderedlist.d.ts +19 -0
- package/dist/memsqs/queue.d.ts +84 -0
- package/dist/memsqs/server.d.ts +37 -0
- package/dist/ot-editutil/all.d.ts +2 -0
- package/dist/ot-editutil/oteditutil.d.ts +14 -0
- package/dist/ot-editutil/otmaputil.d.ts +21 -0
- package/dist/ot-js/all.d.ts +9 -0
- package/dist/ot-js/otarray.d.ts +111 -0
- package/dist/ot-js/otclientengine.d.ts +38 -0
- package/dist/ot-js/otcomposite.d.ts +37 -0
- package/dist/ot-js/otcounter.d.ts +17 -0
- package/dist/ot-js/otengine.d.ts +22 -0
- package/dist/ot-js/otmap.d.ts +19 -0
- package/dist/ot-js/otserverengine.d.ts +38 -0
- package/dist/ot-js/otsession.d.ts +111 -0
- package/dist/ot-js/ottypes.d.ts +29 -0
- package/dist/poly/all.d.ts +15 -0
- package/dist/poly/blend.d.ts +1 -0
- package/dist/poly/boundbox.d.ts +16 -0
- package/dist/poly/cartesian.d.ts +5 -0
- package/dist/poly/graham-scan.d.ts +8 -0
- package/dist/poly/hash.d.ts +1 -0
- package/dist/poly/matrix.d.ts +24 -0
- package/dist/poly/minbound.d.ts +1 -0
- package/dist/poly/poly.d.ts +52 -0
- package/dist/poly/polybin.d.ts +5 -0
- package/dist/poly/polylabel.d.ts +7 -0
- package/dist/poly/polypack.d.ts +30 -0
- package/dist/poly/polyround.d.ts +1 -0
- package/dist/poly/polysimplify.d.ts +1 -0
- package/dist/poly/quad.d.ts +48 -0
- package/dist/poly/selfintersect.d.ts +1 -0
- package/dist/poly/shamos.d.ts +1 -0
- package/dist/poly/simplify.d.ts +2 -0
- package/dist/poly/topo.d.ts +46 -0
- package/dist/poly/union.d.ts +48 -0
- package/dist/storage/all.d.ts +4 -0
- package/dist/storage/datablob.d.ts +9 -0
- package/dist/storage/env.d.ts +10 -0
- package/dist/storage/splitsblob.d.ts +13 -0
- package/dist/storage/storage.d.ts +166 -0
- package/dist/storages3/all.d.ts +1 -0
- package/dist/storages3/s3.d.ts +62 -0
- package/dist/util/all.d.ts +5 -0
- package/dist/util/bintrie.d.ts +93 -0
- package/dist/util/countedhash.d.ts +19 -0
- package/dist/util/gradient.d.ts +15 -0
- package/dist/util/indexedarray.d.ts +15 -0
- package/dist/util/util.d.ts +68 -0
- package/docs/context.md +2 -0
- package/docs/dbabstract.md +2 -0
- package/docs/dbdynamo.md +2 -0
- package/docs/fsm.md +243 -0
- package/docs/fsmfile.md +2 -0
- package/docs/jsonstream.md +44 -0
- package/docs/lambda.md +2 -0
- package/docs/logabstract.md +2 -0
- package/docs/logclient.md +2 -0
- package/docs/logserver.md +2 -0
- package/docs/ot-editutil.md +2 -0
- package/docs/ot-js.md +95 -0
- package/docs/poly.md +103 -0
- package/docs/storage.md +2 -0
- package/docs/storages3.md +2 -0
- package/docs/util.md +2 -0
- package/lib/all/all.ts +41 -0
- package/lib/all/allclient.ts +19 -0
- package/lib/context/all.ts +1 -0
- package/lib/context/context.ts +82 -0
- package/lib/dbabstract/all.ts +1 -0
- package/lib/dbabstract/db.ts +246 -0
- package/lib/dbdynamo/all.ts +1 -0
- package/lib/dbdynamo/dbdynamo.ts +1551 -0
- package/lib/filterexpr/all.ts +1 -0
- package/lib/filterexpr/filterexpr.ts +625 -0
- package/lib/fsm/all.ts +1 -0
- package/lib/fsm/fsm.ts +549 -0
- package/lib/fsmfile/all.ts +1 -0
- package/lib/fsmfile/fsmfile.ts +236 -0
- package/lib/jsonstream/all.ts +1 -0
- package/lib/jsonstream/jsonstream.ts +940 -0
- package/lib/lambda/all.ts +1 -0
- package/lib/lambda/env.ts +13 -0
- package/lib/lambda/lambda.ts +120 -0
- package/lib/logabstract/all.ts +1 -0
- package/lib/logabstract/log.ts +55 -0
- package/lib/logclient/all.ts +1 -0
- package/lib/logclient/log.ts +105 -0
- package/lib/logserver/all.ts +5 -0
- package/lib/logserver/log.ts +565 -0
- package/lib/logserver/logaccum.ts +1445 -0
- package/lib/logserver/logblob.ts +84 -0
- package/lib/logserver/logconcat.ts +313 -0
- package/lib/logserver/logkey.ts +125 -0
- package/lib/memsqs/all.ts +4 -0
- package/lib/memsqs/client.ts +268 -0
- package/lib/memsqs/loopback.ts +64 -0
- package/lib/memsqs/orderedlist.ts +74 -0
- package/lib/memsqs/queue.ts +395 -0
- package/lib/memsqs/server.ts +262 -0
- package/lib/ot-editutil/all.ts +2 -0
- package/lib/ot-editutil/oteditutil.ts +180 -0
- package/lib/ot-editutil/otmaputil.ts +209 -0
- package/lib/ot-js/all.ts +9 -0
- package/lib/ot-js/otarray.ts +1168 -0
- package/lib/ot-js/otclientengine.ts +327 -0
- package/lib/ot-js/otcomposite.ts +247 -0
- package/lib/ot-js/otcounter.ts +145 -0
- package/lib/ot-js/otengine.ts +71 -0
- package/lib/ot-js/otmap.ts +144 -0
- package/lib/ot-js/otserverengine.ts +329 -0
- package/lib/ot-js/otsession.ts +199 -0
- package/lib/ot-js/ottypes.ts +98 -0
- package/lib/poly/all.ts +15 -0
- package/lib/poly/blend.ts +27 -0
- package/lib/poly/boundbox.ts +102 -0
- package/lib/poly/cartesian.ts +130 -0
- package/lib/poly/graham-scan.ts +401 -0
- package/lib/poly/hash.ts +15 -0
- package/lib/poly/matrix.ts +309 -0
- package/lib/poly/minbound.ts +211 -0
- package/lib/poly/poly.ts +767 -0
- package/lib/poly/polybin.ts +218 -0
- package/lib/poly/polylabel.ts +204 -0
- package/lib/poly/polypack.ts +458 -0
- package/lib/poly/polyround.ts +30 -0
- package/lib/poly/polysimplify.ts +24 -0
- package/lib/poly/quad.ts +272 -0
- package/lib/poly/selfintersect.ts +87 -0
- package/lib/poly/shamos.ts +297 -0
- package/lib/poly/simplify.ts +119 -0
- package/lib/poly/topo.ts +525 -0
- package/lib/poly/union.ts +371 -0
- package/lib/storage/all.ts +4 -0
- package/lib/storage/datablob.ts +36 -0
- package/lib/storage/env.ts +14 -0
- package/lib/storage/splitsblob.ts +63 -0
- package/lib/storage/storage.ts +604 -0
- package/lib/storages3/all.ts +1 -0
- package/lib/storages3/s3.ts +576 -0
- package/lib/util/all.ts +5 -0
- package/lib/util/bintrie.ts +603 -0
- package/lib/util/countedhash.ts +83 -0
- package/lib/util/gradient.ts +108 -0
- package/lib/util/indexedarray.ts +80 -0
- package/lib/util/util.ts +695 -0
- package/package.json +8 -8
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
// Project libraries
|
|
2
|
+
import * as Util from '../util/all';
|
|
3
|
+
|
|
4
|
+
// Local libraries
|
|
5
|
+
import * as OL from './orderedlist';
|
|
6
|
+
|
|
7
|
+
export const DefaultPort: number = (process.env['MEMSQS_PORT'] ? Number(process.env['MEMSQS_PORT']) : 0) || 80;
|
|
8
|
+
export const DefaultServerUrl: string = `http://localhost:${DefaultPort}`;
|
|
9
|
+
|
|
10
|
+
export interface QMessage
|
|
11
|
+
{
|
|
12
|
+
messageid: string;
|
|
13
|
+
groupid: string;
|
|
14
|
+
seqno?: number;
|
|
15
|
+
contents?: any;
|
|
16
|
+
blobid?: string;
|
|
17
|
+
}
|
|
18
|
+
export type QMessages = QMessage[];
|
|
19
|
+
|
|
20
|
+
interface QMessageHolder
|
|
21
|
+
{
|
|
22
|
+
m: QMessage;
|
|
23
|
+
pending: boolean;
|
|
24
|
+
deadline: Util.Deadline;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type QMessageList = OL.OrderedList<QMessageHolder>;
|
|
28
|
+
type QMessageIndex = { [key: string]: boolean };
|
|
29
|
+
|
|
30
|
+
interface QVisibility
|
|
31
|
+
{
|
|
32
|
+
owner: string;
|
|
33
|
+
deadline: Util.Deadline;
|
|
34
|
+
index: QMessageIndex;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const DefaultVisibility: QVisibility = { owner: '', deadline: null, index: null };
|
|
38
|
+
|
|
39
|
+
interface QGroup
|
|
40
|
+
{
|
|
41
|
+
id: string;
|
|
42
|
+
messages: QMessageList;
|
|
43
|
+
visibility: QVisibility;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
type QGroupIndex = { [id: string]: QGroup };
|
|
47
|
+
|
|
48
|
+
export interface QQueueOptions
|
|
49
|
+
{
|
|
50
|
+
timeoutVisibility: number,
|
|
51
|
+
timeoutDead: number,
|
|
52
|
+
timeoutQueueDead: number,
|
|
53
|
+
receiveLimit: number,
|
|
54
|
+
longpoll: boolean,
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const DefaultQueueOptions = {
|
|
58
|
+
timeoutVisibility: 1000 * 30, // 30 seconds - if server goes down, another server will grab req in 30 sec
|
|
59
|
+
timeoutDead: 1000 * 60 * 30, // 30 minutes - just cleaning up dead server queue
|
|
60
|
+
timeoutQueueDead: 1000 * 60 * 30, // 30 minutes - just cleaning up dead server queue
|
|
61
|
+
receiveLimit: 10, // don't get too greedy, but some batching for efficiency
|
|
62
|
+
longpoll: true,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
class QQueue
|
|
66
|
+
{
|
|
67
|
+
id: string;
|
|
68
|
+
groups: QGroupIndex;
|
|
69
|
+
seqno: number;
|
|
70
|
+
options: QQueueOptions;
|
|
71
|
+
nHeld: number;
|
|
72
|
+
lastActive: Util.Elapsed;
|
|
73
|
+
|
|
74
|
+
constructor(id: string, options?: QQueueOptions)
|
|
75
|
+
{
|
|
76
|
+
this.id = id;
|
|
77
|
+
this.options = options ? options : Util.shallowCopy(DefaultQueueOptions);
|
|
78
|
+
this.groups = {};
|
|
79
|
+
this.nHeld = 0;
|
|
80
|
+
this.seqno = 0;
|
|
81
|
+
this.lastActive = new Util.Elapsed();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
dumpLog(): void
|
|
85
|
+
{
|
|
86
|
+
console.log(`memsqs: queue: ${this.id}: ${this.nHeld} messages held`);
|
|
87
|
+
this.forEachGroup((g: QGroup) => {
|
|
88
|
+
if (! g.messages.isempty())
|
|
89
|
+
console.log(`memsqs: queue ${this.id}: group: ${g.id}: non-empty`);
|
|
90
|
+
if (g.visibility.owner != '')
|
|
91
|
+
console.log(`memsqs: queue ${this.id}: group: ${g.id}: owned by ${g.visibility.owner}`);
|
|
92
|
+
return true;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
setOptions(options: QQueueOptions)
|
|
97
|
+
{
|
|
98
|
+
this.options = Util.shallowCopy(options);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_group(id: string): QGroup
|
|
102
|
+
{
|
|
103
|
+
let g = this.groups[id];
|
|
104
|
+
if (g === undefined)
|
|
105
|
+
{
|
|
106
|
+
g = { id: id, messages: new OL.OrderedList<QMessageHolder>(), visibility: Util.shallowCopy(DefaultVisibility) };
|
|
107
|
+
this.groups[g.id] = g;
|
|
108
|
+
}
|
|
109
|
+
return g;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
forEachGroup(cb: (g: QGroup) => boolean): void
|
|
113
|
+
{
|
|
114
|
+
for (let id in this.groups) if (this.groups.hasOwnProperty(id))
|
|
115
|
+
if (! cb(this.groups[id]))
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
forEachMessageHolder(g: QGroup, cb: (h: QMessageHolder) => boolean): void
|
|
120
|
+
{
|
|
121
|
+
g.messages.forEach(cb);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get isActive(): boolean
|
|
125
|
+
{
|
|
126
|
+
if (this.nHeld > 0) return true;
|
|
127
|
+
|
|
128
|
+
this.lastActive.end();
|
|
129
|
+
return this.lastActive.ms() < this.options.timeoutQueueDead;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
checkTimeout(): void
|
|
133
|
+
{
|
|
134
|
+
// Clear visibility and cull empty groups
|
|
135
|
+
this.forEachGroup((g: QGroup) => {
|
|
136
|
+
// Clear deadline if passed
|
|
137
|
+
if (g.visibility.deadline && g.visibility.deadline.done())
|
|
138
|
+
{
|
|
139
|
+
g.messages.forEach((h: QMessageHolder) => {
|
|
140
|
+
h.pending = false;
|
|
141
|
+
return true; // continue
|
|
142
|
+
});
|
|
143
|
+
g.visibility = Util.shallowCopy(DefaultVisibility);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Cull group if no messages and no deadline.
|
|
147
|
+
// Note that not culling group means that we can build up list of groups (during deadline)
|
|
148
|
+
// if many groups are used (like one-per-message which is standard if no grouping is needed).
|
|
149
|
+
if (g.visibility.owner === '' && g.messages.isempty())
|
|
150
|
+
delete this.groups[g.id];
|
|
151
|
+
return true; // continue
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Cull messages past deadline
|
|
155
|
+
this.forEachGroup((g: QGroup) => {
|
|
156
|
+
g.messages.forEach((h: QMessageHolder) => {
|
|
157
|
+
if (h.deadline.done())
|
|
158
|
+
this.removeFromGroup(g, h.m);
|
|
159
|
+
return true; // continue
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Cull group if no messages and no deadline
|
|
163
|
+
if (g.visibility.owner === '' && g.messages.isempty())
|
|
164
|
+
delete this.groups[g.id];
|
|
165
|
+
return true; // continue
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
send(m: QMessage): string
|
|
171
|
+
{
|
|
172
|
+
this.lastActive.start();
|
|
173
|
+
if (m.groupid === '*')
|
|
174
|
+
{
|
|
175
|
+
let e: string = null;
|
|
176
|
+
this.forEachGroup((g: QGroup) => {
|
|
177
|
+
if (g.visibility.owner !== '')
|
|
178
|
+
{
|
|
179
|
+
this.nHeld++;
|
|
180
|
+
let o = g.visibility.owner;
|
|
181
|
+
let gm: QMessage = { messageid: m.messageid + o, groupid: o, contents: m.contents, seqno: this.seqno++ };
|
|
182
|
+
e = g.messages.insert(gm.messageid, { m: gm, pending: false, deadline: new Util.Deadline(this.options.timeoutDead) } );
|
|
183
|
+
return e == null;
|
|
184
|
+
}
|
|
185
|
+
else
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
return e;
|
|
189
|
+
}
|
|
190
|
+
else
|
|
191
|
+
{
|
|
192
|
+
m.seqno = this.seqno++; // inject sequence number
|
|
193
|
+
this.nHeld++;
|
|
194
|
+
let g = this._group(m.groupid);
|
|
195
|
+
return g.messages.insert(m.messageid, { m: m, pending: false, deadline: new Util.Deadline(this.options.timeoutDead) } );
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
claim(owner: string, groupid: string): string
|
|
200
|
+
{
|
|
201
|
+
this.lastActive.start();
|
|
202
|
+
let g = this._group(groupid);
|
|
203
|
+
let deadline = new Util.Deadline(this.options.timeoutVisibility);
|
|
204
|
+
if (g.visibility.owner === '')
|
|
205
|
+
{
|
|
206
|
+
g.visibility = { owner: owner, deadline: deadline, index: {} };
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
else if (g.visibility.owner === owner)
|
|
210
|
+
{
|
|
211
|
+
g.visibility.deadline = deadline;
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
return `memsqs: claim: group ${groupid} already owned by ${g.visibility.owner}, ${owner} cannot claim.`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
receive(owner: string, result: QMessages): void
|
|
218
|
+
{
|
|
219
|
+
this.lastActive.start();
|
|
220
|
+
let deadline = new Util.Deadline(this.options.timeoutVisibility);
|
|
221
|
+
|
|
222
|
+
// First prioritize returning non-pending messages for same owner
|
|
223
|
+
this.forEachGroup((g: QGroup) => {
|
|
224
|
+
if (g.visibility.owner === owner)
|
|
225
|
+
{
|
|
226
|
+
g.messages.forEach((h: QMessageHolder) => {
|
|
227
|
+
if (! h.pending)
|
|
228
|
+
{
|
|
229
|
+
h.pending = true;
|
|
230
|
+
g.visibility.index[h.m.messageid] = true;
|
|
231
|
+
g.visibility.deadline = deadline;
|
|
232
|
+
result.push(h.m);
|
|
233
|
+
if (result.length >= this.options.receiveLimit)
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return true;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Now go through and add messages for any unclaimed message groups
|
|
243
|
+
if (result.length >= this.options.receiveLimit) return;
|
|
244
|
+
|
|
245
|
+
this.forEachGroup((g: QGroup) => {
|
|
246
|
+
if (g.visibility.owner === '')
|
|
247
|
+
{
|
|
248
|
+
g.messages.forEach((h: QMessageHolder) => {
|
|
249
|
+
if (! h.pending)
|
|
250
|
+
{
|
|
251
|
+
if (g.visibility.owner === '')
|
|
252
|
+
g.visibility = { owner: owner, deadline: deadline, index: {} };
|
|
253
|
+
h.pending = true;
|
|
254
|
+
g.visibility.index[h.m.messageid] = true;
|
|
255
|
+
result.push(h.m);
|
|
256
|
+
if (result.length >= this.options.receiveLimit)
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return true;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
removeFromGroup(g: QGroup, m: QMessage): string
|
|
267
|
+
{
|
|
268
|
+
this.lastActive.start();
|
|
269
|
+
let result = g.messages.remove(m.messageid);
|
|
270
|
+
if (g.visibility.index)
|
|
271
|
+
delete g.visibility.index[m.messageid];
|
|
272
|
+
if (result === null) this.nHeld--;
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
remove(m: QMessage): string
|
|
277
|
+
{
|
|
278
|
+
let g = this._group(m.groupid);
|
|
279
|
+
if (g)
|
|
280
|
+
{
|
|
281
|
+
let result = this.removeFromGroup(g, m);
|
|
282
|
+
|
|
283
|
+
// Comment these lines out to leave visiblity constrained through deadline.
|
|
284
|
+
// (Not completely sure what AWS semantics for SQS is on this point.)
|
|
285
|
+
// The alternative is to force client to send/receive/remove keep-alive messages.
|
|
286
|
+
// if (Util.isEmpty(g.visibility.index))
|
|
287
|
+
// g.visibility = Util.shallowCopy(DefaultVisibility);
|
|
288
|
+
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
else
|
|
292
|
+
return `memsqs: remove: queue ${this.id} has no existing group ${m.groupid}`;;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
type QQueueIndex = { [id: string]: QQueue };
|
|
297
|
+
|
|
298
|
+
export class QQueueManager
|
|
299
|
+
{
|
|
300
|
+
queues: QQueueIndex;
|
|
301
|
+
|
|
302
|
+
nSent: number;
|
|
303
|
+
nReceived: number;
|
|
304
|
+
nRemoved: number;
|
|
305
|
+
nCulled: number;
|
|
306
|
+
|
|
307
|
+
constructor()
|
|
308
|
+
{
|
|
309
|
+
this.queues = {};
|
|
310
|
+
this.nSent = 0;
|
|
311
|
+
this.nReceived = 0;
|
|
312
|
+
this.nRemoved = 0;
|
|
313
|
+
this.nCulled = 0;
|
|
314
|
+
this.checkTimeout = this.checkTimeout.bind(this);
|
|
315
|
+
setTimeout(this.checkTimeout, 5000);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
_queue(queueid: string): QQueue
|
|
319
|
+
{
|
|
320
|
+
let q = this.queues[queueid];
|
|
321
|
+
if (q === undefined)
|
|
322
|
+
{
|
|
323
|
+
q = new QQueue(queueid);
|
|
324
|
+
this.queues[queueid] = q;
|
|
325
|
+
}
|
|
326
|
+
return q;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
checkTimeout(): void
|
|
330
|
+
{
|
|
331
|
+
this.forEach((q: QQueue) => {
|
|
332
|
+
let nHeld = q.nHeld;
|
|
333
|
+
q.checkTimeout();
|
|
334
|
+
this.nCulled += nHeld - q.nHeld;
|
|
335
|
+
if (! q.isActive)
|
|
336
|
+
{
|
|
337
|
+
console.log(`memsqs:checkTimeout: deleting inactive queue ${q.id}`);
|
|
338
|
+
delete this.queues[q.id];
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
setTimeout(this.checkTimeout, 5000);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
forEach(cb: (q: QQueue) => void): void
|
|
346
|
+
{
|
|
347
|
+
for (let qid in this.queues) if (this.queues.hasOwnProperty(qid))
|
|
348
|
+
cb(this.queues[qid]);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
dumpLog(): void
|
|
352
|
+
{
|
|
353
|
+
this.forEach((q: QQueue) => {
|
|
354
|
+
q.dumpLog();
|
|
355
|
+
return true;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
setOptions(queueid: string, options: QQueueOptions): void
|
|
360
|
+
{
|
|
361
|
+
this._queue(queueid).setOptions(options);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
isLongpoll(queueid: string): boolean
|
|
365
|
+
{
|
|
366
|
+
return this._queue(queueid).options.longpoll;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
claim(queueid: string, owner: string, groupid: string): string
|
|
370
|
+
{
|
|
371
|
+
return this._queue(queueid).claim(owner, groupid);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
send(queueid: string, m: QMessage): string
|
|
375
|
+
{
|
|
376
|
+
let result = this._queue(queueid).send(m);
|
|
377
|
+
if (! result)
|
|
378
|
+
this.nSent++;
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
receive(queueid: string, owner: string, result: QMessages): void
|
|
383
|
+
{
|
|
384
|
+
this._queue(queueid).receive(owner, result);
|
|
385
|
+
this.nReceived += result.length;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
remove(queueid: string, m: QMessage): string
|
|
389
|
+
{
|
|
390
|
+
let result = this._queue(queueid).remove(m);
|
|
391
|
+
if (! result)
|
|
392
|
+
this.nRemoved++;
|
|
393
|
+
return result;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// Node libraries
|
|
2
|
+
import * as http from 'http';
|
|
3
|
+
import * as url from 'url';
|
|
4
|
+
|
|
5
|
+
// Shared libraries
|
|
6
|
+
import * as JS from '../jsonstream/all';
|
|
7
|
+
|
|
8
|
+
// Local libraries
|
|
9
|
+
import * as Q from './queue';
|
|
10
|
+
|
|
11
|
+
const LongPollTimeout: number = 5000;
|
|
12
|
+
|
|
13
|
+
export class SQSServer
|
|
14
|
+
{
|
|
15
|
+
private port: number;
|
|
16
|
+
private server: any;
|
|
17
|
+
queuemanager: Q.QQueueManager;
|
|
18
|
+
longpoll: LongPoll;
|
|
19
|
+
|
|
20
|
+
constructor(port: number = Q.DefaultPort)
|
|
21
|
+
{
|
|
22
|
+
this.queuemanager = new Q.QQueueManager();
|
|
23
|
+
this.longpoll = new LongPoll();
|
|
24
|
+
this.port = port;
|
|
25
|
+
|
|
26
|
+
this.server = http.createServer();
|
|
27
|
+
this.server.keepAliveTimeout = 61 * 1000; // Don't interfere with longpoll timeout
|
|
28
|
+
this.server.headersTimeout = 65 * 1000; // Longer than keepAliveTimeout
|
|
29
|
+
|
|
30
|
+
this.server.on('request', (req: any, res: any) => {
|
|
31
|
+
new OneRequest(this, req, res);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.server.on('close', () => {
|
|
35
|
+
this.server = null;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
this.server.on('error', (err: any) => {
|
|
39
|
+
if (err && err.message) err = err.message;
|
|
40
|
+
console.log(`memsqs: server: unexpected error ${err}`);
|
|
41
|
+
this.server = null;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
try
|
|
45
|
+
{
|
|
46
|
+
this.server.listen(this.port);
|
|
47
|
+
}
|
|
48
|
+
catch (err)
|
|
49
|
+
{
|
|
50
|
+
console.log(`memsqs: server: unexpected exception on listen: ${JSON.stringify(err)}`);
|
|
51
|
+
this.server = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get running(): boolean
|
|
56
|
+
{
|
|
57
|
+
return this.server != null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
close(): void
|
|
61
|
+
{
|
|
62
|
+
if (this.server)
|
|
63
|
+
this.server.close();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface LongPollRequest
|
|
68
|
+
{
|
|
69
|
+
deadline: number;
|
|
70
|
+
request: OneRequest;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
class LongPoll
|
|
74
|
+
{
|
|
75
|
+
requests: LongPollRequest[];
|
|
76
|
+
|
|
77
|
+
constructor()
|
|
78
|
+
{
|
|
79
|
+
this.requests = [];
|
|
80
|
+
this.tick = this.tick.bind(this);
|
|
81
|
+
setTimeout(this.tick, 1000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
check(): void
|
|
85
|
+
{
|
|
86
|
+
let deadline: number = (new Date()).getTime();
|
|
87
|
+
for (let i: number = 0; i < this.requests.length; )
|
|
88
|
+
{
|
|
89
|
+
let r = this.requests[i];
|
|
90
|
+
if (! r.request.onRetry())
|
|
91
|
+
if (r.deadline < deadline)
|
|
92
|
+
r.request.onFinish();
|
|
93
|
+
if (r.request.isDone())
|
|
94
|
+
this.requests.splice(i, 1);
|
|
95
|
+
else
|
|
96
|
+
i++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
tick(): void
|
|
101
|
+
{
|
|
102
|
+
this.check();
|
|
103
|
+
setTimeout(this.tick, 1000);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
add(one: OneRequest): void
|
|
107
|
+
{
|
|
108
|
+
//console.log('memsqs: queuing longpoll');
|
|
109
|
+
this.requests.push({ deadline: (new Date()).getTime() + LongPollTimeout, request: one });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class OneRequest
|
|
114
|
+
{
|
|
115
|
+
server: SQSServer;
|
|
116
|
+
req: any;
|
|
117
|
+
res: any;
|
|
118
|
+
jsonstream: JS.JSONStreamReader;
|
|
119
|
+
json: any;
|
|
120
|
+
body: any;
|
|
121
|
+
|
|
122
|
+
constructor(server: SQSServer, req: any, res: any)
|
|
123
|
+
{
|
|
124
|
+
this.server = server;
|
|
125
|
+
this.req = req;
|
|
126
|
+
this.res = res;
|
|
127
|
+
this.jsonstream = new JS.JSONStreamReader();
|
|
128
|
+
this.json = null;
|
|
129
|
+
this.body = { statuscode: 0 };
|
|
130
|
+
this.jsonstream.on('end', (json: any) => { this.json = json; this.onDone(); });
|
|
131
|
+
this.jsonstream.on('error', (err: any) => {
|
|
132
|
+
this.onError(`memsqs: error parsing request body: ${err}`);
|
|
133
|
+
});
|
|
134
|
+
this.req.on('error', (err: any) => {
|
|
135
|
+
this.onError(`memsqs: error reading request: ${err}`);
|
|
136
|
+
});
|
|
137
|
+
this.res.on('error', (err: any) => {
|
|
138
|
+
this.onError(`memsqs: error writing response: ${err}`);
|
|
139
|
+
});
|
|
140
|
+
this.jsonstream.start(req);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
isDone(): boolean
|
|
144
|
+
{
|
|
145
|
+
return this.res == null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
onRetry(): boolean
|
|
149
|
+
{
|
|
150
|
+
let result: Q.QMessages = [];
|
|
151
|
+
let qm = this.server.queuemanager;
|
|
152
|
+
qm.receive(this.json.queueid, this.json.owner, result);
|
|
153
|
+
this.body.result = result;
|
|
154
|
+
if (result.length != 0)
|
|
155
|
+
{
|
|
156
|
+
this.onFinish();
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
else
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
onDone(): void
|
|
164
|
+
{
|
|
165
|
+
try
|
|
166
|
+
{
|
|
167
|
+
if (this.json.queueid === undefined || this.json.queueid == '' || this.json.api === undefined)
|
|
168
|
+
{
|
|
169
|
+
this.onError(`memsqs: badly formed request: ${JSON.stringify(this.json)}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let qm = this.server.queuemanager;
|
|
174
|
+
|
|
175
|
+
switch (this.json.api)
|
|
176
|
+
{
|
|
177
|
+
case 'setoptions':
|
|
178
|
+
if (this.json.data === undefined)
|
|
179
|
+
this.onError('memsqs: setoptions: payload is empty');
|
|
180
|
+
else
|
|
181
|
+
qm.setOptions(this.json.queueid, this.json.data as Q.QQueueOptions);
|
|
182
|
+
break;
|
|
183
|
+
|
|
184
|
+
case 'claim':
|
|
185
|
+
if (this.json.data === undefined || this.json.data.owner === undefined || this.json.data.groupid === undefined)
|
|
186
|
+
this.onError('memsqs: claim: badly formed request');
|
|
187
|
+
else
|
|
188
|
+
{
|
|
189
|
+
let err = qm.claim(this.json.queueid, this.json.data.owner, this.json.data.groupid);
|
|
190
|
+
if (err)
|
|
191
|
+
this.onError(err);
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case 'send':
|
|
196
|
+
if (this.json.data === undefined)
|
|
197
|
+
this.onError('memsqs: send: payload is empty');
|
|
198
|
+
else
|
|
199
|
+
{
|
|
200
|
+
let err = qm.send(this.json.queueid, this.json.data as Q.QMessage);
|
|
201
|
+
if (err)
|
|
202
|
+
this.onError(err);
|
|
203
|
+
else
|
|
204
|
+
this.server.longpoll.check();
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
|
|
208
|
+
case 'receive':
|
|
209
|
+
if (this.json.owner === undefined)
|
|
210
|
+
this.onError('memsqs: receive: no owner specified');
|
|
211
|
+
else
|
|
212
|
+
{
|
|
213
|
+
let result: Q.QMessages = [];
|
|
214
|
+
qm.receive(this.json.queueid, this.json.owner, result);
|
|
215
|
+
this.body.result = result;
|
|
216
|
+
if (result.length == 0 && qm.isLongpoll(this.json.queueid))
|
|
217
|
+
{
|
|
218
|
+
this.server.longpoll.add(this);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
|
|
224
|
+
case 'remove':
|
|
225
|
+
if (this.json.data === undefined)
|
|
226
|
+
this.onError('memsqs: remove: payload is empty');
|
|
227
|
+
else
|
|
228
|
+
{
|
|
229
|
+
let err = qm.remove(this.json.queueid, this.json.data as Q.QMessage);
|
|
230
|
+
if (err)
|
|
231
|
+
this.onError(err);
|
|
232
|
+
}
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
this.onFinish();
|
|
237
|
+
}
|
|
238
|
+
catch (err)
|
|
239
|
+
{
|
|
240
|
+
console.log(`memsqs: server: unexpected exception in onDone: ${JSON.stringify(err)}`);
|
|
241
|
+
this.onError((err && err.message) ? err.message : err);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
onError(s: string): void
|
|
246
|
+
{
|
|
247
|
+
console.log(`${s}: reporting failure`);
|
|
248
|
+
this.body.statuscode = 1;
|
|
249
|
+
this.body.error = 'failure';
|
|
250
|
+
this.onFinish();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
onFinish(): void
|
|
254
|
+
{
|
|
255
|
+
if (this.res)
|
|
256
|
+
{
|
|
257
|
+
this.res.writeHead(200, { 'Content-Type': 'application/json; charset=UTF-8' });
|
|
258
|
+
this.res.end(JSON.stringify(this.body));
|
|
259
|
+
this.res = null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|