braid-text 0.2.104 → 0.2.105
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/index.js +93 -81
- package/package.json +1 -1
- package/test/tests.js +42 -0
package/index.js
CHANGED
|
@@ -93,45 +93,27 @@ function create_braid_text() {
|
|
|
93
93
|
return frontier.sort()
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
var closed
|
|
97
|
-
var disconnect = () => {}
|
|
98
96
|
options.signal?.addEventListener('abort', () => {
|
|
99
|
-
|
|
100
97
|
// DEBUGGING HACK ID: L04LPFHQ1M -- INVESTIGATING DISCONNECTS
|
|
101
98
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text sync abort')
|
|
102
|
-
|
|
103
|
-
closed = true
|
|
104
|
-
disconnect()
|
|
105
99
|
})
|
|
106
100
|
|
|
107
101
|
var local_first_put
|
|
108
102
|
var local_first_put_promise = new Promise(done => local_first_put = done)
|
|
109
103
|
|
|
110
|
-
|
|
111
|
-
function handle_error(_e) {
|
|
112
|
-
|
|
104
|
+
reconnector(options.signal, (_e, count) => {
|
|
113
105
|
// DEBUGGING HACK ID: L04LPFHQ1M -- INVESTIGATING DISCONNECTS
|
|
114
106
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text handle_error: ' + _e)
|
|
115
107
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
setTimeout(connect, delay)
|
|
121
|
-
waitTime = Math.min(waitTime + 1, 3)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
connect()
|
|
125
|
-
async function connect() {
|
|
108
|
+
var delay = Math.min(count, 3) * 1000
|
|
109
|
+
console.log(`disconnected from ${b}, retrying in ${delay}ms`)
|
|
110
|
+
return delay
|
|
111
|
+
}, async (signal, handle_error) => {
|
|
126
112
|
// DEBUGGING HACK ID: L04LPFHQ1M -- INVESTIGATING DISCONNECTS
|
|
127
113
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text connect before on_pre_connect')
|
|
128
114
|
|
|
129
115
|
if (options.on_pre_connect) await options.on_pre_connect()
|
|
130
|
-
|
|
131
|
-
if (closed) return
|
|
132
|
-
|
|
133
|
-
var ac = new AbortController()
|
|
134
|
-
disconnect = () => ac.abort()
|
|
116
|
+
if (signal.aborted) return
|
|
135
117
|
|
|
136
118
|
// DEBUGGING HACK ID: L04LPFHQ1M -- INVESTIGATING DISCONNECTS
|
|
137
119
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text connect before fork-point stuff')
|
|
@@ -140,11 +122,13 @@ function create_braid_text() {
|
|
|
140
122
|
// fork-point
|
|
141
123
|
async function check_version(version) {
|
|
142
124
|
var r = await braid_fetch(b.href, {
|
|
143
|
-
signal
|
|
125
|
+
signal,
|
|
144
126
|
method: "HEAD",
|
|
145
127
|
version,
|
|
146
128
|
headers: get_headers
|
|
147
129
|
})
|
|
130
|
+
if (signal.aborted) return
|
|
131
|
+
|
|
148
132
|
if (!r.ok && r.status !== 309 && r.status !== 500)
|
|
149
133
|
throw new Error(`unexpected HEAD status: ${r.status}`)
|
|
150
134
|
return r.ok
|
|
@@ -160,9 +144,12 @@ function create_braid_text() {
|
|
|
160
144
|
// see if remote has the fork point
|
|
161
145
|
if (resource.meta.fork_point &&
|
|
162
146
|
!(await check_version(resource.meta.fork_point))) {
|
|
147
|
+
if (signal.aborted) return
|
|
148
|
+
|
|
163
149
|
resource.meta.fork_point = null
|
|
164
150
|
resource.change_meta()
|
|
165
151
|
}
|
|
152
|
+
if (signal.aborted) return
|
|
166
153
|
|
|
167
154
|
// otherwise let's binary search for new fork point..
|
|
168
155
|
if (!resource.meta.fork_point) {
|
|
@@ -180,6 +167,8 @@ function create_braid_text() {
|
|
|
180
167
|
var i = Math.floor((min + max)/2)
|
|
181
168
|
var version = [events[i]]
|
|
182
169
|
if (await check_version(version)) {
|
|
170
|
+
if (signal.aborted) return
|
|
171
|
+
|
|
183
172
|
min = i
|
|
184
173
|
resource.meta.fork_point = version
|
|
185
174
|
} else max = i
|
|
@@ -192,7 +181,7 @@ function create_braid_text() {
|
|
|
192
181
|
var max_in_flight = 10
|
|
193
182
|
var send_pump_lock = 0
|
|
194
183
|
var temp_acs = new Set()
|
|
195
|
-
|
|
184
|
+
signal.addEventListener('abort', () => {
|
|
196
185
|
for (var t of temp_acs) t.abort()
|
|
197
186
|
})
|
|
198
187
|
|
|
@@ -201,11 +190,12 @@ function create_braid_text() {
|
|
|
201
190
|
// DEBUGGING HACK ID: L04LPFHQ1M -- INVESTIGATING DISCONNECTS
|
|
202
191
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text send_out')
|
|
203
192
|
|
|
204
|
-
update.signal =
|
|
193
|
+
update.signal = signal
|
|
205
194
|
update.dont_retry = true
|
|
206
195
|
if (options.peer) update.peer = options.peer
|
|
207
196
|
update.headers = put_headers
|
|
208
197
|
var x = await braid_text.put(b, update)
|
|
198
|
+
if (signal.aborted) return
|
|
209
199
|
|
|
210
200
|
// DEBUGGING HACK ID: L04LPFHQ1M -- INVESTIGATING DISCONNECTS
|
|
211
201
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text send_out result: ' + x.ok)
|
|
@@ -219,61 +209,52 @@ function create_braid_text() {
|
|
|
219
209
|
}
|
|
220
210
|
|
|
221
211
|
async function send_pump() {
|
|
212
|
+
if (signal.aborted) return
|
|
222
213
|
send_pump_lock++
|
|
223
214
|
if (send_pump_lock > 1) return
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
temp_ac.abort()
|
|
244
|
-
temp_acs.delete(temp_ac)
|
|
245
|
-
}
|
|
246
|
-
while (q.length && in_flight.size < max_in_flight) {
|
|
247
|
-
let u = q.shift()
|
|
248
|
-
if (!u.version?.length) continue
|
|
249
|
-
in_flight.set(u.version[0], u);
|
|
250
|
-
(async () => {
|
|
251
|
-
try {
|
|
252
|
-
if (closed) return
|
|
253
|
-
await send_out(u)
|
|
254
|
-
if (closed) return
|
|
255
|
-
in_flight.delete(u.version[0])
|
|
256
|
-
setTimeout(send_pump, 0)
|
|
257
|
-
} catch (e) {
|
|
258
|
-
if (e.name === 'AbortError') {
|
|
259
|
-
// ignore
|
|
260
|
-
} else handle_error(e)
|
|
261
|
-
}
|
|
262
|
-
})()
|
|
263
|
-
}
|
|
264
|
-
} finally {
|
|
265
|
-
var retry = send_pump_lock > 1
|
|
266
|
-
send_pump_lock = 0
|
|
267
|
-
if (retry) setTimeout(send_pump, 0)
|
|
215
|
+
if (in_flight.size >= max_in_flight) return
|
|
216
|
+
if (!q.length) {
|
|
217
|
+
// Extend frontier based on in-flight updates
|
|
218
|
+
var frontier = resource.meta.fork_point || []
|
|
219
|
+
for (var u of in_flight.values())
|
|
220
|
+
frontier = extend_frontier(frontier, u.version, u.parents)
|
|
221
|
+
|
|
222
|
+
var temp_ac = new AbortController()
|
|
223
|
+
temp_acs.add(temp_ac)
|
|
224
|
+
await braid_text.get(a, {
|
|
225
|
+
signal: temp_ac.signal,
|
|
226
|
+
parents: frontier,
|
|
227
|
+
merge_type: 'dt',
|
|
228
|
+
peer: options.peer,
|
|
229
|
+
subscribe: u => u.version?.length && q.push(u)
|
|
230
|
+
})
|
|
231
|
+
if (signal.aborted) return
|
|
232
|
+
temp_ac.abort()
|
|
233
|
+
temp_acs.delete(temp_ac)
|
|
268
234
|
}
|
|
235
|
+
while (q.length && in_flight.size < max_in_flight) {
|
|
236
|
+
let u = q.shift()
|
|
237
|
+
if (!u.version?.length) continue
|
|
238
|
+
in_flight.set(u.version[0], u)
|
|
239
|
+
void (async () => {
|
|
240
|
+
try {
|
|
241
|
+
await send_out(u)
|
|
242
|
+
if (signal.aborted) return
|
|
243
|
+
in_flight.delete(u.version[0])
|
|
244
|
+
setTimeout(send_pump, 0)
|
|
245
|
+
} catch (e) { handle_error(e) }
|
|
246
|
+
})()
|
|
247
|
+
}
|
|
248
|
+
if (send_pump_lock > 1) setTimeout(send_pump, 0)
|
|
249
|
+
send_pump_lock = 0
|
|
269
250
|
}
|
|
270
251
|
|
|
271
252
|
var a_ops = {
|
|
272
|
-
signal
|
|
253
|
+
signal,
|
|
273
254
|
merge_type: 'dt',
|
|
274
255
|
peer: options.peer,
|
|
275
256
|
subscribe: update => {
|
|
276
|
-
if (
|
|
257
|
+
if (signal.aborted) return
|
|
277
258
|
if (update.version?.length) {
|
|
278
259
|
q.push(update)
|
|
279
260
|
send_pump()
|
|
@@ -293,7 +274,7 @@ function create_braid_text() {
|
|
|
293
274
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text before GET/sub')
|
|
294
275
|
|
|
295
276
|
var b_ops = {
|
|
296
|
-
signal
|
|
277
|
+
signal,
|
|
297
278
|
dont_retry: true,
|
|
298
279
|
headers: { ...get_headers, 'Merge-Type': 'dt', 'accept-encoding': 'updates(dt)' },
|
|
299
280
|
parents: resource.meta.fork_point,
|
|
@@ -312,6 +293,7 @@ function create_braid_text() {
|
|
|
312
293
|
|
|
313
294
|
// Wait for remote_res to be available
|
|
314
295
|
await remote_res_promise
|
|
296
|
+
if (signal.aborted) return
|
|
315
297
|
|
|
316
298
|
// Check if this is a dt-encoded update
|
|
317
299
|
if (update.extra_headers?.encoding === 'dt') {
|
|
@@ -320,9 +302,11 @@ function create_braid_text() {
|
|
|
320
302
|
body: update.body,
|
|
321
303
|
transfer_encoding: 'dt'
|
|
322
304
|
})
|
|
305
|
+
if (signal.aborted) return
|
|
323
306
|
if (cv) extend_fork_point({ version: JSON.parse(`[${cv}]`), parents: resource.meta.fork_point || [] })
|
|
324
307
|
} else {
|
|
325
308
|
await braid_text.put(a, update)
|
|
309
|
+
if (signal.aborted) return
|
|
326
310
|
if (update.version) extend_fork_point(update)
|
|
327
311
|
}
|
|
328
312
|
},
|
|
@@ -333,6 +317,7 @@ function create_braid_text() {
|
|
|
333
317
|
}
|
|
334
318
|
// Handle case where remote doesn't exist yet - wait for local to create it
|
|
335
319
|
remote_res = await braid_text.get(b, b_ops)
|
|
320
|
+
if (signal.aborted) return
|
|
336
321
|
|
|
337
322
|
// DEBUGGING HACK ID: L04LPFHQ1M -- INVESTIGATING DISCONNECTS
|
|
338
323
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text after GET/sub: ' + remote_res?.status)
|
|
@@ -341,16 +326,12 @@ function create_braid_text() {
|
|
|
341
326
|
if (remote_res === null) {
|
|
342
327
|
// Remote doesn't exist yet, wait for local to put something
|
|
343
328
|
await local_first_put_promise
|
|
344
|
-
|
|
345
|
-
connect()
|
|
346
|
-
return
|
|
329
|
+
return handle_error(new Error('try again'))
|
|
347
330
|
}
|
|
348
331
|
options.on_res?.(remote_res)
|
|
349
332
|
// on_error will call handle_error when connection drops
|
|
350
|
-
} catch (e) {
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
}
|
|
333
|
+
} catch (e) { handle_error(e) }
|
|
334
|
+
})
|
|
354
335
|
}
|
|
355
336
|
|
|
356
337
|
braid_text.serve = async (req, res, options = {}) => {
|
|
@@ -2823,6 +2804,37 @@ function create_braid_text() {
|
|
|
2823
2804
|
return within_fiber.chains[id] = curr
|
|
2824
2805
|
}
|
|
2825
2806
|
|
|
2807
|
+
// Calls func(inner_signal, reconnect) immediately and handles reconnection.
|
|
2808
|
+
// - inner_signal: AbortSignal that aborts when reconnect() is called or outter_signal aborts
|
|
2809
|
+
// - reconnect(error): call this to trigger a reconnection after get_delay(error, count) ms
|
|
2810
|
+
// - Multiple/rapid reconnect() calls are safe - only one reconnection will be scheduled
|
|
2811
|
+
// - If outter_signal aborts, no further calls to func will occur
|
|
2812
|
+
function reconnector(outter_signal, get_delay, func) {
|
|
2813
|
+
if (outter_signal?.aborted) return
|
|
2814
|
+
|
|
2815
|
+
var current_inner_ac = null
|
|
2816
|
+
outter_signal?.addEventListener('abort', () =>
|
|
2817
|
+
current_inner_ac?.abort())
|
|
2818
|
+
|
|
2819
|
+
var reconnect_count = 0
|
|
2820
|
+
connect()
|
|
2821
|
+
function connect() {
|
|
2822
|
+
if (outter_signal?.aborted) return
|
|
2823
|
+
|
|
2824
|
+
var ac = current_inner_ac = new AbortController()
|
|
2825
|
+
var inner_signal = ac.signal
|
|
2826
|
+
|
|
2827
|
+
func(inner_signal, (e) => {
|
|
2828
|
+
if (outter_signal?.aborted ||
|
|
2829
|
+
inner_signal.aborted) return
|
|
2830
|
+
|
|
2831
|
+
ac.abort()
|
|
2832
|
+
var delay = get_delay(e, ++reconnect_count)
|
|
2833
|
+
setTimeout(connect, delay)
|
|
2834
|
+
})
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2826
2838
|
braid_text.get_resource = get_resource
|
|
2827
2839
|
|
|
2828
2840
|
braid_text.db_folder_init = db_folder_init
|
package/package.json
CHANGED
package/test/tests.js
CHANGED
|
@@ -2849,6 +2849,48 @@ runTest(
|
|
|
2849
2849
|
'correctly threw error'
|
|
2850
2850
|
)
|
|
2851
2851
|
|
|
2852
|
+
// Tests for reconnector/sync edge cases
|
|
2853
|
+
|
|
2854
|
+
runTest(
|
|
2855
|
+
"test braid_text.sync remote null triggers local_first_put_promise path",
|
|
2856
|
+
async () => {
|
|
2857
|
+
var local_key = 'test-local-' + Math.random().toString(36).slice(2)
|
|
2858
|
+
|
|
2859
|
+
// Use the /404 endpoint which always returns 404 (null from braid_text.get)
|
|
2860
|
+
var r = await braid_fetch(`/eval`, {
|
|
2861
|
+
method: 'PUT',
|
|
2862
|
+
body: `void (async () => {
|
|
2863
|
+
var ac = new AbortController()
|
|
2864
|
+
var reconnect_count = 0
|
|
2865
|
+
|
|
2866
|
+
braid_text.sync('/${local_key}', new URL('http://localhost:8889/404'), {
|
|
2867
|
+
signal: ac.signal,
|
|
2868
|
+
on_pre_connect: () => {
|
|
2869
|
+
reconnect_count++
|
|
2870
|
+
if (reconnect_count >= 2) {
|
|
2871
|
+
ac.abort()
|
|
2872
|
+
res.end('reconnected after local put')
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
})
|
|
2876
|
+
|
|
2877
|
+
// Wait a bit then put something locally - this should trigger
|
|
2878
|
+
// the local_first_put_promise to resolve and cause reconnect
|
|
2879
|
+
await new Promise(done => setTimeout(done, 100))
|
|
2880
|
+
await braid_text.put('/${local_key}', { body: 'local data' })
|
|
2881
|
+
|
|
2882
|
+
// Wait for reconnect
|
|
2883
|
+
await new Promise(done => setTimeout(done, 2000))
|
|
2884
|
+
res.end('did not reconnect')
|
|
2885
|
+
})()`
|
|
2886
|
+
})
|
|
2887
|
+
if (!r.ok) return 'eval failed: ' + r.status
|
|
2888
|
+
|
|
2889
|
+
return await r.text()
|
|
2890
|
+
},
|
|
2891
|
+
'reconnected after local put'
|
|
2892
|
+
)
|
|
2893
|
+
|
|
2852
2894
|
}
|
|
2853
2895
|
|
|
2854
2896
|
// Export for both Node.js and browser environments
|