vrpc 3.9.0 → 3.10.0
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/package.json +3 -2
- package/vrpc/VrpcAdapter.js +34 -17
- package/vrpc/VrpcAgent.js +14 -2
- package/vrpc/VrpcClient.js +5 -1
- package/vrpc/VrpcPersistor.js +191 -53
- package/vrpc/comment-parser/parser.js +5 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vrpc",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.0",
|
|
4
4
|
"description": "Variadic Remote Procedure Calls",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
"build:addon": "ln -sf binding.gyp.no-auto binding.gyp && node-gyp rebuild && rm binding.gyp",
|
|
13
13
|
"build:browser": "./node_modules/.bin/webpack --stats-error-details",
|
|
14
14
|
"build:doc": "./node_modules/.bin/jsdoc2md -f vrpc/VrpcAdapter.js vrpc/VrpcAgent.js vrpc/VrpcClient.js vrpc/VrpcNative.js --param-list-format=list --separators > docs/api.md",
|
|
15
|
-
"test": "npm run test:adapter && npm run test:agent && npm run test:client && npm run test:persistor && npm run test:production",
|
|
15
|
+
"test": "npm run test:adapter && npm run test:agent && npm run test:client && npm run test:persistor && npm run test:lifecycle && npm run test:production",
|
|
16
16
|
"test:adapter": "./node_modules/.bin/mocha tests/adapter/*.js --exit",
|
|
17
17
|
"test:native": "./node_modules/.bin/mocha tests/native/*.js --exit",
|
|
18
18
|
"test:agent": "tests/agent/test.sh",
|
|
19
19
|
"test:client": "tests/client/test.sh",
|
|
20
20
|
"test:performance": "tests/performance/test.sh",
|
|
21
21
|
"test:persistor": "./node_modules/.bin/mocha tests/persistor/*.js --timeout 30000 --exit",
|
|
22
|
+
"test:lifecycle": "./node_modules/.bin/mocha tests/lifecycle/*.js --timeout 30000 --exit",
|
|
22
23
|
"test:production": "./node_modules/.bin/mocha tests/production/lifeCycleTest.js --timeout 30000 --exit && tests/production/test.sh"
|
|
23
24
|
},
|
|
24
25
|
"bin": {
|
package/vrpc/VrpcAdapter.js
CHANGED
|
@@ -276,6 +276,15 @@ class VrpcAdapter {
|
|
|
276
276
|
* @param {String} description Parameter description
|
|
277
277
|
* @param {String} [type] Parameter type
|
|
278
278
|
* @param {Any} [default] The default to be injected when not provided
|
|
279
|
+
* @param {Callback} [callback] The signature of a function-typed
|
|
280
|
+
* parameter, when a `@callback` typedef of that name documents it
|
|
281
|
+
*/
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @typedef {Object} Callback
|
|
285
|
+
* @param {String} name Typedef name (the parameter's type)
|
|
286
|
+
* @param {String} description Callback description
|
|
287
|
+
* @param {Array.<Param>} params The arguments the function is called with
|
|
279
288
|
*/
|
|
280
289
|
|
|
281
290
|
/**
|
|
@@ -341,27 +350,35 @@ class VrpcAdapter {
|
|
|
341
350
|
static _parseComments (content) {
|
|
342
351
|
const comments = commentParser(content, { assocFunctions: true })
|
|
343
352
|
const meta = {}
|
|
353
|
+
const paramsOf = tags =>
|
|
354
|
+
tags
|
|
355
|
+
.filter(
|
|
356
|
+
({ tag }) => tag === 'param' || tag === 'arg' || tag === 'argument'
|
|
357
|
+
)
|
|
358
|
+
.map(({ name, optional, description, type, default: defaultValue }) => {
|
|
359
|
+
return { name, optional, description, type, defaultValue }
|
|
360
|
+
})
|
|
361
|
+
// `@callback Name` blocks document the signature of a function-typed
|
|
362
|
+
// parameter; a parameter whose type names one carries it as `callback`
|
|
363
|
+
const callbacks = {}
|
|
364
|
+
comments.forEach(({ tags, description }) => {
|
|
365
|
+
const typedef = tags && tags.find(({ tag }) => tag === 'callback')
|
|
366
|
+
if (!typedef || !typedef.name) return
|
|
367
|
+
callbacks[typedef.name] = {
|
|
368
|
+
name: typedef.name,
|
|
369
|
+
description,
|
|
370
|
+
params: paramsOf(tags)
|
|
371
|
+
}
|
|
372
|
+
})
|
|
344
373
|
comments.forEach(({ tags, description, functionName }) => {
|
|
374
|
+
if (tags && tags.some(({ tag }) => tag === 'callback')) return
|
|
345
375
|
if (functionName) {
|
|
346
376
|
if (tags) {
|
|
347
|
-
|
|
348
|
-
|
|
377
|
+
const params = paramsOf(tags).map(param =>
|
|
378
|
+
callbacks[param.type]
|
|
379
|
+
? { ...param, callback: callbacks[param.type] }
|
|
380
|
+
: param
|
|
349
381
|
)
|
|
350
|
-
if (params.length > 0) {
|
|
351
|
-
params = params.map(
|
|
352
|
-
({
|
|
353
|
-
name,
|
|
354
|
-
optional,
|
|
355
|
-
description,
|
|
356
|
-
type,
|
|
357
|
-
default: defaultValue
|
|
358
|
-
}) => {
|
|
359
|
-
return { name, optional, description, type, defaultValue }
|
|
360
|
-
}
|
|
361
|
-
)
|
|
362
|
-
} else {
|
|
363
|
-
params = []
|
|
364
|
-
}
|
|
365
382
|
let ret = tags.filter(
|
|
366
383
|
({ tag }) => tag === 'returns' || tag === 'return'
|
|
367
384
|
)
|
package/vrpc/VrpcAgent.js
CHANGED
|
@@ -614,7 +614,14 @@ class VrpcAgent extends EventEmitter {
|
|
|
614
614
|
// Intersecting life-cycle functions
|
|
615
615
|
switch (method) {
|
|
616
616
|
case '__createIsolated__': {
|
|
617
|
-
|
|
617
|
+
if (json.e) {
|
|
618
|
+
// a constructor that threw leaves nothing behind: no instance,
|
|
619
|
+
// no subscription, no lifetime bookkeeping
|
|
620
|
+
this._log.warn(
|
|
621
|
+
`Instantiation of ${className} failed: ${json.e.message}`
|
|
622
|
+
)
|
|
623
|
+
break
|
|
624
|
+
}
|
|
618
625
|
const instanceId = json.r
|
|
619
626
|
// TODO await this
|
|
620
627
|
this._subscribeToMethodsOfNewInstance(className, instanceId)
|
|
@@ -622,7 +629,12 @@ class VrpcAgent extends EventEmitter {
|
|
|
622
629
|
break
|
|
623
630
|
}
|
|
624
631
|
case '__createShared__': {
|
|
625
|
-
|
|
632
|
+
if (json.e) {
|
|
633
|
+
this._log.warn(
|
|
634
|
+
`Instantiation of ${className} failed: ${json.e.message}`
|
|
635
|
+
)
|
|
636
|
+
break
|
|
637
|
+
}
|
|
626
638
|
const instanceId = json.r
|
|
627
639
|
if (!this._hasSharedInstance(instanceId)) {
|
|
628
640
|
this._subscribeToMethodsOfNewInstance(className, instanceId)
|
package/vrpc/VrpcClient.js
CHANGED
|
@@ -1013,7 +1013,11 @@ class VrpcClient extends EventEmitter {
|
|
|
1013
1013
|
this._eventEmitter.once(i, ({ e, r }) => {
|
|
1014
1014
|
clearTimeout(timer)
|
|
1015
1015
|
if (e) {
|
|
1016
|
-
|
|
1016
|
+
// the agent answers a throwing constructor with { message, cause }
|
|
1017
|
+
const { message, cause } = VrpcClient._prepareError(e)
|
|
1018
|
+
reject(
|
|
1019
|
+
new Error(`[vrpc ${agent}-${className}-${f}]: ${message}`, { cause })
|
|
1020
|
+
)
|
|
1017
1021
|
} else {
|
|
1018
1022
|
const proxy = this._createProxy(agent, className, r)
|
|
1019
1023
|
resolve(proxy)
|
package/vrpc/VrpcPersistor.js
CHANGED
|
@@ -9,12 +9,10 @@ __\/\\\_______\/\\\__/\\\///////\\\___\/\\\/////////\\\____/\\\////////__
|
|
|
9
9
|
_______\//\\\_______\/\\\______\//\\\_\/\\\_______________\////\\\\\\\\\_
|
|
10
10
|
________\///________\///________\///__\///___________________\/////////__
|
|
11
11
|
|
|
12
|
-
|
|
13
12
|
Non-intrusively adapts code and provides access in form of asynchronous remote
|
|
14
13
|
procedure calls (RPC).
|
|
15
14
|
Author: Dr. Burkhard C. Heisen (https://github.com/heisenware/vrpc)
|
|
16
15
|
|
|
17
|
-
|
|
18
16
|
Licensed under the MIT License <http://opensource.org/licenses/MIT>.
|
|
19
17
|
Copyright (c) 2018 - 2022 Dr. Burkhard C. Heisen <burkhard.heisen@heisenware.com>.
|
|
20
18
|
|
|
@@ -39,12 +37,22 @@ SOFTWARE.
|
|
|
39
37
|
|
|
40
38
|
const VrpcAdapter = require('./VrpcAdapter')
|
|
41
39
|
|
|
40
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
41
|
+
|
|
42
42
|
/**
|
|
43
43
|
* Provides a persistence layer for VRPC instances.
|
|
44
44
|
*
|
|
45
45
|
* This class automatically saves the constructor arguments of newly created
|
|
46
|
-
* instances and re-creates them when the application restarts. It also
|
|
47
|
-
* for an 'update' event on instances to persist their state after
|
|
46
|
+
* shared instances and re-creates them when the application restarts. It also
|
|
47
|
+
* listens for an 'update' event on instances to persist their state after
|
|
48
|
+
* creation (the event's payload is used as the single constructor argument
|
|
49
|
+
* upon restoration). Isolated instances belong to the connection that
|
|
50
|
+
* created them and are never persisted.
|
|
51
|
+
*
|
|
52
|
+
* A record that cannot be restored is never deleted: it is quarantined
|
|
53
|
+
* (marked with the error, kept on disk), retried once per start so it heals
|
|
54
|
+
* by itself once the cause is fixed, and can be inspected (`status`), retried
|
|
55
|
+
* (`retry`) or removed on purpose (`forget`).
|
|
48
56
|
*
|
|
49
57
|
* @requires @heisenware/storage - This peer dependency must be installed.
|
|
50
58
|
* Storage 1.x (synchronous constructor) and >= 2.x (async `Storage.open`)
|
|
@@ -59,8 +67,16 @@ class VrpcPersistor {
|
|
|
59
67
|
* @param {VrpcAgent} options.agentInstance The VRPC agent whose instances should be persisted.
|
|
60
68
|
* @param {string} [options.dir] Optional directory for storage. Defaults to a path derived from the agent's name.
|
|
61
69
|
* @param {object} [options.log] Optional logger object (e.g. console) with info, warn, and error methods.
|
|
70
|
+
* @param {number} [options.retries=5] How often a record that fails on a fresh restore is retried before it is quarantined.
|
|
71
|
+
* @param {number} [options.retryDelay=1000] Milliseconds between retries, multiplied by the attempt number.
|
|
62
72
|
*/
|
|
63
|
-
constructor ({
|
|
73
|
+
constructor ({
|
|
74
|
+
agentInstance,
|
|
75
|
+
dir,
|
|
76
|
+
log = console,
|
|
77
|
+
retries = 5,
|
|
78
|
+
retryDelay = 1000
|
|
79
|
+
}) {
|
|
64
80
|
let Storage
|
|
65
81
|
try {
|
|
66
82
|
Storage = require('@heisenware/storage')
|
|
@@ -72,6 +88,8 @@ class VrpcPersistor {
|
|
|
72
88
|
|
|
73
89
|
this._agentInstance = agentInstance
|
|
74
90
|
this._log = log
|
|
91
|
+
this._retries = retries
|
|
92
|
+
this._retryDelay = retryDelay
|
|
75
93
|
|
|
76
94
|
this._dir =
|
|
77
95
|
dir ||
|
|
@@ -90,79 +108,149 @@ class VrpcPersistor {
|
|
|
90
108
|
/**
|
|
91
109
|
* Restores all persisted instances from storage.
|
|
92
110
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
111
|
+
* A record seen for the first time is retried with a growing delay; a
|
|
112
|
+
* record that still fails is quarantined: it stays on disk, marked with
|
|
113
|
+
* the error and the number of attempts. A quarantined record gets one
|
|
114
|
+
* attempt per restore, so it heals on the next start once its cause is
|
|
115
|
+
* fixed, and never turns a start into a retry storm.
|
|
116
|
+
*
|
|
117
|
+
* @returns {Promise<{restored: string[], quarantined: Array<{instance: string, className: string, error: string, attempts: number, since: string}>}>}
|
|
96
118
|
*/
|
|
97
119
|
async restore () {
|
|
98
120
|
await this._isInitialized
|
|
121
|
+
const summary = { restored: [], quarantined: [] }
|
|
99
122
|
const allIds = this._storage.keys()
|
|
100
123
|
if (allIds.length === 0) {
|
|
101
124
|
this._log.info('[VrpcPersistor] No instances to restore.')
|
|
102
|
-
return
|
|
125
|
+
return summary
|
|
103
126
|
}
|
|
104
|
-
|
|
105
127
|
this._log.info(
|
|
106
128
|
`[VrpcPersistor] Found ${allIds.length} persisted instance(s) to restore.`
|
|
107
129
|
)
|
|
108
130
|
|
|
109
|
-
let
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this._log.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
let failing = []
|
|
132
|
+
for (const id of allIds) {
|
|
133
|
+
const record = await this._storage.getItem(id)
|
|
134
|
+
if (!record || !record.className) {
|
|
135
|
+
this._log.warn(`[VrpcPersistor] Skipping ${id}: record is missing or broken.`)
|
|
136
|
+
continue
|
|
137
|
+
}
|
|
138
|
+
if (record.restoreError) {
|
|
139
|
+
// quarantined: one attempt per start
|
|
140
|
+
const err = this._tryCreate(id, record)
|
|
141
|
+
if (!err) {
|
|
142
|
+
summary.restored.push(id)
|
|
143
|
+
this._log.info(
|
|
144
|
+
`[VrpcPersistor] Healed ${id} (${record.className}) after ${record.restoreError.attempts} failed attempt(s).`
|
|
145
|
+
)
|
|
146
|
+
} else {
|
|
147
|
+
summary.quarantined.push(await this._quarantine(id, record, err, 1))
|
|
148
|
+
}
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
this._log.info(`[VrpcPersistor] Restoring instance: ${id} (${record.className})`)
|
|
152
|
+
const err = this._tryCreate(id, record)
|
|
153
|
+
if (!err) {
|
|
154
|
+
summary.restored.push(id)
|
|
155
|
+
} else {
|
|
118
156
|
this._log.warn(
|
|
119
157
|
`[VrpcPersistor] Could not restore ${id}: ${err.message}. Will retry.`
|
|
120
158
|
)
|
|
121
|
-
|
|
159
|
+
failing.push({ id, record, err })
|
|
122
160
|
}
|
|
123
|
-
}
|
|
124
|
-
await Promise.all(restorationPromises)
|
|
161
|
+
}
|
|
125
162
|
|
|
126
|
-
// Retry
|
|
163
|
+
// Retry fresh failures with a growing delay
|
|
127
164
|
let trial = 0
|
|
128
|
-
|
|
129
|
-
while (failedIds.length > 0 && trial++ < MAX_TRIALS) {
|
|
165
|
+
while (failing.length > 0 && trial++ < this._retries) {
|
|
130
166
|
this._log.info(
|
|
131
|
-
`[VrpcPersistor] Retrying ${
|
|
167
|
+
`[VrpcPersistor] Retrying ${failing.length} failed instance(s), attempt ${trial} of ${this._retries}.`
|
|
132
168
|
)
|
|
133
|
-
await
|
|
169
|
+
await sleep(this._retryDelay * trial)
|
|
134
170
|
const stillFailing = []
|
|
135
|
-
for (const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this._log.info(
|
|
140
|
-
|
|
141
|
-
)
|
|
142
|
-
} catch (err) {
|
|
143
|
-
stillFailing.push(id)
|
|
171
|
+
for (const entry of failing) {
|
|
172
|
+
const err = this._tryCreate(entry.id, entry.record)
|
|
173
|
+
if (!err) {
|
|
174
|
+
summary.restored.push(entry.id)
|
|
175
|
+
this._log.info(`[VrpcPersistor] Successfully restored ${entry.id} on retry.`)
|
|
176
|
+
} else {
|
|
177
|
+
stillFailing.push({ ...entry, err })
|
|
144
178
|
}
|
|
145
179
|
}
|
|
146
|
-
|
|
180
|
+
failing = stillFailing
|
|
147
181
|
}
|
|
148
182
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
'[VrpcPersistor] The following instances could not be restored and will be removed:'
|
|
183
|
+
for (const { id, record, err } of failing) {
|
|
184
|
+
summary.quarantined.push(
|
|
185
|
+
await this._quarantine(id, record, err, 1 + this._retries)
|
|
153
186
|
)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this._log.info(
|
|
190
|
+
`[VrpcPersistor] Restored ${summary.restored.length} of ${allIds.length} instance(s)` +
|
|
191
|
+
(summary.quarantined.length > 0
|
|
192
|
+
? `, ${summary.quarantined.length} quarantined: ${summary.quarantined
|
|
193
|
+
.map(x => `${x.instance} (${x.className}): ${x.error}`)
|
|
194
|
+
.join('; ')}`
|
|
195
|
+
: '.')
|
|
196
|
+
)
|
|
197
|
+
return summary
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Lists every persisted record with its quarantine mark, if any.
|
|
202
|
+
*
|
|
203
|
+
* @returns {Promise<{dir: string, instances: Array<{instance: string, className: string, restoreError: (object|null)}>}>}
|
|
204
|
+
*/
|
|
205
|
+
async status () {
|
|
206
|
+
await this._isInitialized
|
|
207
|
+
const instances = []
|
|
208
|
+
for (const id of this._storage.keys()) {
|
|
209
|
+
const record = await this._storage.getItem(id)
|
|
210
|
+
if (!record) continue
|
|
211
|
+
instances.push({
|
|
212
|
+
instance: id,
|
|
213
|
+
className: record.className,
|
|
214
|
+
restoreError: record.restoreError || null
|
|
157
215
|
})
|
|
158
|
-
try {
|
|
159
|
-
await Promise.all(deletionPromises)
|
|
160
|
-
} catch (err) {
|
|
161
|
-
this._log.error(
|
|
162
|
-
`[VrpcPersistor] Failed to delete broken instances: ${err.message}`
|
|
163
|
-
)
|
|
164
|
-
}
|
|
165
216
|
}
|
|
217
|
+
return { dir: this._dir, instances }
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Attempts one more time to restore a persisted instance (quarantined or
|
|
222
|
+
* not). A success clears the quarantine mark, a failure updates it.
|
|
223
|
+
*
|
|
224
|
+
* @param {string} id The instance id
|
|
225
|
+
* @returns {Promise<boolean>} true when the instance exists afterwards
|
|
226
|
+
*/
|
|
227
|
+
async retry (id) {
|
|
228
|
+
await this._isInitialized
|
|
229
|
+
const record = await this._storage.getItem(id)
|
|
230
|
+
if (!record) throw new Error(`Unknown persisted instance: ${id}`)
|
|
231
|
+
const err = this._tryCreate(id, record)
|
|
232
|
+
if (!err) {
|
|
233
|
+
this._log.info(`[VrpcPersistor] Restored ${id} (${record.className}) on request.`)
|
|
234
|
+
return true
|
|
235
|
+
}
|
|
236
|
+
await this._quarantine(id, record, err, 1)
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Removes a persisted record on purpose, the only way a record leaves the
|
|
242
|
+
* storage other than the deletion of a live instance.
|
|
243
|
+
*
|
|
244
|
+
* @param {string} id The instance id
|
|
245
|
+
* @returns {Promise<boolean>} true
|
|
246
|
+
*/
|
|
247
|
+
async forget (id) {
|
|
248
|
+
await this._isInitialized
|
|
249
|
+
const record = await this._storage.getItem(id)
|
|
250
|
+
if (!record) throw new Error(`Unknown persisted instance: ${id}`)
|
|
251
|
+
await this._storage.removeItem(id)
|
|
252
|
+
this._log.info(`[VrpcPersistor] Forgot ${id} (${record.className}).`)
|
|
253
|
+
return true
|
|
166
254
|
}
|
|
167
255
|
|
|
168
256
|
/**
|
|
@@ -175,8 +263,15 @@ class VrpcPersistor {
|
|
|
175
263
|
// after construction must be caught. The operations they trigger wait
|
|
176
264
|
// for the storage layer opened below.
|
|
177
265
|
|
|
178
|
-
// Persist new instance creation
|
|
179
|
-
|
|
266
|
+
// Persist new instance creation (a restore passes here too, which
|
|
267
|
+
// rewrites the record and thereby clears a quarantine mark)
|
|
268
|
+
VrpcAdapter.on('create', async ({ instance, className, args, isIsolated }) => {
|
|
269
|
+
if (isIsolated) {
|
|
270
|
+
this._log.info(
|
|
271
|
+
`[VrpcPersistor] Not persisting isolated instance: ${instance} (${className})`
|
|
272
|
+
)
|
|
273
|
+
return
|
|
274
|
+
}
|
|
180
275
|
this._log.info(
|
|
181
276
|
`[VrpcPersistor] Persisting new instance: ${instance} (${className})`
|
|
182
277
|
)
|
|
@@ -235,6 +330,49 @@ class VrpcPersistor {
|
|
|
235
330
|
}
|
|
236
331
|
}
|
|
237
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Creates the instance from its record; returns the error instead of
|
|
335
|
+
* throwing (an existing instance counts as success).
|
|
336
|
+
* @private
|
|
337
|
+
*/
|
|
338
|
+
_tryCreate (id, { className, args }) {
|
|
339
|
+
try {
|
|
340
|
+
this._agentInstance.create({ className, args, instance: id })
|
|
341
|
+
return null
|
|
342
|
+
} catch (err) {
|
|
343
|
+
return err
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Marks a record as not restorable and keeps it.
|
|
349
|
+
* @private
|
|
350
|
+
*/
|
|
351
|
+
async _quarantine (id, record, err, attempts) {
|
|
352
|
+
const previous = record.restoreError || { attempts: 0, since: new Date().toISOString() }
|
|
353
|
+
const restoreError = {
|
|
354
|
+
message: err.message,
|
|
355
|
+
at: new Date().toISOString(),
|
|
356
|
+
since: previous.since,
|
|
357
|
+
attempts: previous.attempts + attempts
|
|
358
|
+
}
|
|
359
|
+
await this._storage.setItem(
|
|
360
|
+
id,
|
|
361
|
+
{ className: record.className, args: record.args, restoreError },
|
|
362
|
+
{ folder: record.className }
|
|
363
|
+
)
|
|
364
|
+
this._log.warn(
|
|
365
|
+
`[VrpcPersistor] Quarantined ${id} (${record.className}) after ${restoreError.attempts} attempt(s): ${err.message}. The record is kept; fix the cause and restart, or call retry()/forget().`
|
|
366
|
+
)
|
|
367
|
+
return {
|
|
368
|
+
instance: id,
|
|
369
|
+
className: record.className,
|
|
370
|
+
error: err.message,
|
|
371
|
+
attempts: restoreError.attempts,
|
|
372
|
+
since: restoreError.since
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
238
376
|
/**
|
|
239
377
|
* Saves an instance's details to storage.
|
|
240
378
|
* @private
|
|
@@ -219,12 +219,15 @@ function mkextract (opts) {
|
|
|
219
219
|
|
|
220
220
|
// if open marker detected and it's not, skip one
|
|
221
221
|
if (startPos !== -1 && line.indexOf(MARKER_START_SKIP) !== startPos) {
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
// A block still waiting for its function is followed by another
|
|
223
|
+
// block: it documents no function (a typedef, a callback) and is
|
|
224
|
+
// returned as it is - parsed BEFORE the chunk makes way
|
|
224
225
|
if (findMethod) {
|
|
225
226
|
result = parse_block(chunk, opts)
|
|
226
227
|
findMethod = false
|
|
227
228
|
}
|
|
229
|
+
chunk = []
|
|
230
|
+
indent = startPos + MARKER_START.length
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
if (findMethod) {
|