braidfs 0.0.154 → 0.0.155

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.
Files changed (3) hide show
  1. package/index.js +111 -51
  2. package/index.sh +6 -4
  3. package/package.json +2 -2
package/index.js CHANGED
@@ -541,8 +541,6 @@ function sync_url(url) {
541
541
  var res = await braid_fetch(url, {
542
542
  signal: self.ac.signal,
543
543
  method: 'HEAD',
544
- // version needed to force Merge-Type return header
545
- version: [],
546
544
  // setting subscribe shouldn't work according to spec,
547
545
  // but it does for some old braid-text servers
548
546
  subscribe: !!try_sub,
@@ -565,15 +563,16 @@ function sync_url(url) {
565
563
  }
566
564
  }
567
565
  } catch (e) {
568
- if (e.name !== 'AbortError') throw e
569
- }
570
- if (self.ac.signal.aborted) return
566
+ if (self.ac.signal.aborted) return
571
567
 
572
- // Retry with increasing delays: 1s, 2s, 3s, 3s, 3s...
573
- var delay = Math.min(++retry_count, 3)
574
- console.log(`retrying in ${delay}s: ${url} after error: no merge-type header`)
575
- await new Promise(r => setTimeout(r, delay * 1000))
576
- if (self.ac.signal.aborted) return
568
+ // Retry with increasing delays: 1s, 2s, 3s, 3s, 3s...
569
+ var delay = Math.min(++retry_count, 3)
570
+ console.log(`retrying in ${delay}s: ${url} after error: ${e}`)
571
+ await new Promise(r => setTimeout(r, delay * 1000))
572
+ if (self.ac.signal.aborted) return
573
+ continue
574
+ }
575
+ throw new Error(`no merge-type detected for url: ${url}`)
577
576
  }
578
577
  }
579
578
 
@@ -1091,6 +1090,22 @@ function sync_url(url) {
1091
1090
  }
1092
1091
  })
1093
1092
 
1093
+ // Debugging: Print out changes in memory usage
1094
+ ;(async () => {
1095
+ var before = process.memoryUsage().heapUsed
1096
+ try {
1097
+ var r = await braid_text.get(url, { full_response: true })
1098
+ if (self.ac.signal.aborted) return
1099
+ var after = process.memoryUsage().heapUsed
1100
+ console.log(`${url}: +${((after - before) / 1e6).toFixed(1)} MB`
1101
+ + ` (heap now ${(after / 1e6).toFixed(0)} MB,`
1102
+ + ` body ${(r?.body?.length || 0)} chars)`)
1103
+ } catch (e) {
1104
+ if (e?.name !== 'AbortError')
1105
+ console.log(`DOC-MEM ${url}: probe error ${e}`)
1106
+ }
1107
+ })()
1108
+
1094
1109
  // Use braid_text.sync for bidirectional sync with the remote URL
