braidfs 0.0.153 → 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 +112 -51
  2. package/index.sh +6 -4
  3. package/package.json +3 -3
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
 
@@ -1083,6 +1082,7 @@ function sync_url(url) {
1083
1082
  braid_text.get(url, {
1084
1083
  signal: ac.signal,
1085
1084
  peer: file_peer,
1085
+ merge_type: 'dt',
1086
1086
  head: true,
1087
1087
  subscribe: () => {
1088
1088
  if (self.ac.signal.aborted) return
@@ -1090,6 +1090,22 @@ function sync_url(url) {
1090
1090
  }
1091
1091
  })
1092
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
+
1093
1109
  // Use braid_text.sync for bidirectional sync with the remote URL
1094
1110
  if (is_external_link) braid_text.sync(url, new URL(url), {
1095
1111
 
@@ -1173,81 +1189,126 @@ async function ensure_path(path) {
1173
1189
  }
1174
1190
  }
1175
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
1176
1200
  function ReconnectRateLimiter(wait_time) {
1177
1201
  var self = {}
1178
1202
 
1179
1203
  self.conns = new Map() // Map<host, Set<url>>
1180
- self.host_to_q = new Map() // Map<host, Array<resolve>>
1181
- self.qs = [] // Array<{host, turns: Array<resolve>, last_turn}>
1182
- 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
1183
1207
  self.timer = null
1184
1208
 
1185
- 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 () {
1186
1216
  if (self.timer) clearTimeout(self.timer)
1187
1217
  self.timer = null
1188
1218
 
1189
1219
  if (!self.qs.length) return
1190
- var now = Date.now()
1191
- var my_last_turn = () => self.conns.size === 0 ? self.last_turn : self.qs[0].last_turn
1192
-
1193
- while (self.qs.length && now >= my_last_turn() + wait_time) {
1194
- var x = self.qs.shift()
1195
- if (!x.turns.length) {
1196
- 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)
1197
1234
  continue
1198
1235
  }
1199
1236
 
1200
- x.turns.shift()()
1201
- x.last_turn = self.last_turn = now
1202
- 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
1203
1243
  }
1204
1244
 
1245
+ // Set timer for the next round of get_turns() to run
1205
1246
  if (self.qs.length)
1206
- self.timer = setTimeout(process, Math.max(0,
1207
- my_last_turn() + wait_time - now))
1247
+ self.timer = setTimeout(give_turns,
1248
+ Math.max(0, ticket_turn_time() - now))
1208
1249
  }
1209
1250
 
1210
1251
  self.get_turn = async (url) => {
1211
- var host = new URL(url).host
1252
+ var hostname = new URL(url).host
1212
1253
 
1213
1254
  // If host has connections, give turn immediately
1214
- if (self.conns.has(host)) return
1255
+ if (self.conns.has(hostname)) return
1215
1256
 
1216
- // 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'})`)
1217
1258
 
1218
- if (!self.host_to_q.has(host)) {
1219
- var turns = []
1220
- self.host_to_q.set(host, turns)
1221
- 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})
1222
1263
  }
1223
- var p = new Promise(resolve => self.host_to_q.get(host).push(resolve))
1224
- 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()
1225
1269
  await p
1226
1270
  }
1227
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.
1228
1274
  self.on_conn = url => {
1229
- var host = new URL(url).host
1230
- if (!self.conns.has(host))
1231
- self.conns.set(host, new Set())
1232
- 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)
1233
1287
 
1234
- // If there are turns waiting for this host, resolve them all immediately
1235
- var turns = self.host_to_q.get(host)
1236
- if (turns) {
1237
- for (var turn of turns) turn()
1238
- 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)
1239
1293
  }
1240
1294
 
1241
- process()
1295
+ give_turns()
1242
1296
  }
1243
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.
1244
1300
  self.on_diss = url => {
1245
- var host = new URL(url).host
1246
- var urls = self.conns.get(host)
1247
- if (urls) {
1248
- urls.delete(url)
1249
- if (urls.size === 0) self.conns.delete(host)
1250
- }
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)
1251
1312
  }
1252
1313
 
1253
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,14 +1,14 @@
1
1
  {
2
2
  "name": "braidfs",
3
- "version": "0.0.153",
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",
7
7
  "homepage": "https://braid.org",
8
8
  "dependencies": {
9
9
  "braid-http": "~1.3.89",
10
- "braid-text": "~0.5.18",
11
- "braid-blob": "~0.0.72",
10
+ "braid-text": "~0.5.20",
11
+ "braid-blob": "~0.0.84",
12
12
  "chokidar": "^4.0.3",
13
13
  "undici": "^7.18.2"
14
14
  },