braid-text 0.2.104 → 0.2.106
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 +94 -80
- 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,54 @@ 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
|
-
peer: options.peer,
|
|
240
|
-
subscribe: u => u.version?.length && q.push(u)
|
|
241
|
-
}
|
|
242
|
-
await braid_text.get(a, temp_ops)
|
|
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
|
-
})()
|
|
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
|
+
var get_options = {
|
|
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)
|
|
263
230
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
231
|
+
await braid_text.get(a, get_options)
|
|
232
|
+
await get_options.my_subscribe_chain
|
|
233
|
+
if (signal.aborted) return
|
|
234
|
+
temp_ac.abort()
|
|
235
|
+
temp_acs.delete(temp_ac)
|
|
236
|
+
}
|
|
237
|
+
while (q.length && in_flight.size < max_in_flight) {
|
|
238
|
+
let u = q.shift()
|
|
239
|
+
if (!u.version?.length) continue
|
|
240
|
+
in_flight.set(u.version[0], u)
|
|
241
|
+
void (async () => {
|
|
242
|
+
try {
|
|
243
|
+
await send_out(u)
|
|
244
|
+
if (signal.aborted) return
|
|
245
|
+
in_flight.delete(u.version[0])
|
|
246
|
+
setTimeout(send_pump, 0)
|
|
247
|
+
} catch (e) { handle_error(e) }
|
|
248
|
+
})()
|
|
268
249
|
}
|
|
250
|
+
if (send_pump_lock > 1) setTimeout(send_pump, 0)
|
|
251
|
+
send_pump_lock = 0
|
|
269
252
|
}
|
|
270
253
|
|
|
271
254
|
var a_ops = {
|
|
272
|
-
signal
|
|
255
|
+
signal,
|
|
273
256
|
merge_type: 'dt',
|
|
274
257
|
peer: options.peer,
|
|
275
258
|
subscribe: update => {
|
|
276
|
-
if (
|
|
259
|
+
if (signal.aborted) return
|
|
277
260
|
if (update.version?.length) {
|
|
278
261
|
q.push(update)
|
|
279
262
|
send_pump()
|
|
@@ -293,7 +276,7 @@ function create_braid_text() {
|
|
|
293
276
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text before GET/sub')
|
|
294
277
|
|
|
295
278
|
var b_ops = {
|
|
296
|
-
signal
|
|
279
|
+
signal,
|
|
297
280
|
dont_retry: true,
|
|
298
281
|
headers: { ...get_headers, 'Merge-Type': 'dt', 'accept-encoding': 'updates(dt)' },
|
|
299
282
|
parents: resource.meta.fork_point,
|
|
@@ -312,6 +295,7 @@ function create_braid_text() {
|
|
|
312
295
|
|
|
313
296
|
// Wait for remote_res to be available
|
|
314
297
|
await remote_res_promise
|
|
298
|
+
if (signal.aborted) return
|
|
315
299
|
|
|
316
300
|
// Check if this is a dt-encoded update
|
|
317
301
|
if (update.extra_headers?.encoding === 'dt') {
|
|
@@ -320,9 +304,11 @@ function create_braid_text() {
|
|
|
320
304
|
body: update.body,
|
|
321
305
|
transfer_encoding: 'dt'
|
|
322
306
|
})
|
|
307
|
+
if (signal.aborted) return
|
|
323
308
|
if (cv) extend_fork_point({ version: JSON.parse(`[${cv}]`), parents: resource.meta.fork_point || [] })
|
|
324
309
|
} else {
|
|
325
310
|
await braid_text.put(a, update)
|
|
311
|
+
if (signal.aborted) return
|
|
326
312
|
if (update.version) extend_fork_point(update)
|
|
327
313
|
}
|
|
328
314
|
},
|
|
@@ -333,6 +319,7 @@ function create_braid_text() {
|
|
|
333
319
|
}
|
|
334
320
|
// Handle case where remote doesn't exist yet - wait for local to create it
|
|
335
321
|
remote_res = await braid_text.get(b, b_ops)
|
|
322
|
+
if (signal.aborted) return
|
|
336
323
|
|
|
337
324
|
// DEBUGGING HACK ID: L04LPFHQ1M -- INVESTIGATING DISCONNECTS
|
|
338
325
|
options.do_investigating_disconnects_log_L04LPFHQ1M?.(a, 'braid-text after GET/sub: ' + remote_res?.status)
|
|
@@ -341,16 +328,12 @@ function create_braid_text() {
|
|
|
341
328
|
if (remote_res === null) {
|
|
342
329
|
// Remote doesn't exist yet, wait for local to put something
|
|
343
330
|
await local_first_put_promise
|
|
344
|
-
|
|
345
|
-
connect()
|
|
346
|
-
return
|
|
331
|
+
return handle_error(new Error('try again'))
|
|
347
332
|
}
|
|
348
333
|
options.on_res?.(remote_res)
|
|
349
334
|
// on_error will call handle_error when connection drops
|
|
350
|
-
} catch (e) {
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
}
|
|
335
|
+
} catch (e) { handle_error(e) }
|
|
336
|
+
})
|
|
354
337
|
}
|
|
355
338
|
|
|
356
339
|
braid_text.serve = async (req, res, options = {}) => {
|
|
@@ -2823,6 +2806,37 @@ function create_braid_text() {
|
|
|
2823
2806
|
return within_fiber.chains[id] = curr
|
|
2824
2807
|
}
|
|
2825
2808
|
|
|
2809
|
+
// Calls func(inner_signal, reconnect) immediately and handles reconnection.
|
|
2810
|
+
// - inner_signal: AbortSignal that aborts when reconnect() is called or outter_signal aborts
|
|
2811
|
+
// - reconnect(error): call this to trigger a reconnection after get_delay(error, count) ms
|
|
2812
|
+
// - Multiple/rapid reconnect() calls are safe - only one reconnection will be scheduled
|
|
2813
|
+
// - If outter_signal aborts, no further calls to func will occur
|
|
2814
|
+
function reconnector(outter_signal, get_delay, func) {
|
|
2815
|
+
if (outter_signal?.aborted) return
|
|
2816
|
+
|
|
2817
|
+
var current_inner_ac = null
|
|
2818
|
+
outter_signal?.addEventListener('abort', () =>
|
|
2819
|
+
current_inner_ac?.abort())
|
|
2820
|
+
|
|
2821
|
+
var reconnect_count = 0
|
|
2822
|
+
connect()
|
|
2823
|
+
function connect() {
|
|
2824
|
+
if (outter_signal?.aborted) return
|
|
2825
|
+
|
|
2826
|
+
var ac = current_inner_ac = new AbortController()
|
|
2827
|
+
var inner_signal = ac.signal
|
|
2828
|
+
|
|
2829
|
+
func(inner_signal, (e) => {
|
|
2830
|
+
if (outter_signal?.aborted ||
|
|
2831
|
+
inner_signal.aborted) return
|
|
2832
|
+
|
|
2833
|
+
ac.abort()
|
|
2834
|
+
var delay = get_delay(e, ++reconnect_count)
|
|
2835
|
+
setTimeout(connect, delay)
|
|
2836
|
+
})
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2826
2840
|
braid_text.get_resource = get_resource
|
|
2827
2841
|
|
|
2828
2842
|
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
|