1095
1110
  if (is_external_link) braid_text.sync(url, new URL(url), {
1096
1111
 
@@ -1174,81 +1189,126 @@ async function ensure_path(path) {
1174
1189
  }
1175
1190
  }
1176
1191
 
1192
+ // Rate Limiting Behavior:
1193
+ //
1194
+ // - If nothing is connected (totally offline), then give one turn per 3s
1195
+ // - Pick first host, and resolve first ticket for that host
1196
+ //
1197
+ // - If any host is connected:
1198
+ // - Give a turn to everyone else with tickets for that host
1199
+ // - Give a turn to one ticket for disconnected hosts, per 3s
1177
1200
  function ReconnectRateLimiter(wait_time) {
1178
1201
  var self = {}
1179
1202
 
1180
1203
  self.conns = new Map() // Map<host, Set<url>>
1181
- self.host_to_q = new Map() // Map<host, Array<resolve>>
1182
- self.qs = [] // Array<{host, turns: Array<resolve>, last_turn}>
1183
- self.last_turn = 0
1204
+ self.host_to_tickets = new Map() // Map<hostname, Array<ticket>>
1205
+ self.qs = [] // Array<{hostname, tickets: Array<ticket>, latest_turn}>
1206
+ self.latest_turn = 0
1184
1207
  self.timer = null
1185
1208
 
1186
- function process() {
1209
+ // Resolves a batch of calls to `await get_turn(url)`.
1210
+ // - It may be just one
1211
+ // - Or it may be many
1212
+ // - Or it may be nothing
1213
+ // - If the wait_time has not expired
1214
+ // - Or if the queue of tickets is empty
1215
+ function give_turns () {
1187
1216
  if (self.timer) clearTimeout(self.timer)
1188
1217
  self.timer = null
1189
1218
 
1190
1219
  if (!self.qs.length) return
1191
- var now = Date.now()
1192
- var my_last_turn = () => self.conns.size === 0 ? self.last_turn : self.qs[0].last_turn
1193
-
1194
- while (self.qs.length && now >= my_last_turn() + wait_time) {
1195
- var x = self.qs.shift()
1196
- if (!x.turns.length) {
1197
- self.host_to_q.delete(x.host)
1220
+ var fully_disconnected = () => self.conns.size === 0
1221
+ var now = Date.now(),
1222
+ ticket_turn_time = () => wait_time + (fully_disconnected()
1223
+ ? self.latest_turn
1224
+ : self.qs[0].latest_turn)
1225
+
1226
+ // Go through the queue of hosts and tickets, to pick some to resolve
1227
+ while (self.qs.length && now >= ticket_turn_time()) {
1228
+ // Remove the first host from top of the queue
1229
+ var host = self.qs.shift()
1230
+
1231
+ // Garbage collect hosts without tickets
1232
+ if (host.tickets.length === 0) {
1233
+ self.host_to_tickets.delete(host.hostname)
1198
1234
  continue
1199
1235
  }
1200
1236
 
1201
- x.turns.shift()()
1202
- x.last_turn = self.last_turn = now
1203
- self.qs.push(x)
1237
+ // Give this ticket holder a turn!
1238
+ host.tickets.shift()()
1239
+
1240
+ // And reinsert the host back to the end
1241
+ self.qs.push(host)
1242
+ host.latest_turn = self.latest_turn = now
1204
1243
  }
1205
1244
 
1245
+ // Set timer for the next round of get_turns() to run
1206
1246
  if (self.qs.length)
1207
- self.timer = setTimeout(process, Math.max(0,
1208
- my_last_turn() + wait_time - now))
1247
+ self.timer = setTimeout(give_turns,
1248
+ Math.max(0, ticket_turn_time() - now))
1209
1249
  }
1210
1250
 
1211
1251
  self.get_turn = async (url) => {
1212
- var host = new URL(url).host
1252
+ var hostname = new URL(url).host
1213
1253
 
1214
1254
  // If host has connections, give turn immediately
1215
- if (self.conns.has(host)) return
1255
+ if (self.conns.has(hostname)) return
1216
1256
 
1217
- // console.log(`throttling reconn to ${url} (no conns yet to ${self.conns.size ? host : 'anything'})`)
1257
+ // console.log(`throttling reconn to ${url} (no conns yet to ${self.conns.size ? hostname : 'anything'})`)
1218
1258
 
1219
- if (!self.host_to_q.has(host)) {
1220
- var turns = []
1221
- self.host_to_q.set(host, turns)
1222
- self.qs.unshift({host, turns, last_turn: 0})
1259
+ if (!self.host_to_tickets.has(hostname)) {
1260
+ var tickets = []
1261
+ self.host_to_tickets.set(hostname, tickets)
1262
+ self.qs.unshift({hostname, tickets, latest_turn: 0})
1223
1263
  }
1224
- var p = new Promise(resolve => self.host_to_q.get(host).push(resolve))
1225
- process()
1264
+
1265
+ var p = new Promise(resolve =>
1266
+ // This resolve function becomes a ticket!
1267
+ self.host_to_tickets.get(hostname).push(resolve))
1268
+ give_turns()
1226
1269
  await p
1227
1270
  }
1228
1271
 
1272
+ // After you get a turn, and connect, it's your responsibility to call
1273
+ // this function to tell the rate limiter that you're online.
1229
1274
  self.on_conn = url => {
1230
- var host = new URL(url).host
1231
- if (!self.conns.has(host))
1232
- self.conns.set(host, new Set())
1233
- self.conns.get(host).add(url)
1275
+ // When a host connects, it's time to give a turn to everyone else
1276
+ // with tickets for that host
1277
+
1278
+ var hostname = new URL(url).host
1279
+
1280
+ // We keep track of the connections to each host so that we know which
1281
+ // ones are online and offline.
1282
+ if (!self.conns.has(hostname))
1283
+ self.conns.set(hostname, new Set()) // Initialize the set
1284
+
1285
+ // Add this connection to our memory!
1286
+ self.conns.get(hostname).add(url)
1234
1287
 
1235
- // If there are turns waiting for this host, resolve them all immediately
1236
- var turns = self.host_to_q.get(host)
1237
- if (turns) {
1238
- for (var turn of turns) turn()
1239
- turns.splice(0, turns.length)
1288
+ // If there are tickets waiting for this host, resolve them all immediately
1289
+ var tickets = self.host_to_tickets.get(hostname)
1290
+ if (tickets) {
1291
+ for (var ticket of tickets) ticket()
1292
+ tickets.splice(0, tickets.length)
1240
1293
  }
1241
1294
 
1242
- process()
1295
+ give_turns()
1243
1296
  }
1244
1297
 
1298
+ // After you get a turn, and disconnect, it's your responsibility to call
1299
+ // this function to tell the rate limiter that you're offline.
1245
1300
  self.on_diss = url => {
1246
- var host = new URL(url).host
1247
- var urls = self.conns.get(host)
1248
- if (urls) {
1249
- urls.delete(url)
1250
- if (urls.size === 0) self.conns.delete(host)
1251
- }
1301
+ var hostname = new URL(url).host
1302
+ var urls = self.conns.get(hostname)
1303
+
1304
+ console.assert(urls, 'ReconnectRateLimiter: out of sync! '
1305
+ + 'Attempt to disconnect from url that is not known to be connected.')
1306
+
1307
+ // Remove this dropped connection from our set!
1308
+ urls.delete(url)
1309
+
1310
+ // And garbage collect the host's connections set if now empty
1311
+ if (urls.size === 0) self.conns.delete(hostname)
1252
1312
  }
1253
1313
 
1254
1314
  return self
package/index.sh CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  # Function to calculate Base64-encoded SHA256 hash
4
4
  calculate_sha256() {
5
- if command -v shasum > /dev/null; then
6
- cat | shasum -a 256 | cut -d ' ' -f 1 | xxd -r -p | base64
5
+ if command -v openssl > /dev/null; then
6
+ openssl dgst -sha256 -binary | openssl base64
7
+ elif command -v shasum > /dev/null; then
8
+ shasum -a 256 | cut -d ' ' -f 1 | xxd -r -p | base64
7
9
  elif command -v sha256sum > /dev/null; then
8
- cat | sha256sum | cut -d ' ' -f 1 | xxd -r -p | base64
10
+ sha256sum | cut -d ' ' -f 1 | xxd -r -p | base64
9
11
  else
10
- echo "Error: Neither shasum nor sha256sum is available." >&2
12
+ echo "Error: No SHA256 tool available." >&2
11
13
  exit 1
12
14
  fi
13
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braidfs",
3
- "version": "0.0.154",
3
+ "version": "0.0.155",
4
4
  "description": "braid technology synchronizing files and webpages",
5
5
  "author": "Braid Working Group",
6
6
  "repository": "braid-org/braidfs",
@@ -8,7 +8,7 @@
8
8
  "dependencies": {
9
9
  "braid-http": "~1.3.89",
10
10
  "braid-text": "~0.5.20",
11
- "braid-blob": "~0.0.72",
11
+ "braid-blob": "~0.0.84",
12
12
  "chokidar": "^4.0.3",
13
13
  "undici": "^7.18.2"
14
14
  },