@tetherto/mdk-core 0.0.0 → 0.12.1
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 +202 -0
- package/README.md +572 -0
- package/index.js +519 -0
- package/lib/address-policy.js +124 -0
- package/lib/local-discovery.js +321 -0
- package/lib/services/actions.service.js +68 -0
- package/lib/services/alerts.service.js +207 -0
- package/lib/services/comments.service.js +118 -0
- package/lib/services/log-history.service.js +307 -0
- package/lib/services/logs.service.js +249 -0
- package/lib/services/pool-utils/constants.js +20 -0
- package/lib/services/pool-utils/time.js +49 -0
- package/lib/services/pool.service.js +397 -0
- package/lib/services/provisioning.service.js +420 -0
- package/lib/services/settings.service.js +26 -0
- package/lib/services/snaps.service.js +181 -0
- package/lib/services/stats.service.js +205 -0
- package/lib/stats/README.md +80 -0
- package/lib/stats/index.js +57 -0
- package/lib/stats/ops/alerts_aggr.js +40 -0
- package/lib/stats/ops/alerts_group_cnt.js +43 -0
- package/lib/stats/ops/arr_concat.js +27 -0
- package/lib/stats/ops/array_obj_calc.js +41 -0
- package/lib/stats/ops/avg.js +42 -0
- package/lib/stats/ops/cnt.js +19 -0
- package/lib/stats/ops/group.js +38 -0
- package/lib/stats/ops/group_avg.js +53 -0
- package/lib/stats/ops/group_cnt.js +38 -0
- package/lib/stats/ops/group_max.js +40 -0
- package/lib/stats/ops/group_multiple_stats.js +40 -0
- package/lib/stats/ops/group_sum.js +40 -0
- package/lib/stats/ops/index.js +35 -0
- package/lib/stats/ops/nested_obj_concat.js +54 -0
- package/lib/stats/ops/obj_concat.js +33 -0
- package/lib/stats/ops/sum.js +27 -0
- package/lib/stats/utils.js +99 -0
- package/lib/templates/alerts.js +128 -0
- package/lib/templates/stats.js +476 -0
- package/lib/things/constants.js +65 -0
- package/lib/things/container.js +84 -0
- package/lib/things/miner.js +190 -0
- package/lib/things/powermeter.js +30 -0
- package/lib/things/sensor.js +18 -0
- package/lib/things/thing.js +93 -0
- package/lib/utils.js +603 -0
- package/lib/worker-infra.js +221 -0
- package/package.json +64 -1
- package/services.js +246 -0
- package/utils/compose-yaml.js +56 -0
- package/utils/constants.js +30 -0
- package/utils/index.js +29 -0
- package/utils/initialize.js +148 -0
- package/utils/service-bootstrap.js +271 -0
- package/worker.js +9 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { HOUR_MS, HOURS_24_MS } = require('./constants')
|
|
4
|
+
|
|
5
|
+
const isCurrentMonth = (month) => {
|
|
6
|
+
return parseInt(month.split('-')[0]) === new Date().getMonth() + 1
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const convertMsToSeconds = (timestampMs) => {
|
|
10
|
+
return Math.floor(timestampMs / 1000)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Upper bound on buckets one call may build (~1 year hourly, ~27 years
|
|
14
|
+
// daily). start/end come from callers, so an unbounded span is a cheap way to
|
|
15
|
+
// allocate an arbitrarily large array.
|
|
16
|
+
const MAX_TIME_RANGES = 9000
|
|
17
|
+
|
|
18
|
+
const getTimeRanges = (start, end, isHourly = true) => {
|
|
19
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) throw new Error('ERR_TIME_RANGE_INVALID')
|
|
20
|
+
if (start >= end) return []
|
|
21
|
+
|
|
22
|
+
const step = isHourly ? HOUR_MS : HOURS_24_MS
|
|
23
|
+
if ((end - start) / step > MAX_TIME_RANGES) throw new Error('ERR_TIME_RANGE_TOO_LARGE')
|
|
24
|
+
|
|
25
|
+
const ranges = []
|
|
26
|
+
const timeDiff = isHourly ? HOUR_MS : HOURS_24_MS
|
|
27
|
+
let endTime = new Date(start + timeDiff)
|
|
28
|
+
|
|
29
|
+
if (isHourly) {
|
|
30
|
+
endTime.setUTCMinutes(0, 0, 0)
|
|
31
|
+
} else {
|
|
32
|
+
endTime.setUTCHours(0, 0, 0, 0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
endTime = endTime.getTime()
|
|
36
|
+
while (start < end) {
|
|
37
|
+
ranges.push({ start, end: endTime })
|
|
38
|
+
start = endTime
|
|
39
|
+
endTime += timeDiff
|
|
40
|
+
}
|
|
41
|
+
return ranges
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
isCurrentMonth,
|
|
46
|
+
convertMsToSeconds,
|
|
47
|
+
getTimeRanges,
|
|
48
|
+
MAX_TIME_RANGES
|
|
49
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const StoreFacility = require('@tetherto/hp-svc-facs-store')
|
|
4
|
+
const HttpFacility = require('@bitfinex/bfx-facs-http')
|
|
5
|
+
const SchedulerFacility = require('@bitfinex/bfx-facs-scheduler')
|
|
6
|
+
const EventEmitter = require('events')
|
|
7
|
+
const fs = require('fs')
|
|
8
|
+
const path = require('path')
|
|
9
|
+
const gLibUtilBase = require('@bitfinex/lib-js-util-base')
|
|
10
|
+
const mingo = require('mingo')
|
|
11
|
+
const { SCHEDULER_TIMES, BTC_SATS, MINUTE_MS, HOUR_MS, HOURS_24_MS } = require('./pool-utils/constants')
|
|
12
|
+
const { getTimeRanges } = require('./pool-utils/time')
|
|
13
|
+
|
|
14
|
+
// Rows one getDbData call may return (conf.maxDbReadLimit overrides).
|
|
15
|
+
const MAX_DB_READ_LIMIT = 10000
|
|
16
|
+
const utilsStore = require('@tetherto/hp-svc-facs-store/utils')
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Scheduler-driven HTTP-polling pool worker base (relocated
|
|
20
|
+
* MinerpoolManager). Not a Thing worker: one logical pool per instance,
|
|
21
|
+
* data exposed through getWrkExtData / getWorkers rather than per-device
|
|
22
|
+
* telemetry. Concrete pools (ocean, f2pool) subclass and implement the
|
|
23
|
+
* fetch* hooks + getHttpUrl.
|
|
24
|
+
*/
|
|
25
|
+
class PoolService extends EventEmitter {
|
|
26
|
+
constructor (conf, ctx) {
|
|
27
|
+
super()
|
|
28
|
+
this.conf = conf || {}
|
|
29
|
+
this.ctx = ctx || {}
|
|
30
|
+
this.wtype = this.conf.wtype
|
|
31
|
+
this.debugError = (ctx && ctx.debugError) || ((...args) => console.error(...args))
|
|
32
|
+
|
|
33
|
+
if (!ctx || !ctx.rack) {
|
|
34
|
+
throw new Error('ERR_PROC_RACK_UNDEFINED')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
this.prefix = `${this.wtype}-${ctx.rack}`
|
|
38
|
+
this.loadConf = ctx.loadConf ?? this._defaultLoadConf.bind(this)
|
|
39
|
+
|
|
40
|
+
// Aborts any in-flight fetch* HTTP call/rate-limit sleep the instant stop()
|
|
41
|
+
// runs — stop() tears down http_0 (baseUrl reset to '') right after, and
|
|
42
|
+
// without this a request already in flight fails with a confusing
|
|
43
|
+
// "Only absolute URLs are supported" instead of a clean abort.
|
|
44
|
+
this._abortController = new AbortController()
|
|
45
|
+
|
|
46
|
+
this.data = {
|
|
47
|
+
statsData: {},
|
|
48
|
+
workersData: { ts: 0, workers: [] },
|
|
49
|
+
blocks: [],
|
|
50
|
+
yearlyBalances: {}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async init (pool) {
|
|
55
|
+
if (this._initialized) return
|
|
56
|
+
|
|
57
|
+
this.loadConf(pool, pool)
|
|
58
|
+
const ctx = this.ctx
|
|
59
|
+
await this._createFacilities(ctx)
|
|
60
|
+
|
|
61
|
+
// Library default: never take over the process's signals. Standalone
|
|
62
|
+
// processes opt in with ctx.handleSignals.
|
|
63
|
+
if (this._ownsFacilities && this.ctx.handleSignals === true) {
|
|
64
|
+
this._bindProcessExit()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this._initialized = true
|
|
68
|
+
|
|
69
|
+
const db = await this.store_s1.getBee(
|
|
70
|
+
{ name: 'main' },
|
|
71
|
+
{ keyEncoding: 'binary' }
|
|
72
|
+
)
|
|
73
|
+
await db.ready()
|
|
74
|
+
this.transactionsDb = db.sub('transactions')
|
|
75
|
+
this.workersCountDb = db.sub('workers-count')
|
|
76
|
+
this.statsDb = db.sub('stats')
|
|
77
|
+
this.workersDb = db.sub('workers')
|
|
78
|
+
this.blocksDb = db.sub('blocks')
|
|
79
|
+
|
|
80
|
+
for (const { time, key } of Object.values(SCHEDULER_TIMES)) {
|
|
81
|
+
this.scheduler_0.add(key, (fireTime) => {
|
|
82
|
+
this.fetchData(key, fireTime)
|
|
83
|
+
}, time)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async fetchData (key, time) {
|
|
88
|
+
try {
|
|
89
|
+
switch (key) {
|
|
90
|
+
case SCHEDULER_TIMES._1M.key:
|
|
91
|
+
await this.fetchStats(time)
|
|
92
|
+
break
|
|
93
|
+
case SCHEDULER_TIMES._5M.key:
|
|
94
|
+
await this.fetchWorkers(time)
|
|
95
|
+
await this.saveStats(time)
|
|
96
|
+
break
|
|
97
|
+
case SCHEDULER_TIMES._1D.key:
|
|
98
|
+
await this.fetchTransactions()
|
|
99
|
+
await this.fetchBlocks()
|
|
100
|
+
await this.saveWorkers(time)
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
this._logErr('ERR_DATA_FETCH', e)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async fetchStats (time) {
|
|
109
|
+
// no-op
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async fetchWorkers (time) {
|
|
113
|
+
// no-op
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async fetchTransactions () {
|
|
117
|
+
// no-op
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async fetchBlocks () {
|
|
121
|
+
// no-op
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async saveStats (time) {
|
|
125
|
+
const ts = Math.floor(time.getTime() / 1000) * 1000
|
|
126
|
+
await this._saveToDb(this.statsDb, ts, { ts, stats: this.data.statsData.stats })
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async saveWorkers (time) {
|
|
130
|
+
const ts = Math.floor(time.getTime() / 1000) * 1000
|
|
131
|
+
await this._saveToDb(this.workersDb, ts, { ts, workers: this.data.workersData.workers })
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async _saveToDb (db, ts, data) {
|
|
135
|
+
await db.put(utilsStore.convIntToBin(ts), Buffer.from(JSON.stringify(data)))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
_logErr (msg, err) {
|
|
139
|
+
console.error(new Date().toISOString(), msg, err)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_aggrTransactions (data, { start, end }) {
|
|
143
|
+
const totalRevenue = data.reduce((total, log) => {
|
|
144
|
+
log.transactions?.forEach((transaction) => {
|
|
145
|
+
total += transaction.satoshis_net_earned
|
|
146
|
+
})
|
|
147
|
+
return total
|
|
148
|
+
}, 0)
|
|
149
|
+
const tsRange = getTimeRanges(Number(start), Number(end))
|
|
150
|
+
const hourlyRevenues = []
|
|
151
|
+
if (!tsRange.length) return { ts: Date.now(), hourlyRevenues }
|
|
152
|
+
const hourlyAvgRevenue = (totalRevenue / tsRange.length) / BTC_SATS
|
|
153
|
+
tsRange.forEach(({ end: rangeEnd }) => {
|
|
154
|
+
hourlyRevenues.push({ ts: rangeEnd, revenue: hourlyAvgRevenue })
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return { ts: Date.now(), hourlyRevenues }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
_getIntervalMs (interval) {
|
|
161
|
+
switch (interval) {
|
|
162
|
+
case '1D':
|
|
163
|
+
return HOURS_24_MS
|
|
164
|
+
case '3h':
|
|
165
|
+
return 3 * HOUR_MS
|
|
166
|
+
case '30m':
|
|
167
|
+
return 30 * MINUTE_MS
|
|
168
|
+
case '5m':
|
|
169
|
+
default:
|
|
170
|
+
return 5 * MINUTE_MS
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_avg (avg, value, count) {
|
|
175
|
+
return (avg * (count - 1) + value) / count
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_aggrByInterval (data, interval) {
|
|
179
|
+
const intervalMs = this._getIntervalMs(interval)
|
|
180
|
+
const aggrBuckets = {}
|
|
181
|
+
|
|
182
|
+
data.forEach(d => {
|
|
183
|
+
const aggrTimestamp = Math.ceil(d.ts / intervalMs) * intervalMs
|
|
184
|
+
if (!aggrBuckets[aggrTimestamp]) {
|
|
185
|
+
aggrBuckets[aggrTimestamp] = []
|
|
186
|
+
}
|
|
187
|
+
aggrBuckets[aggrTimestamp].push(d)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
return Object.entries(aggrBuckets).map(([ts, items]) => {
|
|
191
|
+
return items.reduce((acc, d, itemIndex) => {
|
|
192
|
+
d.stats = d.stats.map((stat, statsIndex) => {
|
|
193
|
+
const avgHashrate = acc?.stats?.[statsIndex]?.hashrate || 0
|
|
194
|
+
const hashrate = this._avg(avgHashrate, stat.hashrate, itemIndex + 1)
|
|
195
|
+
return { ...stat, hashrate }
|
|
196
|
+
})
|
|
197
|
+
return { ...acc, ...d, ts: Number(ts) }
|
|
198
|
+
}, {})
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async getDbData (db, { start, end, limit }) {
|
|
203
|
+
if (!start || !Number.isFinite(Number(start)) || Number(start) < 0) throw new Error('ERR_START_INVALID')
|
|
204
|
+
if (!end || !Number.isFinite(Number(end)) || Number(end) < 0) throw new Error('ERR_END_INVALID')
|
|
205
|
+
start = Number(start)
|
|
206
|
+
end = Number(end)
|
|
207
|
+
|
|
208
|
+
const maxLimit = Number.isInteger(this.conf.maxDbReadLimit) && this.conf.maxDbReadLimit > 0
|
|
209
|
+
? this.conf.maxDbReadLimit
|
|
210
|
+
: MAX_DB_READ_LIMIT
|
|
211
|
+
const n = limit === undefined || limit === null ? maxLimit : Number(limit)
|
|
212
|
+
if (!Number.isInteger(n) || n < 0) throw new Error('ERR_LIMIT_INVALID')
|
|
213
|
+
const query = {
|
|
214
|
+
gte: utilsStore.convIntToBin(start),
|
|
215
|
+
lte: utilsStore.convIntToBin(end),
|
|
216
|
+
limit: Math.min(n, maxLimit)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const stream = db.createReadStream(query)
|
|
220
|
+
const res = []
|
|
221
|
+
for await (const entry of stream) {
|
|
222
|
+
res.push(JSON.parse(entry.value.toString()))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return res
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
_projection (data, fields = {}) {
|
|
229
|
+
const query = new mingo.Query({})
|
|
230
|
+
if (Array.isArray(data)) return query.find(data, fields).all()
|
|
231
|
+
const cursor = query.find([data], fields)
|
|
232
|
+
return cursor.all()[0]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
filterWorkers (workers, offset, limit) {
|
|
236
|
+
offset = Number(offset)
|
|
237
|
+
limit = Number(limit)
|
|
238
|
+
if (!Number.isInteger(offset) || offset < 0) throw new Error('ERR_OFFSET_INVALID')
|
|
239
|
+
if (!Number.isInteger(limit) || limit < 0) throw new Error('ERR_LIMIT_INVALID')
|
|
240
|
+
return workers.slice(offset, offset + (Math.min(limit, 100)))
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async getWorkers (query) {
|
|
244
|
+
const { offset = 0, limit = 100, name, start, end } = query
|
|
245
|
+
if (!start || !end) {
|
|
246
|
+
const workersObj = { ts: 0, workers: this.filterWorkers(this.data.workersData.workers, offset, limit) }
|
|
247
|
+
workersObj.workers = this.appendPoolType(workersObj.workers)
|
|
248
|
+
return workersObj
|
|
249
|
+
}
|
|
250
|
+
const data = await this.getDbData(this.workersDb, query)
|
|
251
|
+
return data.reduce((aggr, obj) => {
|
|
252
|
+
let workersObj
|
|
253
|
+
if (name) workersObj = { ts: obj.ts, workers: obj.workers.filter(w => w.name === name) }
|
|
254
|
+
else workersObj = { ts: obj.ts, workers: this.filterWorkers(obj.workers, offset, limit) }
|
|
255
|
+
workersObj.workers = this.appendPoolType(workersObj.workers)
|
|
256
|
+
aggr = aggr.concat(workersObj)
|
|
257
|
+
return aggr
|
|
258
|
+
}, [])
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
appendPoolType (data) {
|
|
262
|
+
return data.map(d => ({ poolType: this.wtype, ...d }))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async _getWrkExtDataExtra (key, query) {
|
|
266
|
+
return undefined
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async getWrkExtData (req) {
|
|
270
|
+
const { query } = req
|
|
271
|
+
if (!query) throw new Error('ERR_QUERY_INVALID')
|
|
272
|
+
|
|
273
|
+
const { key } = query
|
|
274
|
+
if (!key) throw new Error('ERR_KEY_INVALID')
|
|
275
|
+
|
|
276
|
+
const extra = await this._getWrkExtDataExtra(key, query)
|
|
277
|
+
if (extra !== undefined) {
|
|
278
|
+
const data = extra
|
|
279
|
+
if (!gLibUtilBase.isEmpty(query.fields)) return this._projection(data, query.fields)
|
|
280
|
+
return data
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
let data
|
|
284
|
+
switch (key) {
|
|
285
|
+
case 'transactions':
|
|
286
|
+
data = await this.getDbData(this.transactionsDb, query)
|
|
287
|
+
if (query.aggrHourly) data = this._aggrTransactions(data, query)
|
|
288
|
+
break
|
|
289
|
+
case 'workers-count':
|
|
290
|
+
data = await this.getDbData(this.workersCountDb, query)
|
|
291
|
+
break
|
|
292
|
+
case 'workers':
|
|
293
|
+
data = await this.getWorkers(query)
|
|
294
|
+
break
|
|
295
|
+
case 'stats':
|
|
296
|
+
data = this.data.statsData
|
|
297
|
+
if (data.stats) data.stats = this.appendPoolType(data.stats)
|
|
298
|
+
break
|
|
299
|
+
case 'stats-history':
|
|
300
|
+
data = await this.getDbData(this.statsDb, query)
|
|
301
|
+
if (query.interval) data = this._aggrByInterval(data, query.interval)
|
|
302
|
+
data.forEach(d => { if (d.stats) d.stats = this.appendPoolType(d.stats) })
|
|
303
|
+
break
|
|
304
|
+
default:
|
|
305
|
+
// Own keys only — never hand back inherited members (constructor, ...).
|
|
306
|
+
data = Object.prototype.hasOwnProperty.call(this.data, key) ? this.data[key] : undefined
|
|
307
|
+
break
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!gLibUtilBase.isEmpty(query.fields)) return this._projection(data, query.fields)
|
|
311
|
+
return data
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
_defaultLoadConf (c, group = null) {
|
|
315
|
+
const root = this.ctx.root || ''
|
|
316
|
+
const configPath = path.join(root, 'config', `${c}.json`)
|
|
317
|
+
if (fs.existsSync(configPath)) {
|
|
318
|
+
const data = JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
319
|
+
const merged = group ? { [group]: data } : data
|
|
320
|
+
Object.assign(this.conf, merged)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async _createFacilities (ctx) {
|
|
325
|
+
if (ctx.facs) {
|
|
326
|
+
this.store_s1 = ctx.facs.store_s1
|
|
327
|
+
this.http_0 = ctx.facs.http_0
|
|
328
|
+
this.scheduler_0 = ctx.facs.scheduler_0
|
|
329
|
+
return
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
this.store_s1 = new StoreFacility(this, { storeDir: ctx.storeDir }, ctx)
|
|
333
|
+
await this._startFacility(this.store_s1)
|
|
334
|
+
|
|
335
|
+
this.scheduler_0 = new SchedulerFacility(this, {}, ctx)
|
|
336
|
+
await this._startFacility(this.scheduler_0)
|
|
337
|
+
|
|
338
|
+
this.http_0 = new HttpFacility(this, {
|
|
339
|
+
baseUrl: this.getHttpUrl(),
|
|
340
|
+
timeout: 30 * 1000
|
|
341
|
+
}, {})
|
|
342
|
+
await this._startFacility(this.http_0)
|
|
343
|
+
|
|
344
|
+
this._ownsFacilities = true
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
getHttpUrl () {
|
|
348
|
+
return this.conf.baseUrl
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Concrete pools (ocean, f2pool) thread this into their API client so a
|
|
352
|
+
// fetch* call in flight when stop() runs aborts instead of racing the
|
|
353
|
+
// facility teardown below.
|
|
354
|
+
get abortSignal () {
|
|
355
|
+
return this._abortController.signal
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
stop (cb) {
|
|
359
|
+
this._abortController.abort()
|
|
360
|
+
if (!this._ownsFacilities || !this._initialized) {
|
|
361
|
+
return cb()
|
|
362
|
+
}
|
|
363
|
+
this._initialized = false
|
|
364
|
+
const facilities = [
|
|
365
|
+
this.scheduler_0,
|
|
366
|
+
this.http_0,
|
|
367
|
+
this.store_s1
|
|
368
|
+
]
|
|
369
|
+
let i = 0
|
|
370
|
+
const next = (err) => {
|
|
371
|
+
if (err) this.debugError('ERR_FAC_STOP', err)
|
|
372
|
+
if (i >= facilities.length) return cb()
|
|
373
|
+
const facility = facilities[i++]
|
|
374
|
+
if (typeof facility.stop !== 'function') return next()
|
|
375
|
+
facility.stop(next)
|
|
376
|
+
}
|
|
377
|
+
next()
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Opt-in only (ctx.handleSignals: true): stop the facilities on
|
|
381
|
+
// SIGINT/SIGTERM. Embedders own their process's signals by default.
|
|
382
|
+
_bindProcessExit () {
|
|
383
|
+
const stop = () => {
|
|
384
|
+
this.stop(() => {})
|
|
385
|
+
}
|
|
386
|
+
process.once('SIGINT', stop)
|
|
387
|
+
process.once('SIGTERM', stop)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
_startFacility (facility) {
|
|
391
|
+
return new Promise((resolve, reject) => {
|
|
392
|
+
facility.start((err) => (err ? reject(err) : resolve()))
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
module.exports = PoolService
|