@livestore/webmesh 0.3.0-dev.4 → 0.3.0-dev.41
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 +42 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/channel/direct-channel-internal.d.ts +26 -0
- package/dist/channel/direct-channel-internal.d.ts.map +1 -0
- package/dist/channel/direct-channel-internal.js +217 -0
- package/dist/channel/direct-channel-internal.js.map +1 -0
- package/dist/channel/direct-channel.d.ts +22 -0
- package/dist/channel/direct-channel.d.ts.map +1 -0
- package/dist/channel/direct-channel.js +153 -0
- package/dist/channel/direct-channel.js.map +1 -0
- package/dist/channel/proxy-channel.d.ts +3 -3
- package/dist/channel/proxy-channel.d.ts.map +1 -1
- package/dist/channel/proxy-channel.js +119 -37
- package/dist/channel/proxy-channel.js.map +1 -1
- package/dist/common.d.ts +47 -19
- package/dist/common.d.ts.map +1 -1
- package/dist/common.js +13 -5
- package/dist/common.js.map +1 -1
- package/dist/mesh-schema.d.ts +79 -13
- package/dist/mesh-schema.d.ts.map +1 -1
- package/dist/mesh-schema.js +59 -10
- package/dist/mesh-schema.js.map +1 -1
- package/dist/mod.d.ts +2 -2
- package/dist/mod.d.ts.map +1 -1
- package/dist/mod.js +2 -2
- package/dist/mod.js.map +1 -1
- package/dist/node.d.ts +51 -24
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +322 -111
- package/dist/node.js.map +1 -1
- package/dist/node.test.d.ts +1 -1
- package/dist/node.test.d.ts.map +1 -1
- package/dist/node.test.js +489 -157
- package/dist/node.test.js.map +1 -1
- package/dist/utils.d.ts +4 -4
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +7 -1
- package/dist/utils.js.map +1 -1
- package/dist/websocket-edge.d.ts +53 -0
- package/dist/websocket-edge.d.ts.map +1 -0
- package/dist/websocket-edge.js +89 -0
- package/dist/websocket-edge.js.map +1 -0
- package/package.json +10 -6
- package/src/channel/direct-channel-internal.ts +356 -0
- package/src/channel/direct-channel.ts +234 -0
- package/src/channel/proxy-channel.ts +344 -234
- package/src/common.ts +24 -17
- package/src/mesh-schema.ts +73 -20
- package/src/mod.ts +2 -2
- package/src/node.test.ts +723 -190
- package/src/node.ts +497 -152
- package/src/utils.ts +13 -2
- package/src/websocket-edge.ts +183 -0
- package/dist/channel/message-channel.d.ts +0 -20
- package/dist/channel/message-channel.d.ts.map +0 -1
- package/dist/channel/message-channel.js +0 -183
- package/dist/channel/message-channel.js.map +0 -1
- package/dist/websocket-connection.d.ts +0 -51
- package/dist/websocket-connection.d.ts.map +0 -1
- package/dist/websocket-connection.js +0 -74
- package/dist/websocket-connection.js.map +0 -1
- package/dist/websocket-server.d.ts +0 -7
- package/dist/websocket-server.d.ts.map +0 -1
- package/dist/websocket-server.js +0 -24
- package/dist/websocket-server.js.map +0 -1
- package/src/channel/message-channel.ts +0 -354
- package/src/websocket-connection.ts +0 -158
- package/src/websocket-server.ts +0 -40
- package/tsconfig.json +0 -11
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
Deferred,
|
|
5
5
|
Effect,
|
|
6
6
|
Either,
|
|
7
|
+
Exit,
|
|
7
8
|
Fiber,
|
|
8
9
|
FiberHandle,
|
|
9
10
|
Queue,
|
|
10
11
|
Schedule,
|
|
11
12
|
Schema,
|
|
13
|
+
Scope,
|
|
12
14
|
Stream,
|
|
13
15
|
SubscriptionRef,
|
|
14
16
|
WebChannel,
|
|
@@ -27,7 +29,7 @@ import * as MeshSchema from '../mesh-schema.js'
|
|
|
27
29
|
interface MakeProxyChannelArgs {
|
|
28
30
|
queue: Queue.Queue<ProxyQueueItem>
|
|
29
31
|
nodeName: MeshNodeName
|
|
30
|
-
|
|
32
|
+
newEdgeAvailablePubSub: PubSub.PubSub<MeshNodeName>
|
|
31
33
|
sendPacket: (packet: typeof MeshSchema.ProxyChannelPacket.Type) => Effect.Effect<void>
|
|
32
34
|
channelName: ChannelName
|
|
33
35
|
target: MeshNodeName
|
|
@@ -40,293 +42,401 @@ interface MakeProxyChannelArgs {
|
|
|
40
42
|
export const makeProxyChannel = ({
|
|
41
43
|
queue,
|
|
42
44
|
nodeName,
|
|
43
|
-
|
|
45
|
+
newEdgeAvailablePubSub,
|
|
44
46
|
sendPacket,
|
|
45
47
|
target,
|
|
46
48
|
channelName,
|
|
47
49
|
schema,
|
|
48
50
|
}: MakeProxyChannelArgs) =>
|
|
49
|
-
Effect.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const channelStateRef = { current: { _tag: 'Initial' } as ProxiedChannelState }
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* We need to unique identify a channel as multiple channels might exist between the same two nodes.
|
|
72
|
-
* We do this by letting each channel end generate a unique id and then combining them in a deterministic way.
|
|
73
|
-
*/
|
|
74
|
-
const channelIdCandidate = nanoid(5)
|
|
75
|
-
yield* Effect.annotateCurrentSpan({ channelIdCandidate })
|
|
76
|
-
|
|
77
|
-
const channelSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
78
|
-
|
|
79
|
-
const connectedStateRef = yield* SubscriptionRef.make<ProxiedChannelStateEstablished | false>(false)
|
|
80
|
-
|
|
81
|
-
const waitForEstablished = Effect.gen(function* () {
|
|
82
|
-
const state = yield* SubscriptionRef.waitUntil(connectedStateRef, (state) => state !== false)
|
|
83
|
-
|
|
84
|
-
return state as ProxiedChannelStateEstablished
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const setStateToEstablished = (channelId: string) =>
|
|
88
|
-
Effect.gen(function* () {
|
|
89
|
-
// TODO avoid "double" `Connected` events (we might call `setStateToEstablished` twice during initial connection)
|
|
90
|
-
yield* Effect.spanEvent(`Connected (${channelId})`).pipe(Effect.withParentSpan(channelSpan))
|
|
91
|
-
channelStateRef.current = {
|
|
92
|
-
_tag: 'Established',
|
|
93
|
-
listenSchema: schema.listen,
|
|
94
|
-
listenQueue,
|
|
95
|
-
ackMap,
|
|
96
|
-
combinedChannelId: channelId,
|
|
97
|
-
}
|
|
98
|
-
yield* SubscriptionRef.set(connectedStateRef, channelStateRef.current)
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const connectionRequest = Effect.suspend(() =>
|
|
102
|
-
sendPacket(
|
|
103
|
-
MeshSchema.ProxyChannelRequest.make({ channelName, hops: [], source: nodeName, target, channelIdCandidate }),
|
|
104
|
-
),
|
|
105
|
-
)
|
|
51
|
+
Effect.scopeWithCloseable((scope) =>
|
|
52
|
+
Effect.gen(function* () {
|
|
53
|
+
type ProxiedChannelState =
|
|
54
|
+
| {
|
|
55
|
+
_tag: 'Initial'
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
_tag: 'Pending'
|
|
59
|
+
initiatedVia: 'outgoing-request' | 'incoming-request'
|
|
60
|
+
}
|
|
61
|
+
| ProxiedChannelStateEstablished
|
|
62
|
+
|
|
63
|
+
type ProxiedChannelStateEstablished = {
|
|
64
|
+
_tag: 'Established'
|
|
65
|
+
listenSchema: Schema.Schema<any, any>
|
|
66
|
+
listenQueue: Queue.Queue<any>
|
|
67
|
+
ackMap: Map<string, Deferred.Deferred<void, never>>
|
|
68
|
+
combinedChannelId: string
|
|
69
|
+
}
|
|
106
70
|
|
|
107
|
-
|
|
108
|
-
[channelIdCandidate, otherSideChannelIdCandidate].sort().join('_')
|
|
71
|
+
const channelStateRef = { current: { _tag: 'Initial' } as ProxiedChannelState }
|
|
109
72
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
73
|
+
const debugInfo = {
|
|
74
|
+
kind: 'proxy-channel',
|
|
75
|
+
pendingSends: 0,
|
|
76
|
+
totalSends: 0,
|
|
77
|
+
connectCounter: 0,
|
|
78
|
+
isConnected: false,
|
|
79
|
+
}
|
|
113
80
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
81
|
+
/**
|
|
82
|
+
* We need to unique identify a channel as multiple channels might exist between the same two nodes.
|
|
83
|
+
* We do this by letting each channel end generate a unique id and then combining them in a deterministic way.
|
|
84
|
+
*/
|
|
85
|
+
const channelIdCandidate = nanoid(5)
|
|
86
|
+
yield* Effect.annotateCurrentSpan({ channelIdCandidate })
|
|
117
87
|
|
|
118
|
-
|
|
119
|
-
case 'ProxyChannelRequest': {
|
|
120
|
-
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
88
|
+
const channelSpan = yield* Effect.currentSpan.pipe(Effect.orDie)
|
|
121
89
|
|
|
122
|
-
|
|
123
|
-
yield* SubscriptionRef.set(connectedStateRef, false)
|
|
124
|
-
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
|
|
125
|
-
yield* Effect.spanEvent(`Reconnecting`).pipe(Effect.withParentSpan(channelSpan))
|
|
90
|
+
const connectedStateRef = yield* SubscriptionRef.make<ProxiedChannelStateEstablished | false>(false)
|
|
126
91
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
yield* connectionRequest
|
|
130
|
-
}
|
|
131
|
-
}
|
|
92
|
+
const waitForEstablished = Effect.gen(function* () {
|
|
93
|
+
const state = yield* SubscriptionRef.waitUntil(connectedStateRef, (state) => state !== false)
|
|
132
94
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
reqId: packet.id,
|
|
136
|
-
remainingHops: packet.hops,
|
|
137
|
-
hops: [],
|
|
138
|
-
target,
|
|
139
|
-
source: nodeName,
|
|
140
|
-
channelName,
|
|
141
|
-
combinedChannelId,
|
|
142
|
-
channelIdCandidate,
|
|
143
|
-
}),
|
|
144
|
-
)
|
|
95
|
+
return state as ProxiedChannelStateEstablished
|
|
96
|
+
})
|
|
145
97
|
|
|
146
|
-
|
|
98
|
+
const setStateToEstablished = (channelId: string) =>
|
|
99
|
+
Effect.gen(function* () {
|
|
100
|
+
// TODO avoid "double" `Connected` events (we might call `setStateToEstablished` twice during initial edge)
|
|
101
|
+
yield* Effect.spanEvent(`Connected (${channelId})`).pipe(Effect.withParentSpan(channelSpan))
|
|
102
|
+
channelStateRef.current = {
|
|
103
|
+
_tag: 'Established',
|
|
104
|
+
listenSchema: schema.listen,
|
|
105
|
+
listenQueue,
|
|
106
|
+
ackMap,
|
|
107
|
+
combinedChannelId: channelId,
|
|
147
108
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
109
|
+
yield* SubscriptionRef.set(connectedStateRef, channelStateRef.current)
|
|
110
|
+
debugInfo.isConnected = true
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const edgeRequest = Effect.suspend(() =>
|
|
114
|
+
sendPacket(
|
|
115
|
+
MeshSchema.ProxyChannelRequest.make({ channelName, hops: [], source: nodeName, target, channelIdCandidate }),
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
const getCombinedChannelId = (otherSideChannelIdCandidate: string) =>
|
|
120
|
+
[channelIdCandidate, otherSideChannelIdCandidate].sort().join('_')
|
|
121
|
+
|
|
122
|
+
const earlyPayloadBuffer = yield* Queue.unbounded<typeof MeshSchema.ProxyChannelPayload.Type>().pipe(
|
|
123
|
+
Effect.acquireRelease(Queue.shutdown),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
const processProxyPacket = ({ packet, respondToSender }: ProxyQueueItem) =>
|
|
127
|
+
Effect.gen(function* () {
|
|
128
|
+
// yield* Effect.logDebug(
|
|
129
|
+
// `[${nodeName}] processProxyPacket received: ${packet._tag} from ${packet.source} (reqId: ${packet.id})`,
|
|
130
|
+
// )
|
|
131
|
+
|
|
132
|
+
const otherSideName = packet.source
|
|
133
|
+
const channelKey = `target:${otherSideName}, channelName:${packet.channelName}` satisfies ChannelKey
|
|
134
|
+
const channelState = channelStateRef.current
|
|
135
|
+
|
|
136
|
+
switch (packet._tag) {
|
|
137
|
+
case 'ProxyChannelRequest': {
|
|
138
|
+
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
139
|
+
|
|
140
|
+
// Handle Established state explicitly
|
|
141
|
+
if (channelState._tag === 'Established') {
|
|
142
|
+
// Check if the incoming request is for the *same* channel instance
|
|
143
|
+
if (channelState.combinedChannelId === combinedChannelId) {
|
|
144
|
+
// Already established with the same ID, likely a redundant request.
|
|
145
|
+
// Just respond and stay established.
|
|
146
|
+
// yield* Effect.logDebug(
|
|
147
|
+
// `[${nodeName}] Received redundant ProxyChannelRequest for already established channel instance ${combinedChannelId}. Responding.`,
|
|
148
|
+
// )
|
|
149
|
+
} else {
|
|
150
|
+
// Established, but the incoming request has a different ID.
|
|
151
|
+
// This implies a reconnect scenario where IDs don't match. Reset to Pending and re-initiate.
|
|
152
|
+
yield* Effect.logWarning(
|
|
153
|
+
`[${nodeName}] Received ProxyChannelRequest with different channel ID (${combinedChannelId}) while established with ${channelState.combinedChannelId}. Re-establishing.`,
|
|
154
|
+
)
|
|
155
|
+
yield* SubscriptionRef.set(connectedStateRef, false)
|
|
156
|
+
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
|
|
157
|
+
yield* Effect.spanEvent(`Reconnecting (received conflicting ProxyChannelRequest)`).pipe(
|
|
158
|
+
Effect.withParentSpan(channelSpan),
|
|
159
|
+
)
|
|
160
|
+
debugInfo.isConnected = false
|
|
161
|
+
debugInfo.connectCounter++
|
|
162
|
+
// We need to send our own request as well to complete the handshake for the new ID
|
|
163
|
+
yield* edgeRequest
|
|
164
|
+
}
|
|
165
|
+
} else if (channelState._tag === 'Initial') {
|
|
166
|
+
// Standard initial connection: set to Pending
|
|
167
|
+
yield* SubscriptionRef.set(connectedStateRef, false) // Ensure connectedStateRef is false if we were somehow Initial but it wasn't false
|
|
168
|
+
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'incoming-request' }
|
|
169
|
+
yield* Effect.spanEvent(`Connecting (received ProxyChannelRequest)`).pipe(
|
|
170
|
+
Effect.withParentSpan(channelSpan),
|
|
154
171
|
)
|
|
155
|
-
|
|
156
|
-
|
|
172
|
+
debugInfo.isConnected = false // Should be false already, but ensure consistency
|
|
173
|
+
debugInfo.connectCounter++
|
|
174
|
+
// No need to send edgeRequest here, the response acts as our part of the handshake for the incoming request's ID
|
|
157
175
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
176
|
+
// If state is 'Pending', we are already trying to connect.
|
|
177
|
+
// Just let the response go out, don't change state.
|
|
178
|
+
|
|
179
|
+
// Send the response regardless of the initial state (unless an error occurred)
|
|
180
|
+
yield* respondToSender(
|
|
181
|
+
MeshSchema.ProxyChannelResponseSuccess.make({
|
|
182
|
+
reqId: packet.id,
|
|
183
|
+
remainingHops: packet.hops,
|
|
184
|
+
hops: [],
|
|
185
|
+
target,
|
|
186
|
+
source: nodeName,
|
|
187
|
+
channelName,
|
|
188
|
+
combinedChannelId,
|
|
189
|
+
channelIdCandidate,
|
|
190
|
+
}),
|
|
164
191
|
)
|
|
192
|
+
|
|
193
|
+
return
|
|
165
194
|
}
|
|
195
|
+
case 'ProxyChannelResponseSuccess': {
|
|
196
|
+
if (channelState._tag !== 'Pending') {
|
|
197
|
+
if (
|
|
198
|
+
channelState._tag === 'Established' &&
|
|
199
|
+
channelState.combinedChannelId !== packet.combinedChannelId
|
|
200
|
+
) {
|
|
201
|
+
return shouldNeverHappen(
|
|
202
|
+
`ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
203
|
+
)
|
|
204
|
+
} else if (channelState._tag === 'Established') {
|
|
205
|
+
// yield* Effect.logDebug(`[${nodeName}] Ignoring redundant ResponseSuccess with same ID ${packet.id}`)
|
|
206
|
+
return
|
|
207
|
+
} else {
|
|
208
|
+
yield* Effect.logWarning(
|
|
209
|
+
`[${nodeName}] Ignoring ResponseSuccess ${packet.id} received in unexpected state ${channelState._tag}`,
|
|
210
|
+
)
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const combinedChannelId = getCombinedChannelId(packet.channelIdCandidate)
|
|
216
|
+
if (combinedChannelId !== packet.combinedChannelId) {
|
|
217
|
+
return yield* Effect.die(
|
|
218
|
+
`ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
219
|
+
)
|
|
220
|
+
}
|
|
166
221
|
|
|
167
|
-
|
|
222
|
+
yield* setStateToEstablished(packet.combinedChannelId)
|
|
223
|
+
|
|
224
|
+
const establishedState = channelStateRef.current
|
|
225
|
+
if (establishedState._tag === 'Established') {
|
|
226
|
+
//
|
|
227
|
+
const bufferedPackets = yield* Queue.takeAll(earlyPayloadBuffer)
|
|
228
|
+
// yield* Effect.logDebug(
|
|
229
|
+
// `[${nodeName}] Draining early payload buffer (${bufferedPackets.length}) after ResponseSuccess`,
|
|
230
|
+
// )
|
|
231
|
+
for (const bufferedPacket of bufferedPackets) {
|
|
232
|
+
if (establishedState.combinedChannelId !== bufferedPacket.combinedChannelId) {
|
|
233
|
+
yield* Effect.logWarning(
|
|
234
|
+
`[${nodeName}] Discarding buffered payload ${bufferedPacket.id}: Combined channel ID mismatch during drain. Expected ${establishedState.combinedChannelId}, got ${bufferedPacket.combinedChannelId}`,
|
|
235
|
+
)
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
const decodedMessage = yield* Schema.decodeUnknown(establishedState.listenSchema)(
|
|
239
|
+
bufferedPacket.payload,
|
|
240
|
+
)
|
|
241
|
+
yield* establishedState.listenQueue.pipe(Queue.offer(decodedMessage))
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
yield* Effect.logError(
|
|
245
|
+
`[${nodeName}] State is not Established immediately after setStateToEstablished was called. Cannot drain buffer. State: ${establishedState._tag}`,
|
|
246
|
+
)
|
|
247
|
+
}
|
|
168
248
|
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
case 'ProxyChannelPayload': {
|
|
172
|
-
if (channelState._tag !== 'Established') {
|
|
173
|
-
// return yield* Effect.die(`Not yet connected to ${target}. dropping message`)
|
|
174
|
-
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`, { packet })
|
|
175
249
|
return
|
|
176
250
|
}
|
|
251
|
+
case 'ProxyChannelPayload': {
|
|
252
|
+
if (channelState._tag === 'Established' && channelState.combinedChannelId !== packet.combinedChannelId) {
|
|
253
|
+
return yield* Effect.die(
|
|
254
|
+
`ProxyChannel[${channelKey}]: Expected proxy channel to have the same combinedChannelId as the packet:\n${channelState.combinedChannelId} (channel) === ${packet.combinedChannelId} (packet)`,
|
|
255
|
+
)
|
|
256
|
+
}
|
|
177
257
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
258
|
+
// yield* Effect.logDebug(`[${nodeName}] Received payload reqId: ${packet.id}. Sending Ack.`)
|
|
259
|
+
yield* respondToSender(
|
|
260
|
+
MeshSchema.ProxyChannelPayloadAck.make({
|
|
261
|
+
reqId: packet.id,
|
|
262
|
+
remainingHops: packet.hops,
|
|
263
|
+
hops: [],
|
|
264
|
+
target,
|
|
265
|
+
source: nodeName,
|
|
266
|
+
channelName,
|
|
267
|
+
combinedChannelId:
|
|
268
|
+
channelState._tag === 'Established' ? channelState.combinedChannelId : packet.combinedChannelId,
|
|
269
|
+
}),
|
|
181
270
|
)
|
|
182
|
-
}
|
|
183
271
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}),
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
const decodedMessage = yield* Schema.decodeUnknown(channelState.listenSchema)(packet.payload)
|
|
197
|
-
yield* channelState.listenQueue.pipe(Queue.offer(decodedMessage))
|
|
198
|
-
|
|
199
|
-
return
|
|
200
|
-
}
|
|
201
|
-
case 'ProxyChannelPayloadAck': {
|
|
202
|
-
if (channelState._tag !== 'Established') {
|
|
203
|
-
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
|
|
272
|
+
if (channelState._tag === 'Established') {
|
|
273
|
+
const decodedMessage = yield* Schema.decodeUnknown(channelState.listenSchema)(packet.payload)
|
|
274
|
+
yield* channelState.listenQueue.pipe(Queue.offer(decodedMessage))
|
|
275
|
+
} else {
|
|
276
|
+
// yield* Effect.logDebug(
|
|
277
|
+
// `[${nodeName}] Buffering early payload reqId: ${packet.id} (state: ${channelState._tag})`,
|
|
278
|
+
// )
|
|
279
|
+
yield* Queue.offer(earlyPayloadBuffer, packet)
|
|
280
|
+
}
|
|
204
281
|
return
|
|
205
282
|
}
|
|
283
|
+
case 'ProxyChannelPayloadAck': {
|
|
284
|
+
// yield* Effect.logDebug(`[${nodeName}] Received Ack for reqId: ${packet.reqId}`)
|
|
206
285
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
286
|
+
if (channelState._tag !== 'Established') {
|
|
287
|
+
yield* Effect.spanEvent(`Not yet connected to ${target}. dropping message`)
|
|
288
|
+
yield* Effect.logWarning(
|
|
289
|
+
`[${nodeName}] Received Ack but not established (State: ${channelState._tag}). Dropping Ack for ${packet.reqId}`,
|
|
290
|
+
)
|
|
291
|
+
return
|
|
292
|
+
}
|
|
210
293
|
|
|
211
|
-
|
|
294
|
+
const ack =
|
|
295
|
+
channelState.ackMap.get(packet.reqId) ??
|
|
296
|
+
shouldNeverHappen(`[ProxyChannel[${channelKey}]] Expected ack for ${packet.reqId}`)
|
|
297
|
+
yield* Deferred.succeed(ack, void 0)
|
|
212
298
|
|
|
213
|
-
|
|
299
|
+
channelState.ackMap.delete(packet.reqId)
|
|
214
300
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
default: {
|
|
304
|
+
return casesHandled(packet)
|
|
305
|
+
}
|
|
219
306
|
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
yield* Stream.fromQueue(queue).pipe(
|
|
228
|
-
Stream.tap(processProxyPacket),
|
|
229
|
-
Stream.runDrain,
|
|
230
|
-
Effect.tapCauseLogPretty,
|
|
231
|
-
Effect.forkScoped,
|
|
232
|
-
)
|
|
307
|
+
}).pipe(
|
|
308
|
+
Effect.withSpan(`handleProxyPacket:${packet._tag}:${packet.source}->${packet.target}`, {
|
|
309
|
+
attributes: packetAsOtelAttributes(packet),
|
|
310
|
+
}),
|
|
311
|
+
)
|
|
233
312
|
|
|
234
|
-
|
|
313
|
+
yield* Stream.fromQueue(queue).pipe(
|
|
314
|
+
Stream.tap(processProxyPacket),
|
|
315
|
+
Stream.runDrain,
|
|
316
|
+
Effect.tapCauseLogPretty,
|
|
317
|
+
Effect.forkScoped,
|
|
318
|
+
)
|
|
235
319
|
|
|
236
|
-
|
|
320
|
+
const listenQueue = yield* Queue.unbounded<any>()
|
|
237
321
|
|
|
238
|
-
|
|
322
|
+
yield* Effect.spanEvent(`Connecting`)
|
|
239
323
|
|
|
240
|
-
|
|
241
|
-
// which indicates we already have a connection to the target node
|
|
242
|
-
// const channelState = channelStateRef.current
|
|
243
|
-
{
|
|
244
|
-
if (channelStateRef.current._tag !== 'Initial') {
|
|
245
|
-
return shouldNeverHappen('Expected proxy channel to be Initial')
|
|
246
|
-
}
|
|
324
|
+
const ackMap = new Map<string, Deferred.Deferred<void, never>>()
|
|
247
325
|
|
|
248
|
-
|
|
326
|
+
// check if already established via incoming `ProxyChannelRequest` from other side
|
|
327
|
+
// which indicates we already have a edge to the target node
|
|
328
|
+
// const channelState = channelStateRef.current
|
|
329
|
+
{
|
|
330
|
+
if (channelStateRef.current._tag !== 'Initial') {
|
|
331
|
+
return shouldNeverHappen('Expected proxy channel to be Initial')
|
|
332
|
+
}
|
|
249
333
|
|
|
250
|
-
|
|
334
|
+
channelStateRef.current = { _tag: 'Pending', initiatedVia: 'outgoing-request' }
|
|
251
335
|
|
|
252
|
-
|
|
253
|
-
Stream.tap(() => connectionRequest),
|
|
254
|
-
Stream.runDrain,
|
|
255
|
-
Effect.forkScoped,
|
|
256
|
-
)
|
|
336
|
+
yield* edgeRequest
|
|
257
337
|
|
|
258
|
-
|
|
338
|
+
const retryOnNewEdgeFiber = yield* Stream.fromPubSub(newEdgeAvailablePubSub).pipe(
|
|
339
|
+
Stream.tap(() => edgeRequest),
|
|
340
|
+
Stream.runDrain,
|
|
341
|
+
Effect.forkScoped,
|
|
342
|
+
)
|
|
259
343
|
|
|
260
|
-
|
|
344
|
+
const { combinedChannelId: channelId } = yield* waitForEstablished
|
|
261
345
|
|
|
262
|
-
|
|
263
|
-
}
|
|
346
|
+
yield* Fiber.interrupt(retryOnNewEdgeFiber)
|
|
264
347
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const payload = yield* Schema.encodeUnknown(schema.send)(message)
|
|
268
|
-
const sendFiberHandle = yield* FiberHandle.make<void, never>()
|
|
348
|
+
yield* setStateToEstablished(channelId)
|
|
349
|
+
}
|
|
269
350
|
|
|
270
|
-
|
|
351
|
+
const send = (message: any) =>
|
|
352
|
+
Effect.gen(function* () {
|
|
353
|
+
const payload = yield* Schema.encodeUnknown(schema.send)(message)
|
|
354
|
+
const sendFiberHandle = yield* FiberHandle.make<void, never>()
|
|
271
355
|
|
|
272
|
-
|
|
273
|
-
const { combinedChannelId } = (yield* SubscriptionRef.waitUntil(
|
|
274
|
-
connectedStateRef,
|
|
275
|
-
(channel) => channel !== false,
|
|
276
|
-
)) as ProxiedChannelStateEstablished
|
|
356
|
+
const sentDeferred = yield* Deferred.make<void>()
|
|
277
357
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const ack = yield* Deferred.make<void, never>()
|
|
281
|
-
const packet = MeshSchema.ProxyChannelPayload.make({
|
|
282
|
-
channelName,
|
|
283
|
-
payload,
|
|
284
|
-
hops: [],
|
|
285
|
-
source: nodeName,
|
|
286
|
-
target,
|
|
287
|
-
combinedChannelId,
|
|
288
|
-
})
|
|
289
|
-
ackMap.set(packet.id, ack)
|
|
358
|
+
debugInfo.pendingSends++
|
|
359
|
+
debugInfo.totalSends++
|
|
290
360
|
|
|
291
|
-
|
|
361
|
+
const trySend = Effect.gen(function* () {
|
|
362
|
+
const { combinedChannelId } = (yield* SubscriptionRef.waitUntil(
|
|
363
|
+
connectedStateRef,
|
|
364
|
+
(channel) => channel !== false,
|
|
365
|
+
)) as ProxiedChannelStateEstablished
|
|
292
366
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
367
|
+
const innerSend = Effect.gen(function* () {
|
|
368
|
+
// Note we're re-creating new packets every time otherwise they will be skipped because of `handledIds`
|
|
369
|
+
const ack = yield* Deferred.make<void, never>()
|
|
370
|
+
const packet = MeshSchema.ProxyChannelPayload.make({
|
|
371
|
+
channelName,
|
|
372
|
+
payload,
|
|
373
|
+
hops: [],
|
|
374
|
+
source: nodeName,
|
|
375
|
+
target,
|
|
376
|
+
combinedChannelId,
|
|
377
|
+
})
|
|
378
|
+
// TODO consider handling previous ackMap entries which might leak/fill-up memory
|
|
379
|
+
// as only successful acks are removed from the map
|
|
380
|
+
ackMap.set(packet.id, ack)
|
|
296
381
|
|
|
297
|
-
|
|
298
|
-
}).pipe(Effect.tapErrorCause(Effect.logError))
|
|
382
|
+
yield* sendPacket(packet)
|
|
299
383
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
|
|
303
|
-
Stream.runDrain,
|
|
304
|
-
Effect.fork,
|
|
305
|
-
)
|
|
384
|
+
yield* ack
|
|
385
|
+
yield* Deferred.succeed(sentDeferred, void 0)
|
|
306
386
|
|
|
307
|
-
|
|
387
|
+
debugInfo.pendingSends--
|
|
388
|
+
})
|
|
308
389
|
|
|
309
|
-
|
|
390
|
+
// TODO make this configurable
|
|
391
|
+
// Schedule.exponential(10): 10, 20, 40, 80, 160, 320, ...
|
|
392
|
+
yield* innerSend.pipe(Effect.timeout(100), Effect.retry(Schedule.exponential(10)), Effect.orDie)
|
|
393
|
+
}).pipe(Effect.tapErrorCause(Effect.logError))
|
|
310
394
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
395
|
+
const rerunOnNewChannelFiber = yield* connectedStateRef.changes.pipe(
|
|
396
|
+
Stream.filter((_) => _ === false),
|
|
397
|
+
Stream.tap(() => FiberHandle.run(sendFiberHandle, trySend)),
|
|
398
|
+
Stream.runDrain,
|
|
399
|
+
Effect.fork,
|
|
400
|
+
)
|
|
317
401
|
|
|
318
|
-
|
|
402
|
+
yield* FiberHandle.run(sendFiberHandle, trySend)
|
|
319
403
|
|
|
320
|
-
|
|
404
|
+
yield* sentDeferred
|
|
321
405
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
schema,
|
|
329
|
-
} satisfies WebChannel.WebChannel<any, any>
|
|
406
|
+
yield* Fiber.interrupt(rerunOnNewChannelFiber)
|
|
407
|
+
}).pipe(
|
|
408
|
+
Effect.scoped,
|
|
409
|
+
Effect.withSpan(`sendAckWithRetry:ProxyChannelPayload`),
|
|
410
|
+
Effect.withParentSpan(channelSpan),
|
|
411
|
+
)
|
|
330
412
|
|
|
331
|
-
|
|
332
|
-
|
|
413
|
+
const listen = Stream.fromQueue(listenQueue).pipe(Stream.map(Either.right))
|
|
414
|
+
|
|
415
|
+
const closedDeferred = yield* Deferred.make<void>().pipe(Effect.acquireRelease(Deferred.done(Exit.void)))
|
|
416
|
+
|
|
417
|
+
const runtime = yield* Effect.runtime()
|
|
418
|
+
|
|
419
|
+
const webChannel = {
|
|
420
|
+
[WebChannel.WebChannelSymbol]: WebChannel.WebChannelSymbol,
|
|
421
|
+
send,
|
|
422
|
+
listen,
|
|
423
|
+
closedDeferred,
|
|
424
|
+
supportsTransferables: false,
|
|
425
|
+
schema,
|
|
426
|
+
shutdown: Scope.close(scope, Exit.void),
|
|
427
|
+
debugInfo,
|
|
428
|
+
...({
|
|
429
|
+
debug: {
|
|
430
|
+
ping: (message: string = 'ping') =>
|
|
431
|
+
send(WebChannel.DebugPingMessage.make({ message })).pipe(
|
|
432
|
+
Effect.provide(runtime),
|
|
433
|
+
Effect.tapCauseLogPretty,
|
|
434
|
+
Effect.runFork,
|
|
435
|
+
),
|
|
436
|
+
},
|
|
437
|
+
} as {}),
|
|
438
|
+
} satisfies WebChannel.WebChannel<any, any>
|
|
439
|
+
|
|
440
|
+
return webChannel as WebChannel.WebChannel<any, any>
|
|
441
|
+
}).pipe(Effect.withSpanScoped('makeProxyChannel')),
|
|
442
|
+
)
|