libp2r2p 0.10.19 → 0.10.21
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/README.md +174 -10
- package/idb-queue/index.js +66 -2
- package/package.json +1 -1
- package/private-channel/helpers/rumor.js +26 -0
- package/private-channel/index.js +24 -12
- package/private-channel/services/received-chunks.js +3 -2
- package/private-message/index.js +333 -281
- package/private-messenger/index.js +328 -91
- package/private-messenger/services/channel-state.js +1 -1
- package/relay/helpers/read-admission.js +92 -0
- package/relay/services/relay-pool.js +210 -58
|
@@ -13,7 +13,7 @@ channels, keyPath "pubkey"
|
|
|
13
13
|
pubkey watched channel public key
|
|
14
14
|
value evolving channel-recovery state, including last-seen/watched times,
|
|
15
15
|
mode, relays, seeders, requested offline-recovery retention, offline
|
|
16
|
-
ranges, active offline-range start,
|
|
16
|
+
ranges, active offline-range start, recoveredThrough (last successful scan end),
|
|
17
17
|
per-seeder activity, and sent/received content-key usage
|
|
18
18
|
|
|
19
19
|
Changed channel records are upserted individually and explicit removals are
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { ValidationError } from '../../error/index.js'
|
|
2
|
+
import { maybeUnref } from './timer.js'
|
|
3
|
+
|
|
4
|
+
export function admissionError (code, relay) {
|
|
5
|
+
return Object.assign(new Error(code), { code, relay, phase: 'admission' })
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// One FIFO per pooled connection. A feed reserves both REQs atomically; its
|
|
9
|
+
// history and live leases can subsequently be released independently.
|
|
10
|
+
export class ReadAdmission {
|
|
11
|
+
constructor ({ maxSubscriptionsPerRelay = 28, maxConcurrentHistoryPerRelay = 2, maxQueuedReadsPerRelay = 256 } = {}) {
|
|
12
|
+
for (const value of [maxSubscriptionsPerRelay, maxConcurrentHistoryPerRelay, maxQueuedReadsPerRelay]) {
|
|
13
|
+
if (!Number.isSafeInteger(value) || value < 1) throw new ValidationError('INVALID_RELAY_READ_CAPACITY')
|
|
14
|
+
}
|
|
15
|
+
Object.assign(this, { maxSubscriptionsPerRelay, maxConcurrentHistoryPerRelay, maxQueuedReadsPerRelay })
|
|
16
|
+
this.states = new Map()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
acquire (relay, { history = false, live = false, signal, queueTimeout = 30000 } = {}) {
|
|
20
|
+
if (queueTimeout !== null && (!Number.isFinite(queueTimeout) || queueTimeout < 0)) throw new ValidationError('INVALID_RELAY_TIMEOUT')
|
|
21
|
+
const weight = Number(history) + Number(live)
|
|
22
|
+
if (!weight || weight > this.maxSubscriptionsPerRelay) return Promise.reject(admissionError('RELAY_READ_CAPACITY', relay))
|
|
23
|
+
if (signal?.aborted) return Promise.reject(admissionError('RELAY_READ_CANCELLED', relay))
|
|
24
|
+
let state = this.states.get(relay)
|
|
25
|
+
if (!state) {
|
|
26
|
+
state = { active: 0, history: 0, queue: [] }
|
|
27
|
+
this.states.set(relay, state)
|
|
28
|
+
}
|
|
29
|
+
const deferred = Promise.withResolvers()
|
|
30
|
+
let timer
|
|
31
|
+
const job = {
|
|
32
|
+
history, weight, grant: () => {
|
|
33
|
+
cleanup()
|
|
34
|
+
state.active += weight
|
|
35
|
+
state.history += Number(history)
|
|
36
|
+
const leases = {}
|
|
37
|
+
for (const kind of ['history', 'live']) {
|
|
38
|
+
if (!(kind === 'history' ? history : live)) continue
|
|
39
|
+
let released = false
|
|
40
|
+
leases[kind] = {
|
|
41
|
+
release: () => {
|
|
42
|
+
if (released) return
|
|
43
|
+
released = true
|
|
44
|
+
state.active--
|
|
45
|
+
if (kind === 'history') state.history--
|
|
46
|
+
this.drain(relay, state)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
deferred.resolve(leases)
|
|
51
|
+
}, cancel: code => {
|
|
52
|
+
const index = state.queue.indexOf(job)
|
|
53
|
+
if (index < 0) return
|
|
54
|
+
state.queue.splice(index, 1)
|
|
55
|
+
cleanup()
|
|
56
|
+
deferred.reject(admissionError(code, relay))
|
|
57
|
+
this.drain(relay, state)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const abort = () => job.cancel('RELAY_READ_CANCELLED')
|
|
61
|
+
const cleanup = () => {
|
|
62
|
+
clearTimeout(timer)
|
|
63
|
+
signal?.removeEventListener('abort', abort)
|
|
64
|
+
}
|
|
65
|
+
if (state.queue.length >= this.maxQueuedReadsPerRelay) return Promise.reject(admissionError('RELAY_READ_QUEUE_FULL', relay))
|
|
66
|
+
state.queue.push(job)
|
|
67
|
+
signal?.addEventListener('abort', abort, { once: true })
|
|
68
|
+
if (queueTimeout !== null) timer = maybeUnref(setTimeout(() => job.cancel('RELAY_READ_QUEUE_TIMEOUT'), queueTimeout))
|
|
69
|
+
this.drain(relay, state)
|
|
70
|
+
return deferred.promise
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
drain (relay, state) {
|
|
74
|
+
while (state.queue.length) {
|
|
75
|
+
const job = state.queue[0]
|
|
76
|
+
if (state.active + job.weight > this.maxSubscriptionsPerRelay || (job.history && state.history >= this.maxConcurrentHistoryPerRelay)) break
|
|
77
|
+
state.queue.shift().grant()
|
|
78
|
+
}
|
|
79
|
+
if (!state.active && !state.queue.length && this.states.get(relay) === state) this.states.delete(relay)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
cancelQueued (relay) {
|
|
83
|
+
const state = this.states.get(relay)
|
|
84
|
+
if (!state) return
|
|
85
|
+
// Remove the entire queue first: cancelling one entry must not admit another.
|
|
86
|
+
const jobs = state.queue.splice(0)
|
|
87
|
+
for (const job of jobs) {
|
|
88
|
+
state.queue.push(job)
|
|
89
|
+
job.cancel('RELAY_DISCONNECTED')
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|