@yuants/app-virtual-exchange 0.11.2 → 0.11.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.
- package/dist/quote/implementations/v1.js +7 -2
- package/dist/quote/implementations/v1.js.map +1 -1
- package/dist/quote/scheduler.js +244 -0
- package/dist/quote/scheduler.js.map +1 -0
- package/dist/quote/service.js +4 -67
- package/dist/quote/service.js.map +1 -1
- package/dist/quote/state.js +1 -0
- package/dist/quote/state.js.map +1 -1
- package/lib/quote/implementations/v1.d.ts.map +1 -1
- package/lib/quote/implementations/v1.js +7 -2
- package/lib/quote/implementations/v1.js.map +1 -1
- package/lib/quote/scheduler.d.ts +21 -0
- package/lib/quote/scheduler.d.ts.map +1 -0
- package/lib/quote/scheduler.js +248 -0
- package/lib/quote/scheduler.js.map +1 -0
- package/lib/quote/service.js +7 -70
- package/lib/quote/service.js.map +1 -1
- package/lib/quote/state.d.ts +1 -0
- package/lib/quote/state.d.ts.map +1 -1
- package/lib/quote/state.js +2 -1
- package/lib/quote/state.js.map +1 -1
- package/package.json +2 -2
- package/temp/package-deps.json +8 -12
- package/dist/quote/upstream/executor.js +0 -98
- package/dist/quote/upstream/executor.js.map +0 -1
- package/dist/quote/upstream/index.js +0 -2
- package/dist/quote/upstream/index.js.map +0 -1
- package/dist/quote/upstream/prefix-matcher.js +0 -7
- package/dist/quote/upstream/prefix-matcher.js.map +0 -1
- package/dist/quote/upstream/registry.js +0 -116
- package/dist/quote/upstream/registry.js.map +0 -1
- package/dist/quote/upstream/router.js +0 -119
- package/dist/quote/upstream/router.js.map +0 -1
- package/lib/quote/upstream/executor.d.ts +0 -8
- package/lib/quote/upstream/executor.d.ts.map +0 -1
- package/lib/quote/upstream/executor.js +0 -102
- package/lib/quote/upstream/executor.js.map +0 -1
- package/lib/quote/upstream/index.d.ts +0 -3
- package/lib/quote/upstream/index.d.ts.map +0 -1
- package/lib/quote/upstream/index.js +0 -6
- package/lib/quote/upstream/index.js.map +0 -1
- package/lib/quote/upstream/prefix-matcher.d.ts +0 -8
- package/lib/quote/upstream/prefix-matcher.d.ts.map +0 -1
- package/lib/quote/upstream/prefix-matcher.js +0 -11
- package/lib/quote/upstream/prefix-matcher.js.map +0 -1
- package/lib/quote/upstream/registry.d.ts +0 -18
- package/lib/quote/upstream/registry.d.ts.map +0 -1
- package/lib/quote/upstream/registry.js +0 -120
- package/lib/quote/upstream/registry.js.map +0 -1
- package/lib/quote/upstream/router.d.ts +0 -27
- package/lib/quote/upstream/router.d.ts.map +0 -1
- package/lib/quote/upstream/router.js +0 -124
- package/lib/quote/upstream/router.js.map +0 -1
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.markDirty = exports.cells = void 0;
|
|
4
|
+
const exchange_1 = require("@yuants/exchange");
|
|
5
|
+
const protocol_1 = require("@yuants/protocol");
|
|
6
|
+
const utils_1 = require("@yuants/utils");
|
|
7
|
+
const rxjs_1 = require("rxjs");
|
|
8
|
+
const state_1 = require("./state");
|
|
9
|
+
const createFifoQueue = () => ({ items: [], head: 0 });
|
|
10
|
+
const fifoEnqueue = (queue, value) => {
|
|
11
|
+
queue.items.push(value);
|
|
12
|
+
};
|
|
13
|
+
const fifoDequeue = (queue) => {
|
|
14
|
+
if (queue.head >= queue.items.length)
|
|
15
|
+
return undefined;
|
|
16
|
+
const value = queue.items[queue.head++];
|
|
17
|
+
if (queue.head > 1024 && queue.head * 2 > queue.items.length) {
|
|
18
|
+
queue.items = queue.items.slice(queue.head);
|
|
19
|
+
queue.head = 0;
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
const fifoSize = (queue) => queue.items.length - queue.head;
|
|
24
|
+
const isTraceEnabled = process.env.VEX_QUOTE_UPSTREAM_REFINE_TRACE === '1';
|
|
25
|
+
// Query -> Make Cell Dirty -> Trigger Dirty Check
|
|
26
|
+
// Dirty Check():
|
|
27
|
+
// Acquire dirtyCheck lock
|
|
28
|
+
// Foreach Dirty cell in parallel:
|
|
29
|
+
//. Routing the dirty cell to ServiceID
|
|
30
|
+
//. Acquire Service Lock
|
|
31
|
+
//. if success:
|
|
32
|
+
// request the service and wait for response
|
|
33
|
+
//. release service lock
|
|
34
|
+
// clean cells
|
|
35
|
+
//. if failed: do nothing.
|
|
36
|
+
// Release dirtyCheck lock
|
|
37
|
+
// if any cell dirty remaining, call dirtyCheck()
|
|
38
|
+
// (service_id, service_group_id) -> sync func state (hash func)
|
|
39
|
+
const services = [];
|
|
40
|
+
const mapGroupIdToServices = new Map();
|
|
41
|
+
// (product_id, field, service_group_id, is_dirty = true, is_fetching)
|
|
42
|
+
// use-case 1: find unique product_ids (not is_fetching) by service_group_id
|
|
43
|
+
// use-case 2: mark
|
|
44
|
+
/**
|
|
45
|
+
* Use cases:
|
|
46
|
+
* - upsert items (when queryQuotes)
|
|
47
|
+
* - find unique product_ids (is_dirty = true and is_fetching = false) by service_group_id
|
|
48
|
+
* - when service fired, update is_fetching = true for some items (product_id, field)
|
|
49
|
+
* - when service failed, update is_fetching = false for some items (product_id, field)
|
|
50
|
+
* - when service success, update is_fetching = false and is_dirty = false for some items (product_id, field)
|
|
51
|
+
*/
|
|
52
|
+
exports.cells = [];
|
|
53
|
+
const mapCellKeyToCell = new Map();
|
|
54
|
+
const mapGroupIdToState = new Map();
|
|
55
|
+
const getOrCreateGroupState = (group_id) => {
|
|
56
|
+
const existing = mapGroupIdToState.get(group_id);
|
|
57
|
+
if (existing)
|
|
58
|
+
return existing;
|
|
59
|
+
const next = {
|
|
60
|
+
productQueue: createFifoQueue(),
|
|
61
|
+
inQueue: new Set(),
|
|
62
|
+
fetchingProducts: new Set(),
|
|
63
|
+
dirtyFieldsByProduct: new Map(),
|
|
64
|
+
};
|
|
65
|
+
mapGroupIdToState.set(group_id, next);
|
|
66
|
+
return next;
|
|
67
|
+
};
|
|
68
|
+
const route = (product_id, field) => {
|
|
69
|
+
for (const service of services) {
|
|
70
|
+
if (!product_id.startsWith(service.meta.product_id_prefix))
|
|
71
|
+
continue;
|
|
72
|
+
if (!service.metaFieldsSet.has(field))
|
|
73
|
+
continue;
|
|
74
|
+
return service.service_group_id;
|
|
75
|
+
}
|
|
76
|
+
return '';
|
|
77
|
+
};
|
|
78
|
+
const enqueueDirty = (params) => {
|
|
79
|
+
var _a;
|
|
80
|
+
const { product_id, field, service_group_id } = params;
|
|
81
|
+
if (!service_group_id)
|
|
82
|
+
return;
|
|
83
|
+
const groupState = getOrCreateGroupState(service_group_id);
|
|
84
|
+
const dirtyFields = (_a = groupState.dirtyFieldsByProduct.get(product_id)) !== null && _a !== void 0 ? _a : (() => {
|
|
85
|
+
const next = new Set();
|
|
86
|
+
groupState.dirtyFieldsByProduct.set(product_id, next);
|
|
87
|
+
return next;
|
|
88
|
+
})();
|
|
89
|
+
dirtyFields.add(field);
|
|
90
|
+
if (groupState.fetchingProducts.has(product_id))
|
|
91
|
+
return;
|
|
92
|
+
if (groupState.inQueue.has(product_id))
|
|
93
|
+
return;
|
|
94
|
+
groupState.inQueue.add(product_id);
|
|
95
|
+
fifoEnqueue(groupState.productQueue, product_id);
|
|
96
|
+
};
|
|
97
|
+
const markDirty = (product_id, field) => {
|
|
98
|
+
const service_group_id = route(product_id, field);
|
|
99
|
+
const cellKey = (0, utils_1.encodePath)(product_id, field);
|
|
100
|
+
const existing = mapCellKeyToCell.get(cellKey);
|
|
101
|
+
if (!existing) {
|
|
102
|
+
const cell = {
|
|
103
|
+
product_id,
|
|
104
|
+
field,
|
|
105
|
+
service_group_id,
|
|
106
|
+
is_dirty: true,
|
|
107
|
+
is_fetching: false,
|
|
108
|
+
round: 0,
|
|
109
|
+
};
|
|
110
|
+
mapCellKeyToCell.set(cellKey, cell);
|
|
111
|
+
exports.cells.push(cell);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
existing.service_group_id = service_group_id;
|
|
115
|
+
existing.is_dirty = true;
|
|
116
|
+
}
|
|
117
|
+
enqueueDirty({ product_id, field, service_group_id });
|
|
118
|
+
scheduleServiceGroupId(service_group_id);
|
|
119
|
+
};
|
|
120
|
+
exports.markDirty = markDirty;
|
|
121
|
+
const isServiceIdRunning = new Set();
|
|
122
|
+
const scheduleServiceGroupId = (serviceGroupId) => {
|
|
123
|
+
if (!serviceGroupId)
|
|
124
|
+
return;
|
|
125
|
+
const serviceList = mapGroupIdToServices.get(serviceGroupId);
|
|
126
|
+
if (!serviceList || serviceList.length === 0)
|
|
127
|
+
return;
|
|
128
|
+
for (const service of serviceList) {
|
|
129
|
+
if (isServiceIdRunning.has(service.service_id))
|
|
130
|
+
continue;
|
|
131
|
+
isServiceIdRunning.add(service.service_id);
|
|
132
|
+
handleService(service).finally(() => {
|
|
133
|
+
isServiceIdRunning.delete(service.service_id);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
const handleService = async (service) => {
|
|
138
|
+
var _a;
|
|
139
|
+
const groupState = mapGroupIdToState.get(service.service_group_id);
|
|
140
|
+
if (!groupState)
|
|
141
|
+
return;
|
|
142
|
+
while (true) {
|
|
143
|
+
const maxProducts = (_a = service.meta.max_products_per_request) !== null && _a !== void 0 ? _a : Infinity;
|
|
144
|
+
const fetchedFieldsByProduct = new Map();
|
|
145
|
+
const productsToFetch = [];
|
|
146
|
+
const cellsToFetch = [];
|
|
147
|
+
while (productsToFetch.length < maxProducts && fifoSize(groupState.productQueue) > 0) {
|
|
148
|
+
const product_id = fifoDequeue(groupState.productQueue);
|
|
149
|
+
if (!product_id)
|
|
150
|
+
break;
|
|
151
|
+
groupState.inQueue.delete(product_id);
|
|
152
|
+
if (groupState.fetchingProducts.has(product_id))
|
|
153
|
+
continue;
|
|
154
|
+
const dirtyFields = groupState.dirtyFieldsByProduct.get(product_id);
|
|
155
|
+
if (!dirtyFields || dirtyFields.size === 0)
|
|
156
|
+
continue;
|
|
157
|
+
const matchedFields = [];
|
|
158
|
+
for (const f of dirtyFields) {
|
|
159
|
+
if (!service.metaFieldsSet.has(f))
|
|
160
|
+
continue;
|
|
161
|
+
const cell = mapCellKeyToCell.get((0, utils_1.encodePath)(product_id, f));
|
|
162
|
+
if (!cell)
|
|
163
|
+
continue;
|
|
164
|
+
if (cell.service_group_id !== service.service_group_id)
|
|
165
|
+
continue;
|
|
166
|
+
if (!cell.is_dirty)
|
|
167
|
+
continue;
|
|
168
|
+
if (cell.is_fetching)
|
|
169
|
+
continue;
|
|
170
|
+
matchedFields.push(f);
|
|
171
|
+
cellsToFetch.push(cell);
|
|
172
|
+
}
|
|
173
|
+
if (matchedFields.length === 0)
|
|
174
|
+
continue;
|
|
175
|
+
productsToFetch.push(product_id);
|
|
176
|
+
groupState.fetchingProducts.add(product_id);
|
|
177
|
+
fetchedFieldsByProduct.set(product_id, new Set(matchedFields));
|
|
178
|
+
}
|
|
179
|
+
if (productsToFetch.length === 0)
|
|
180
|
+
return;
|
|
181
|
+
for (const cell of cellsToFetch) {
|
|
182
|
+
cell.round++;
|
|
183
|
+
cell.is_fetching = true;
|
|
184
|
+
}
|
|
185
|
+
const res = await makeRequest(service, productsToFetch).catch(() => ({}));
|
|
186
|
+
state_1.quoteState.update(res);
|
|
187
|
+
for (const cell of cellsToFetch) {
|
|
188
|
+
cell.is_fetching = false;
|
|
189
|
+
cell.is_dirty = false;
|
|
190
|
+
}
|
|
191
|
+
for (const product_id of productsToFetch) {
|
|
192
|
+
groupState.fetchingProducts.delete(product_id);
|
|
193
|
+
const dirtyFields = groupState.dirtyFieldsByProduct.get(product_id);
|
|
194
|
+
const fetchedFields = fetchedFieldsByProduct.get(product_id);
|
|
195
|
+
if (!dirtyFields || !fetchedFields)
|
|
196
|
+
continue;
|
|
197
|
+
for (const f of fetchedFields) {
|
|
198
|
+
dirtyFields.delete(f);
|
|
199
|
+
}
|
|
200
|
+
if (dirtyFields.size === 0) {
|
|
201
|
+
groupState.dirtyFieldsByProduct.delete(product_id);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!groupState.inQueue.has(product_id)) {
|
|
205
|
+
groupState.inQueue.add(product_id);
|
|
206
|
+
fifoEnqueue(groupState.productQueue, product_id);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
const terminal = protocol_1.Terminal.fromNodeEnv();
|
|
212
|
+
const makeRequest = async (service, product_ids) => {
|
|
213
|
+
const res = await terminal.client.requestForResponseData('GetQuotes', {
|
|
214
|
+
product_ids,
|
|
215
|
+
fields: service.meta.fields,
|
|
216
|
+
});
|
|
217
|
+
if (isTraceEnabled) {
|
|
218
|
+
console.info((0, utils_1.formatTime)(Date.now()), `[VEX][Quote][Refine]GetQuotes`, `service_id=${service.service_id}`, `group=${service.service_group_id}`, `products=${product_ids.length}`);
|
|
219
|
+
}
|
|
220
|
+
return res;
|
|
221
|
+
};
|
|
222
|
+
terminal.terminalInfos$
|
|
223
|
+
.pipe((0, rxjs_1.mergeMap)((terminalInfos) => (0, rxjs_1.from)(terminalInfos).pipe((0, rxjs_1.mergeMap)((terminalInfo) => (0, rxjs_1.from)(Object.values(terminalInfo.serviceInfo || {})).pipe((0, rxjs_1.filter)((serviceInfo) => serviceInfo.method === 'GetQuotes'), (0, rxjs_1.map)((serviceInfo) => {
|
|
224
|
+
var _a;
|
|
225
|
+
try {
|
|
226
|
+
const meta = (0, exchange_1.parseQuoteServiceMetadataFromSchema)(serviceInfo.schema);
|
|
227
|
+
const service_group_id = (0, utils_1.encodePath)(meta.product_id_prefix, meta.fields.join(','), (_a = meta.max_products_per_request) !== null && _a !== void 0 ? _a : '');
|
|
228
|
+
return {
|
|
229
|
+
service_id: serviceInfo.service_id,
|
|
230
|
+
service_group_id,
|
|
231
|
+
meta,
|
|
232
|
+
metaFieldsSet: new Set(meta.fields),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
catch (_b) { }
|
|
236
|
+
}), (0, rxjs_1.filter)((x) => !!x))), (0, rxjs_1.toArray)(), (0, rxjs_1.tap)((x) => {
|
|
237
|
+
services.length = 0;
|
|
238
|
+
mapGroupIdToServices.clear();
|
|
239
|
+
x.forEach((service) => {
|
|
240
|
+
var _a;
|
|
241
|
+
services.push(service);
|
|
242
|
+
const list = (_a = mapGroupIdToServices.get(service.service_group_id)) !== null && _a !== void 0 ? _a : [];
|
|
243
|
+
list.push(service);
|
|
244
|
+
mapGroupIdToServices.set(service.service_group_id, list);
|
|
245
|
+
});
|
|
246
|
+
}))))
|
|
247
|
+
.subscribe();
|
|
248
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/quote/scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["import { IQuoteUpdateAction } from '@yuants/data-quote';\nimport { IQuoteField, IQuoteServiceMetadata, parseQuoteServiceMetadataFromSchema } from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { encodePath, formatTime } from '@yuants/utils';\nimport { filter, from, map, mergeMap, tap, toArray } from 'rxjs';\nimport { quoteState } from './state';\n\ninterface IQuoteService {\n service_id: string;\n service_group_id: string;\n meta: IQuoteServiceMetadata;\n metaFieldsSet: Set<IQuoteField>;\n}\n\ninterface ICellState {\n product_id: string;\n field: IQuoteField;\n service_group_id: string;\n is_dirty: boolean;\n is_fetching: boolean;\n round: number;\n}\n\ntype IFifoQueue<T> = {\n items: T[];\n head: number;\n};\n\nconst createFifoQueue = <T>(): IFifoQueue<T> => ({ items: [], head: 0 });\n\nconst fifoEnqueue = <T>(queue: IFifoQueue<T>, value: T) => {\n queue.items.push(value);\n};\n\nconst fifoDequeue = <T>(queue: IFifoQueue<T>): T | undefined => {\n if (queue.head >= queue.items.length) return undefined;\n const value = queue.items[queue.head++];\n if (queue.head > 1024 && queue.head * 2 > queue.items.length) {\n queue.items = queue.items.slice(queue.head);\n queue.head = 0;\n }\n return value;\n};\n\nconst fifoSize = <T>(queue: IFifoQueue<T>): number => queue.items.length - queue.head;\n\nconst isTraceEnabled = process.env.VEX_QUOTE_UPSTREAM_REFINE_TRACE === '1';\n\n// Query -> Make Cell Dirty -> Trigger Dirty Check\n\n// Dirty Check():\n// Acquire dirtyCheck lock\n// Foreach Dirty cell in parallel:\n//. Routing the dirty cell to ServiceID\n//. Acquire Service Lock\n//. if success:\n// request the service and wait for response\n//. release service lock\n// clean cells\n//. if failed: do nothing.\n// Release dirtyCheck lock\n// if any cell dirty remaining, call dirtyCheck()\n\n// (service_id, service_group_id) -> sync func state (hash func)\nconst services: Array<IQuoteService> = [];\nconst mapGroupIdToServices = new Map<string, IQuoteService[]>();\n\n// (product_id, field, service_group_id, is_dirty = true, is_fetching)\n// use-case 1: find unique product_ids (not is_fetching) by service_group_id\n// use-case 2: mark\n/**\n * Use cases:\n * - upsert items (when queryQuotes)\n * - find unique product_ids (is_dirty = true and is_fetching = false) by service_group_id\n * - when service fired, update is_fetching = true for some items (product_id, field)\n * - when service failed, update is_fetching = false for some items (product_id, field)\n * - when service success, update is_fetching = false and is_dirty = false for some items (product_id, field)\n */\nexport const cells: Array<ICellState> = [];\nconst mapCellKeyToCell = new Map<string, ICellState>();\n\ntype IGroupState = {\n productQueue: IFifoQueue<string>;\n inQueue: Set<string>;\n fetchingProducts: Set<string>;\n dirtyFieldsByProduct: Map<string, Set<IQuoteField>>;\n};\n\nconst mapGroupIdToState = new Map<string, IGroupState>();\n\nconst getOrCreateGroupState = (group_id: string): IGroupState => {\n const existing = mapGroupIdToState.get(group_id);\n if (existing) return existing;\n const next: IGroupState = {\n productQueue: createFifoQueue(),\n inQueue: new Set<string>(),\n fetchingProducts: new Set<string>(),\n dirtyFieldsByProduct: new Map<string, Set<IQuoteField>>(),\n };\n mapGroupIdToState.set(group_id, next);\n return next;\n};\n\nconst route = (product_id: string, field: IQuoteField) => {\n for (const service of services) {\n if (!product_id.startsWith(service.meta.product_id_prefix)) continue;\n if (!service.metaFieldsSet.has(field)) continue;\n return service.service_group_id;\n }\n return '';\n};\n\nconst enqueueDirty = (params: { product_id: string; field: IQuoteField; service_group_id: string }) => {\n const { product_id, field, service_group_id } = params;\n if (!service_group_id) return;\n const groupState = getOrCreateGroupState(service_group_id);\n const dirtyFields =\n groupState.dirtyFieldsByProduct.get(product_id) ??\n (() => {\n const next = new Set<IQuoteField>();\n groupState.dirtyFieldsByProduct.set(product_id, next);\n return next;\n })();\n dirtyFields.add(field);\n\n if (groupState.fetchingProducts.has(product_id)) return;\n if (groupState.inQueue.has(product_id)) return;\n groupState.inQueue.add(product_id);\n fifoEnqueue(groupState.productQueue, product_id);\n};\n\nexport const markDirty = (product_id: string, field: IQuoteField) => {\n const service_group_id = route(product_id, field);\n\n const cellKey = encodePath(product_id, field);\n const existing = mapCellKeyToCell.get(cellKey);\n if (!existing) {\n const cell: ICellState = {\n product_id,\n field,\n service_group_id,\n is_dirty: true,\n is_fetching: false,\n round: 0,\n };\n mapCellKeyToCell.set(cellKey, cell);\n cells.push(cell);\n } else {\n existing.service_group_id = service_group_id;\n existing.is_dirty = true;\n }\n\n enqueueDirty({ product_id, field, service_group_id });\n scheduleServiceGroupId(service_group_id);\n};\n\nconst isServiceIdRunning = new Set<string>();\n\nconst scheduleServiceGroupId = (serviceGroupId: string): void => {\n if (!serviceGroupId) return;\n const serviceList = mapGroupIdToServices.get(serviceGroupId);\n if (!serviceList || serviceList.length === 0) return;\n\n for (const service of serviceList) {\n if (isServiceIdRunning.has(service.service_id)) continue;\n isServiceIdRunning.add(service.service_id);\n handleService(service).finally(() => {\n isServiceIdRunning.delete(service.service_id);\n });\n }\n};\n\nconst handleService = async (service: IQuoteService) => {\n const groupState = mapGroupIdToState.get(service.service_group_id);\n if (!groupState) return;\n\n while (true) {\n const maxProducts = service.meta.max_products_per_request ?? Infinity;\n const fetchedFieldsByProduct = new Map<string, Set<IQuoteField>>();\n const productsToFetch: string[] = [];\n const cellsToFetch: ICellState[] = [];\n\n while (productsToFetch.length < maxProducts && fifoSize(groupState.productQueue) > 0) {\n const product_id = fifoDequeue(groupState.productQueue);\n if (!product_id) break;\n groupState.inQueue.delete(product_id);\n\n if (groupState.fetchingProducts.has(product_id)) continue;\n const dirtyFields = groupState.dirtyFieldsByProduct.get(product_id);\n if (!dirtyFields || dirtyFields.size === 0) continue;\n\n const matchedFields: IQuoteField[] = [];\n for (const f of dirtyFields) {\n if (!service.metaFieldsSet.has(f)) continue;\n const cell = mapCellKeyToCell.get(encodePath(product_id, f));\n if (!cell) continue;\n if (cell.service_group_id !== service.service_group_id) continue;\n if (!cell.is_dirty) continue;\n if (cell.is_fetching) continue;\n matchedFields.push(f);\n cellsToFetch.push(cell);\n }\n\n if (matchedFields.length === 0) continue;\n productsToFetch.push(product_id);\n groupState.fetchingProducts.add(product_id);\n fetchedFieldsByProduct.set(product_id, new Set(matchedFields));\n }\n\n if (productsToFetch.length === 0) return;\n\n for (const cell of cellsToFetch) {\n cell.round++;\n cell.is_fetching = true;\n }\n\n const res = await makeRequest(service, productsToFetch).catch(() => ({} as IQuoteUpdateAction));\n quoteState.update(res);\n\n for (const cell of cellsToFetch) {\n cell.is_fetching = false;\n cell.is_dirty = false;\n }\n\n for (const product_id of productsToFetch) {\n groupState.fetchingProducts.delete(product_id);\n const dirtyFields = groupState.dirtyFieldsByProduct.get(product_id);\n const fetchedFields = fetchedFieldsByProduct.get(product_id);\n if (!dirtyFields || !fetchedFields) continue;\n for (const f of fetchedFields) {\n dirtyFields.delete(f);\n }\n if (dirtyFields.size === 0) {\n groupState.dirtyFieldsByProduct.delete(product_id);\n continue;\n }\n if (!groupState.inQueue.has(product_id)) {\n groupState.inQueue.add(product_id);\n fifoEnqueue(groupState.productQueue, product_id);\n }\n }\n }\n};\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst makeRequest = async (service: IQuoteService, product_ids: string[]): Promise<IQuoteUpdateAction> => {\n const res = await terminal.client.requestForResponseData<{}, IQuoteUpdateAction>('GetQuotes', {\n product_ids,\n fields: service.meta.fields,\n });\n if (isTraceEnabled) {\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote][Refine]GetQuotes`,\n `service_id=${service.service_id}`,\n `group=${service.service_group_id}`,\n `products=${product_ids.length}`,\n );\n }\n return res;\n};\n\nterminal.terminalInfos$\n .pipe(\n mergeMap((terminalInfos) =>\n from(terminalInfos).pipe(\n mergeMap((terminalInfo) =>\n from(Object.values(terminalInfo.serviceInfo || {})).pipe(\n filter((serviceInfo) => serviceInfo.method === 'GetQuotes'),\n map((serviceInfo): IQuoteService | undefined => {\n try {\n const meta = parseQuoteServiceMetadataFromSchema(serviceInfo.schema);\n const service_group_id = encodePath(\n meta.product_id_prefix,\n (meta.fields as any[]).join(','),\n meta.max_products_per_request ?? '',\n );\n return {\n service_id: serviceInfo.service_id,\n service_group_id,\n meta,\n metaFieldsSet: new Set(meta.fields),\n };\n } catch {}\n }),\n filter((x): x is Exclude<typeof x, undefined> => !!x),\n ),\n ),\n toArray(),\n tap((x) => {\n services.length = 0;\n mapGroupIdToServices.clear();\n x.forEach((service) => {\n services.push(service);\n const list = mapGroupIdToServices.get(service.service_group_id) ?? [];\n list.push(service);\n mapGroupIdToServices.set(service.service_group_id, list);\n });\n }),\n ),\n ),\n )\n .subscribe();\n"]}
|
package/lib/quote/service.js
CHANGED
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const protocol_1 = require("@yuants/protocol");
|
|
4
|
-
const utils_1 = require("@yuants/utils");
|
|
5
|
-
const rxjs_1 = require("rxjs");
|
|
6
4
|
const state_1 = require("./state");
|
|
7
|
-
const
|
|
5
|
+
const scheduler_1 = require("./scheduler");
|
|
8
6
|
const terminal = protocol_1.Terminal.fromNodeEnv();
|
|
9
|
-
const quoteState = (0, state_1.createQuoteState)();
|
|
10
|
-
const quoteProviderRegistry = (0, upstream_1.createQuoteProviderRegistry)(terminal);
|
|
11
7
|
const normalizeStrings = (values) => [...new Set(values)].sort();
|
|
12
8
|
const normalizeFields = (values) => [...new Set(values)].sort();
|
|
13
9
|
const analyzeRequestedQuotes = (quoteState, product_ids, fields, updated_at) => {
|
|
@@ -26,66 +22,12 @@ const analyzeRequestedQuotes = (quoteState, product_ids, fields, updated_at) =>
|
|
|
26
22
|
}
|
|
27
23
|
return { needUpdate };
|
|
28
24
|
};
|
|
29
|
-
const updateQueue$ = new rxjs_1.Subject();
|
|
30
|
-
const updateQueueStats = {
|
|
31
|
-
queued_total: 0,
|
|
32
|
-
started_total: 0,
|
|
33
|
-
processed_total: 0,
|
|
34
|
-
};
|
|
35
|
-
const getQueueStatus = () => {
|
|
36
|
-
const pending = updateQueueStats.queued_total - updateQueueStats.started_total;
|
|
37
|
-
const in_flight = updateQueueStats.started_total - updateQueueStats.processed_total;
|
|
38
|
-
return Object.assign({ pending,
|
|
39
|
-
in_flight }, updateQueueStats);
|
|
40
|
-
};
|
|
41
|
-
const enqueueUpdateTask = (task) => {
|
|
42
|
-
updateQueueStats.queued_total++;
|
|
43
|
-
updateQueueStats.last_enqueued_at = Date.now();
|
|
44
|
-
updateQueue$.next(task);
|
|
45
|
-
};
|
|
46
|
-
const summarizeError = (error) => {
|
|
47
|
-
if (typeof error === 'object' && error !== null) {
|
|
48
|
-
const code = 'code' in error ? error.code : undefined;
|
|
49
|
-
const message = 'message' in error ? error.message : undefined;
|
|
50
|
-
return {
|
|
51
|
-
code: typeof code === 'string' ? code : undefined,
|
|
52
|
-
message: typeof message === 'string' ? message : undefined,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
return {};
|
|
56
|
-
};
|
|
57
|
-
const processUpdateTask = async (task) => {
|
|
58
|
-
const { needUpdate } = analyzeRequestedQuotes(quoteState, task.product_ids, task.fields, task.updated_at);
|
|
59
|
-
await quoteProviderRegistry.fillQuoteStateFromUpstream({
|
|
60
|
-
quoteState,
|
|
61
|
-
cacheMissed: needUpdate,
|
|
62
|
-
updated_at: task.updated_at,
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
|
-
updateQueue$
|
|
66
|
-
.pipe((0, rxjs_1.concatMap)((task) => (0, rxjs_1.defer)(async () => {
|
|
67
|
-
updateQueueStats.started_total++;
|
|
68
|
-
updateQueueStats.last_started_at = Date.now();
|
|
69
|
-
try {
|
|
70
|
-
await processUpdateTask(task);
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
const summary = summarizeError(error);
|
|
74
|
-
updateQueueStats.last_error = Object.assign({ at: Date.now() }, summary);
|
|
75
|
-
console.info((0, utils_1.formatTime)(Date.now()), `[VEX][Quote]UpdateQueueTaskFailed`, `product_ids=${task.product_ids.length} fields=${task.fields.length} updated_at=${task.updated_at}`, JSON.stringify(summary));
|
|
76
|
-
}
|
|
77
|
-
finally {
|
|
78
|
-
updateQueueStats.processed_total++;
|
|
79
|
-
updateQueueStats.last_processed_at = Date.now();
|
|
80
|
-
}
|
|
81
|
-
})))
|
|
82
|
-
.subscribe();
|
|
83
25
|
terminal.server.provideService('VEX/UpdateQuotes', {}, async (msg) => {
|
|
84
|
-
quoteState.update(msg.req);
|
|
26
|
+
state_1.quoteState.update(msg.req);
|
|
85
27
|
return { res: { code: 0, message: 'OK' } };
|
|
86
28
|
});
|
|
87
29
|
terminal.server.provideService('VEX/DumpQuoteState', {}, async () => {
|
|
88
|
-
return { res: { code: 0, message: 'OK', data: quoteState.dumpAsObject() } };
|
|
30
|
+
return { res: { code: 0, message: 'OK', data: state_1.quoteState.dumpAsObject() } };
|
|
89
31
|
});
|
|
90
32
|
terminal.server.provideService('VEX/QueryQuotes', {
|
|
91
33
|
type: 'object',
|
|
@@ -105,16 +47,11 @@ terminal.server.provideService('VEX/QueryQuotes', {
|
|
|
105
47
|
const product_ids = normalizeStrings(msg.req.product_ids);
|
|
106
48
|
const fields = normalizeFields(msg.req.fields);
|
|
107
49
|
const { updated_at } = msg.req;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (needUpdate.length > 0) {
|
|
112
|
-
enqueueUpdateTask({ product_ids, fields, updated_at });
|
|
50
|
+
const { needUpdate } = analyzeRequestedQuotes(state_1.quoteState, product_ids, fields, updated_at);
|
|
51
|
+
for (const { product_id, field } of needUpdate) {
|
|
52
|
+
(0, scheduler_1.markDirty)(product_id, field);
|
|
113
53
|
}
|
|
114
|
-
const data = quoteState.filterValues(product_ids, fields);
|
|
54
|
+
const data = state_1.quoteState.filterValues(product_ids, fields);
|
|
115
55
|
return { res: { code: 0, message: 'OK', data } };
|
|
116
56
|
});
|
|
117
|
-
terminal.server.provideService('VEX/QuoteUpdateQueueStatus', {}, async () => {
|
|
118
|
-
return { res: { code: 0, message: 'OK', data: getQueueStatus() } };
|
|
119
|
-
});
|
|
120
57
|
//# sourceMappingURL=service.js.map
|
package/lib/quote/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/quote/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { formatTime } from '@yuants/utils';\nimport { Subject, concatMap, defer } from 'rxjs';\nimport { createQuoteState } from './state';\nimport { IQuoteKey, IQuoteRequire, IQuoteState, IQuoteUpdateAction } from './types';\nimport { createQuoteProviderRegistry } from './upstream';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst quoteState = createQuoteState();\nconst quoteProviderRegistry = createQuoteProviderRegistry(terminal);\n\ntype UpdateTask = { product_ids: string[]; fields: IQuoteKey[]; updated_at: number };\n\ntype IQuoteUpdateQueueStatus = {\n pending: number;\n in_flight: number;\n queued_total: number;\n started_total: number;\n processed_total: number;\n last_enqueued_at?: number;\n last_started_at?: number;\n last_processed_at?: number;\n last_error?: { at: number; code?: string; message?: string };\n};\n\nconst normalizeStrings = (values: string[]) => [...new Set(values)].sort();\nconst normalizeFields = (values: IQuoteKey[]) => [...new Set(values)].sort();\n\nconst analyzeRequestedQuotes = (\n quoteState: IQuoteState,\n product_ids: string[],\n fields: IQuoteKey[],\n updated_at: number,\n): { needUpdate: IQuoteRequire[] } => {\n const needUpdate: IQuoteRequire[] = [];\n for (const product_id of product_ids) {\n for (const field of fields) {\n const tuple = quoteState.getValueTuple(product_id, field);\n if (tuple === undefined) {\n needUpdate.push({ product_id, field });\n continue;\n }\n if (tuple[1] < updated_at) {\n needUpdate.push({ product_id, field });\n }\n }\n }\n return { needUpdate };\n};\n\nconst updateQueue$ = new Subject<UpdateTask>();\nconst updateQueueStats: Omit<IQuoteUpdateQueueStatus, 'pending' | 'in_flight'> = {\n queued_total: 0,\n started_total: 0,\n processed_total: 0,\n};\n\nconst getQueueStatus = (): IQuoteUpdateQueueStatus => {\n const pending = updateQueueStats.queued_total - updateQueueStats.started_total;\n const in_flight = updateQueueStats.started_total - updateQueueStats.processed_total;\n return {\n pending,\n in_flight,\n ...updateQueueStats,\n };\n};\n\nconst enqueueUpdateTask = (task: UpdateTask) => {\n updateQueueStats.queued_total++;\n updateQueueStats.last_enqueued_at = Date.now();\n updateQueue$.next(task);\n};\n\nconst summarizeError = (error: unknown): { code?: string; message?: string } => {\n if (typeof error === 'object' && error !== null) {\n const code = 'code' in error ? (error as any).code : undefined;\n const message = 'message' in error ? (error as any).message : undefined;\n return {\n code: typeof code === 'string' ? code : undefined,\n message: typeof message === 'string' ? message : undefined,\n };\n }\n return {};\n};\n\nconst processUpdateTask = async (task: UpdateTask) => {\n const { needUpdate } = analyzeRequestedQuotes(quoteState, task.product_ids, task.fields, task.updated_at);\n await quoteProviderRegistry.fillQuoteStateFromUpstream({\n quoteState,\n cacheMissed: needUpdate,\n updated_at: task.updated_at,\n });\n};\n\nupdateQueue$\n .pipe(\n concatMap((task) =>\n defer(async () => {\n updateQueueStats.started_total++;\n updateQueueStats.last_started_at = Date.now();\n\n try {\n await processUpdateTask(task);\n } catch (error) {\n const summary = summarizeError(error);\n updateQueueStats.last_error = { at: Date.now(), ...summary };\n console.info(\n formatTime(Date.now()),\n `[VEX][Quote]UpdateQueueTaskFailed`,\n `product_ids=${task.product_ids.length} fields=${task.fields.length} updated_at=${task.updated_at}`,\n JSON.stringify(summary),\n );\n } finally {\n updateQueueStats.processed_total++;\n updateQueueStats.last_processed_at = Date.now();\n }\n }),\n ),\n )\n .subscribe();\n\nterminal.server.provideService<IQuoteUpdateAction>('VEX/UpdateQuotes', {}, async (msg) => {\n quoteState.update(msg.req);\n return { res: { code: 0, message: 'OK' } };\n});\n\nterminal.server.provideService<{}, IQuoteUpdateAction>('VEX/DumpQuoteState', {}, async () => {\n return { res: { code: 0, message: 'OK', data: quoteState.dumpAsObject() } };\n});\n\nterminal.server.provideService<\n { product_ids: string[]; fields: IQuoteKey[]; updated_at: number },\n Record<string, Partial<Record<IQuoteKey, string>>>\n>(\n 'VEX/QueryQuotes',\n {\n type: 'object',\n required: ['product_ids', 'fields', 'updated_at'],\n properties: {\n product_ids: {\n type: 'array',\n items: { type: 'string' },\n },\n fields: {\n type: 'array',\n items: { type: 'string' },\n },\n updated_at: { type: 'number' },\n },\n },\n async (msg) => {\n const product_ids = normalizeStrings(msg.req.product_ids);\n const fields = normalizeFields(msg.req.fields);\n const { updated_at } = msg.req;\n\n // SWR strategy: if we have stale or missing data, enqueue an update task,\n // but still return the current data immediately.\n const { needUpdate } = analyzeRequestedQuotes(quoteState, product_ids, fields, updated_at);\n if (needUpdate.length > 0) {\n enqueueUpdateTask({ product_ids, fields, updated_at });\n }\n\n const data = quoteState.filterValues(product_ids, fields);\n return { res: { code: 0, message: 'OK', data } };\n },\n);\n\nterminal.server.provideService<{}, IQuoteUpdateQueueStatus>('VEX/QuoteUpdateQueueStatus', {}, async () => {\n return { res: { code: 0, message: 'OK', data: getQueueStatus() } };\n});\n"]}
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/quote/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Terminal } from '@yuants/protocol';\nimport { IQuoteField } from '@yuants/exchange';\nimport { quoteState } from './state';\nimport { IQuoteKey, IQuoteRequire, IQuoteState, IQuoteUpdateAction } from './types';\nimport { markDirty } from './scheduler';\n\nconst terminal = Terminal.fromNodeEnv();\n\nconst normalizeStrings = (values: string[]) => [...new Set(values)].sort();\nconst normalizeFields = (values: IQuoteKey[]) => [...new Set(values)].sort();\n\nconst analyzeRequestedQuotes = (\n quoteState: IQuoteState,\n product_ids: string[],\n fields: IQuoteKey[],\n updated_at: number,\n): { needUpdate: IQuoteRequire[] } => {\n const needUpdate: IQuoteRequire[] = [];\n for (const product_id of product_ids) {\n for (const field of fields) {\n const tuple = quoteState.getValueTuple(product_id, field);\n if (tuple === undefined) {\n needUpdate.push({ product_id, field });\n continue;\n }\n if (tuple[1] < updated_at) {\n needUpdate.push({ product_id, field });\n }\n }\n }\n return { needUpdate };\n};\n\nterminal.server.provideService<IQuoteUpdateAction>('VEX/UpdateQuotes', {}, async (msg) => {\n quoteState.update(msg.req);\n return { res: { code: 0, message: 'OK' } };\n});\n\nterminal.server.provideService<{}, IQuoteUpdateAction>('VEX/DumpQuoteState', {}, async () => {\n return { res: { code: 0, message: 'OK', data: quoteState.dumpAsObject() } };\n});\n\nterminal.server.provideService<\n { product_ids: string[]; fields: IQuoteKey[]; updated_at: number },\n Record<string, Partial<Record<IQuoteKey, string>>>\n>(\n 'VEX/QueryQuotes',\n {\n type: 'object',\n required: ['product_ids', 'fields', 'updated_at'],\n properties: {\n product_ids: {\n type: 'array',\n items: { type: 'string' },\n },\n fields: {\n type: 'array',\n items: { type: 'string' },\n },\n updated_at: { type: 'number' },\n },\n },\n async (msg) => {\n const product_ids = normalizeStrings(msg.req.product_ids);\n const fields = normalizeFields(msg.req.fields);\n const { updated_at } = msg.req;\n\n const { needUpdate } = analyzeRequestedQuotes(quoteState, product_ids, fields, updated_at);\n for (const { product_id, field } of needUpdate) {\n markDirty(product_id, field as any as IQuoteField);\n }\n\n const data = quoteState.filterValues(product_ids, fields);\n return { res: { code: 0, message: 'OK', data } };\n },\n);\n"]}
|
package/lib/quote/state.d.ts
CHANGED
package/lib/quote/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/quote/state.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,qCAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/quote/state.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB,qCAAqB,CAAC;AAEnD,eAAO,MAAM,UAAU,+BAAqB,CAAC"}
|
package/lib/quote/state.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createQuoteState = void 0;
|
|
3
|
+
exports.quoteState = exports.createQuoteState = void 0;
|
|
4
4
|
const implementations_1 = require("./implementations");
|
|
5
5
|
// 使用 v1 作为生产版本
|
|
6
6
|
exports.createQuoteState = implementations_1.implementations.v1;
|
|
7
|
+
exports.quoteState = (0, exports.createQuoteState)();
|
|
7
8
|
//# sourceMappingURL=state.js.map
|
package/lib/quote/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/quote/state.ts"],"names":[],"mappings":";;;AAAA,uDAAoD;AAEpD,eAAe;AACF,QAAA,gBAAgB,GAAG,iCAAe,CAAC,EAAE,CAAC","sourcesContent":["import { implementations } from './implementations';\n\n// 使用 v1 作为生产版本\nexport const createQuoteState = implementations.v1;\n"]}
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/quote/state.ts"],"names":[],"mappings":";;;AAAA,uDAAoD;AAEpD,eAAe;AACF,QAAA,gBAAgB,GAAG,iCAAe,CAAC,EAAE,CAAC;AAEtC,QAAA,UAAU,GAAG,IAAA,wBAAgB,GAAE,CAAC","sourcesContent":["import { implementations } from './implementations';\n\n// 使用 v1 作为生产版本\nexport const createQuoteState = implementations.v1;\n\nexport const quoteState = createQuoteState();\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yuants/app-virtual-exchange",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@yuants/data-quote": "0.4.0",
|
|
17
17
|
"@yuants/secret": "0.4.1",
|
|
18
18
|
"@yuants/sql": "0.9.31",
|
|
19
|
-
"@yuants/exchange": "0.
|
|
19
|
+
"@yuants/exchange": "0.8.1",
|
|
20
20
|
"@yuants/cache": "0.3.4",
|
|
21
21
|
"rxjs": "~7.5.6",
|
|
22
22
|
"ajv": "~8.12.0"
|
package/temp/package-deps.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"apps/virtual-exchange/CHANGELOG.json": "
|
|
3
|
-
"apps/virtual-exchange/CHANGELOG.md": "
|
|
2
|
+
"apps/virtual-exchange/CHANGELOG.json": "8714dfc565d4cc8331a853f05f2a607862987856",
|
|
3
|
+
"apps/virtual-exchange/CHANGELOG.md": "50dbd467d7c5b389f59d1c33b8a3031c1be6d375",
|
|
4
4
|
"apps/virtual-exchange/api-extractor.json": "62f4fd324425b9a235f0c117975967aab09ced0c",
|
|
5
5
|
"apps/virtual-exchange/config/jest.config.json": "4bb17bde3ee911163a3edb36a6eb71491d80b1bd",
|
|
6
6
|
"apps/virtual-exchange/config/rig.json": "f6c7b5537dc77a3170ba9f008bae3b6c3ee11956",
|
|
7
7
|
"apps/virtual-exchange/config/typescript.json": "854907e8a821f2050f6533368db160c649c25348",
|
|
8
8
|
"apps/virtual-exchange/etc/app-virtual-exchange.api.md": "6cb40ec1fa2d40a31a7b0dd3f02b8b24a4d7c4de",
|
|
9
|
-
"apps/virtual-exchange/package.json": "
|
|
9
|
+
"apps/virtual-exchange/package.json": "c74823754fc6506e84821ad4bbd99daa4eb0ae9e",
|
|
10
10
|
"apps/virtual-exchange/src/credential.ts": "7ff9cfe06e46005b2a69e60b5596eb1f59d42bba",
|
|
11
11
|
"apps/virtual-exchange/src/general.ts": "b3d0cd8c57975b9711008beaa05ad7f6812bd57e",
|
|
12
12
|
"apps/virtual-exchange/src/index.ts": "67e1963facf0ee2aedd871496361cff90bf49356",
|
|
@@ -26,18 +26,14 @@
|
|
|
26
26
|
"apps/virtual-exchange/src/quote/implementations/README.md": "da3afe60c9bc16576cc226d63c233bb2d2fd7c38",
|
|
27
27
|
"apps/virtual-exchange/src/quote/implementations/index.ts": "99d8c99b24e5e7f14293f489916c83a0bac192d7",
|
|
28
28
|
"apps/virtual-exchange/src/quote/implementations/v0.ts": "cbc6aa7b7356b18d83a219241e9070e2aeed1365",
|
|
29
|
-
"apps/virtual-exchange/src/quote/implementations/v1.ts": "
|
|
29
|
+
"apps/virtual-exchange/src/quote/implementations/v1.ts": "4ae86c1d5d8dc39c1c1fc695d6990d859e2c2137",
|
|
30
30
|
"apps/virtual-exchange/src/quote/implementations/v2.ts": "8723cede6cee6092ec0b8ce9b273ee92f47790cf",
|
|
31
31
|
"apps/virtual-exchange/src/quote/implementations/v3.ts": "f5e4f31c5592d84206d0a27ee51107ba9a91a53d",
|
|
32
|
-
"apps/virtual-exchange/src/quote/
|
|
32
|
+
"apps/virtual-exchange/src/quote/scheduler.ts": "d92a25ed63098a5536b7957f4e98bd1c009843c8",
|
|
33
|
+
"apps/virtual-exchange/src/quote/service.ts": "dd32a7af1aa58b388bcfadfe81b605b646a9b0eb",
|
|
33
34
|
"apps/virtual-exchange/src/quote/state.benchmark.ts": "075d3c5bab0ae6f7b61accfe977f7d35233b06ad",
|
|
34
|
-
"apps/virtual-exchange/src/quote/state.ts": "
|
|
35
|
+
"apps/virtual-exchange/src/quote/state.ts": "8690d156db5850cde46c366a884d6fc707dc3b7c",
|
|
35
36
|
"apps/virtual-exchange/src/quote/types.ts": "4894fd3df8b5bb92e7aaacda9136fa3e70d45835",
|
|
36
|
-
"apps/virtual-exchange/src/quote/upstream/executor.ts": "f7acd3ced6114f1a20ad72c746a9dfbf67b495ec",
|
|
37
|
-
"apps/virtual-exchange/src/quote/upstream/index.ts": "10439c0fa997c61fcca1763aae40fddba3daa3da",
|
|
38
|
-
"apps/virtual-exchange/src/quote/upstream/prefix-matcher.ts": "079066de8c1d5d91b63ffc2bf67c2163e7b8a540",
|
|
39
|
-
"apps/virtual-exchange/src/quote/upstream/registry.ts": "c4ce97bd63963063a690422c5edc41fe988b94a6",
|
|
40
|
-
"apps/virtual-exchange/src/quote/upstream/router.ts": "d34853abadaaea73246ec735c8e9b5f2789311c3",
|
|
41
37
|
"apps/virtual-exchange/tsconfig.json": "22f94ca28b507f8ddcc21b9053158eefd3f726a9",
|
|
42
38
|
"apps/virtual-exchange/.rush/temp/shrinkwrap-deps.json": "2c8344167a574161e8a89138bc1eb686bf6339de",
|
|
43
39
|
"libraries/protocol/temp/package-deps.json": "c94931cb0eab7180d90a06d740c1a97c0ac0c6b6",
|
|
@@ -48,7 +44,7 @@
|
|
|
48
44
|
"libraries/data-quote/temp/package-deps.json": "c2f009f818a503342bef588da21c9abd52beea00",
|
|
49
45
|
"libraries/secret/temp/package-deps.json": "360293dbcad7870a579c7c043d7f8f87a1b68c48",
|
|
50
46
|
"libraries/sql/temp/package-deps.json": "041074e0102f9a01820c0fddcf82b9eaee226c79",
|
|
51
|
-
"libraries/exchange/temp/package-deps.json": "
|
|
47
|
+
"libraries/exchange/temp/package-deps.json": "0f0549ddf97292ceadb8c3cbcc8799e31e7a0901",
|
|
52
48
|
"libraries/cache/temp/package-deps.json": "e1f94620bc6245add32a4f9d379ed2901129d818",
|
|
53
49
|
"tools/toolkit/temp/package-deps.json": "23e053490eb8feade23e4d45de4e54883e322711"
|
|
54
50
|
}
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import { newError } from '@yuants/utils';
|
|
2
|
-
import { filter, firstValueFrom, map } from 'rxjs';
|
|
3
|
-
const createConcurrencyLimiter = (concurrency) => {
|
|
4
|
-
const queue = [];
|
|
5
|
-
let active = 0;
|
|
6
|
-
const next = () => {
|
|
7
|
-
if (active >= concurrency)
|
|
8
|
-
return;
|
|
9
|
-
const task = queue.shift();
|
|
10
|
-
if (!task)
|
|
11
|
-
return;
|
|
12
|
-
active++;
|
|
13
|
-
task();
|
|
14
|
-
};
|
|
15
|
-
return async (fn) => await new Promise((resolve, reject) => {
|
|
16
|
-
queue.push(async () => {
|
|
17
|
-
try {
|
|
18
|
-
resolve(await fn());
|
|
19
|
-
}
|
|
20
|
-
catch (e) {
|
|
21
|
-
reject(e);
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
active--;
|
|
25
|
-
next();
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
next();
|
|
29
|
-
});
|
|
30
|
-
};
|
|
31
|
-
const pickInstance = (params) => {
|
|
32
|
-
var _a;
|
|
33
|
-
const { group_id, instances, mapGroupIdToRoundRobinIndex } = params;
|
|
34
|
-
if (instances.length === 0)
|
|
35
|
-
throw newError('VEX_QUOTE_PROVIDER_INSTANCE_EMPTY', { group_id });
|
|
36
|
-
const nextIndex = ((_a = mapGroupIdToRoundRobinIndex.get(group_id)) !== null && _a !== void 0 ? _a : 0) % instances.length;
|
|
37
|
-
mapGroupIdToRoundRobinIndex.set(group_id, nextIndex + 1);
|
|
38
|
-
return instances[nextIndex];
|
|
39
|
-
};
|
|
40
|
-
const requestGetQuotes = async (params) => {
|
|
41
|
-
const { terminal, instance, req } = params;
|
|
42
|
-
const res = await firstValueFrom(terminal.client
|
|
43
|
-
.request('GetQuotes', instance.terminal_id, req, instance.service_id)
|
|
44
|
-
.pipe(map((msg) => msg.res), filter((v) => v !== undefined)));
|
|
45
|
-
if (res.code !== 0)
|
|
46
|
-
throw newError('VEX_QUOTE_PROVIDER_ERROR', { instance, res });
|
|
47
|
-
if (res.data === undefined)
|
|
48
|
-
throw newError('VEX_QUOTE_PROVIDER_DATA_MISSING', { instance, res });
|
|
49
|
-
return res.data;
|
|
50
|
-
};
|
|
51
|
-
const runWithProviderGroupConcurrencyLimit1 = async (params) => {
|
|
52
|
-
var _a;
|
|
53
|
-
const { group_id, mapGroupIdToTailPromise, fn } = params;
|
|
54
|
-
const prev = (_a = mapGroupIdToTailPromise.get(group_id)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
55
|
-
let resolveCurrent = () => { };
|
|
56
|
-
const current = new Promise((resolve) => {
|
|
57
|
-
resolveCurrent = resolve;
|
|
58
|
-
});
|
|
59
|
-
mapGroupIdToTailPromise.set(group_id, prev.then(() => current));
|
|
60
|
-
await prev;
|
|
61
|
-
try {
|
|
62
|
-
return await fn();
|
|
63
|
-
}
|
|
64
|
-
finally {
|
|
65
|
-
resolveCurrent();
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
export const createGetQuotesExecutor = (terminal) => {
|
|
69
|
-
const mapGroupIdToRoundRobinIndex = new Map();
|
|
70
|
-
const mapGroupIdToTailPromise = new Map();
|
|
71
|
-
const limitGetQuotes = createConcurrencyLimiter(32);
|
|
72
|
-
const mapKeyToInFlightGetQuotesPromise = new Map();
|
|
73
|
-
const requestGetQuotesInFlight = (key, planned) => {
|
|
74
|
-
const existing = mapKeyToInFlightGetQuotesPromise.get(key);
|
|
75
|
-
if (existing)
|
|
76
|
-
return existing;
|
|
77
|
-
const promise = limitGetQuotes(() => runWithProviderGroupConcurrencyLimit1({
|
|
78
|
-
group_id: planned.group_id,
|
|
79
|
-
mapGroupIdToTailPromise,
|
|
80
|
-
fn: async () => {
|
|
81
|
-
const instance = pickInstance({
|
|
82
|
-
group_id: planned.group_id,
|
|
83
|
-
instances: planned.instances,
|
|
84
|
-
mapGroupIdToRoundRobinIndex,
|
|
85
|
-
});
|
|
86
|
-
return await requestGetQuotes({ terminal, instance, req: planned.req });
|
|
87
|
-
},
|
|
88
|
-
})).finally(() => {
|
|
89
|
-
mapKeyToInFlightGetQuotesPromise.delete(key);
|
|
90
|
-
});
|
|
91
|
-
mapKeyToInFlightGetQuotesPromise.set(key, promise);
|
|
92
|
-
return promise;
|
|
93
|
-
};
|
|
94
|
-
return {
|
|
95
|
-
execute: async (requests) => await Promise.all(requests.map(async ({ key, planned }) => await requestGetQuotesInFlight(key, planned))),
|
|
96
|
-
};
|
|
97
|
-
};
|
|
98
|
-
//# sourceMappingURL=executor.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/quote/upstream/executor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n IQuoteUpdateAction as IExchangeQuoteUpdateAction,\n IQuoteServiceRequestByVEX,\n} from '@yuants/exchange';\nimport { Terminal } from '@yuants/protocol';\nimport { newError } from '@yuants/utils';\nimport { filter, firstValueFrom, map } from 'rxjs';\nimport { IQuoteProviderInstance } from '../types';\nimport { IPlannedRequestWithKey } from './router';\n\ntype IPlannedRequest = IPlannedRequestWithKey['planned'];\n\nconst createConcurrencyLimiter = (concurrency: number) => {\n const queue: Array<() => void> = [];\n let active = 0;\n const next = () => {\n if (active >= concurrency) return;\n const task = queue.shift();\n if (!task) return;\n active++;\n task();\n };\n return async <T>(fn: () => Promise<T>): Promise<T> =>\n await new Promise<T>((resolve, reject) => {\n queue.push(async () => {\n try {\n resolve(await fn());\n } catch (e) {\n reject(e);\n } finally {\n active--;\n next();\n }\n });\n next();\n });\n};\n\nconst pickInstance = (params: {\n group_id: string;\n instances: IQuoteProviderInstance[];\n mapGroupIdToRoundRobinIndex: Map<string, number>;\n}): IQuoteProviderInstance => {\n const { group_id, instances, mapGroupIdToRoundRobinIndex } = params;\n if (instances.length === 0) throw newError('VEX_QUOTE_PROVIDER_INSTANCE_EMPTY', { group_id });\n const nextIndex = (mapGroupIdToRoundRobinIndex.get(group_id) ?? 0) % instances.length;\n mapGroupIdToRoundRobinIndex.set(group_id, nextIndex + 1);\n return instances[nextIndex];\n};\n\nconst requestGetQuotes = async (params: {\n terminal: Terminal;\n instance: IQuoteProviderInstance;\n req: IQuoteServiceRequestByVEX;\n}): Promise<IExchangeQuoteUpdateAction> => {\n const { terminal, instance, req } = params;\n const res = await firstValueFrom(\n terminal.client\n .request<IQuoteServiceRequestByVEX, IExchangeQuoteUpdateAction>(\n 'GetQuotes',\n instance.terminal_id,\n req,\n instance.service_id,\n )\n .pipe(\n map((msg) => msg.res),\n filter((v): v is Exclude<typeof v, undefined> => v !== undefined),\n ),\n );\n if (res.code !== 0) throw newError('VEX_QUOTE_PROVIDER_ERROR', { instance, res });\n if (res.data === undefined) throw newError('VEX_QUOTE_PROVIDER_DATA_MISSING', { instance, res });\n return res.data as any;\n};\n\nconst runWithProviderGroupConcurrencyLimit1 = async <T>(params: {\n group_id: string;\n mapGroupIdToTailPromise: Map<string, Promise<void>>;\n fn: () => Promise<T>;\n}): Promise<T> => {\n const { group_id, mapGroupIdToTailPromise, fn } = params;\n const prev = mapGroupIdToTailPromise.get(group_id) ?? Promise.resolve();\n let resolveCurrent: () => void = () => {};\n const current = new Promise<void>((resolve) => {\n resolveCurrent = resolve;\n });\n mapGroupIdToTailPromise.set(\n group_id,\n prev.then(() => current),\n );\n await prev;\n try {\n return await fn();\n } finally {\n resolveCurrent();\n }\n};\n\nexport interface IGetQuotesExecutor {\n execute: (requests: IPlannedRequestWithKey[]) => Promise<IExchangeQuoteUpdateAction[]>;\n}\n\nexport const createGetQuotesExecutor = (terminal: Terminal): IGetQuotesExecutor => {\n const mapGroupIdToRoundRobinIndex = new Map<string, number>();\n const mapGroupIdToTailPromise = new Map<string, Promise<void>>();\n const limitGetQuotes = createConcurrencyLimiter(32);\n\n const mapKeyToInFlightGetQuotesPromise = new Map<string, Promise<IExchangeQuoteUpdateAction>>();\n const requestGetQuotesInFlight = (key: string, planned: IPlannedRequest) => {\n const existing = mapKeyToInFlightGetQuotesPromise.get(key);\n if (existing) return existing;\n const promise = limitGetQuotes(() =>\n runWithProviderGroupConcurrencyLimit1({\n group_id: planned.group_id,\n mapGroupIdToTailPromise,\n fn: async () => {\n const instance = pickInstance({\n group_id: planned.group_id,\n instances: planned.instances,\n mapGroupIdToRoundRobinIndex,\n });\n return await requestGetQuotes({ terminal, instance, req: planned.req });\n },\n }),\n ).finally(() => {\n mapKeyToInFlightGetQuotesPromise.delete(key);\n });\n mapKeyToInFlightGetQuotesPromise.set(key, promise);\n return promise;\n };\n\n return {\n execute: async (requests) =>\n await Promise.all(\n requests.map(async ({ key, planned }) => await requestGetQuotesInFlight(key, planned)),\n ),\n };\n};\n"]}
|