@evanp/activitypub-bot 0.45.7 → 0.45.9

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/CHANGELOG.md CHANGED
@@ -9,7 +9,19 @@ and this project adheres to
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
- ## [0.45.6] - 2026-04-27
12
+ ## [0.45.9] - 2026-04-29
13
+
14
+ ### Fixed
15
+
16
+ - Don't infinitely retry on 429 status code.
17
+
18
+ ## [0.45.8] - 2026-04-29
19
+
20
+ ### Added
21
+
22
+ - Log memory usage.
23
+
24
+ ## [0.45.7] - 2026-04-27
13
25
 
14
26
  ### Fixed
15
27
 
package/lib/app.js CHANGED
@@ -2,6 +2,7 @@ import http from 'node:http'
2
2
  import { resolve, dirname } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { randomUUID } from 'node:crypto'
5
+ import v8 from 'node:v8'
5
6
 
6
7
  import { Sequelize } from 'sequelize'
7
8
  import express from 'express'
@@ -69,6 +70,19 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
69
70
  level: logLevel
70
71
  })
71
72
  logger.debug('Logger initialized')
73
+ const memoryStatus = (message = 'memory', level = 'debug') => {
74
+ const mem = process.memoryUsage()
75
+ const heap = v8.getHeapStatistics()
76
+ logger[level]({
77
+ rss: mem.rss,
78
+ heapUsed: mem.heapUsed,
79
+ heapTotal: mem.heapTotal,
80
+ external: mem.external,
81
+ arrayBuffers: mem.arrayBuffers,
82
+ heapSizeLimit: heap.heap_size_limit
83
+ }, message)
84
+ }
85
+ memoryStatus('makeApp started')
72
86
  const connection = databaseUrl === 'sqlite::memory:' || databaseUrl === 'sqlite::memory'
73
87
  ? new Sequelize({ dialect: 'sqlite', storage: ':memory:', logging: false })
74
88
  : new Sequelize(databaseUrl, { logging: false })
@@ -132,6 +146,8 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
132
146
  await redisClient.connect()
133
147
  }
134
148
 
149
+ memoryStatus('services initialized')
150
+
135
151
  await Promise.all(
136
152
  Object.entries(bots).map(([key, bot]) => bot.initialize(
137
153
  new BotContext(
@@ -169,6 +185,8 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
169
185
  bots
170
186
  ))
171
187
 
188
+ memoryStatus('bots initialized')
189
+
172
190
  const { workers: deliveryWorkers, runs: deliveryWorkerRuns } = createWorkers(logger, deliveryWorkerCount, DeliveryWorker, jobQueue, logger, { actorStorage, activityHandler, bots })
173
191
  const { workers: distributionWorkers, runs: distributionWorkerRuns } = createWorkers(logger, distributionWorkerCount, DistributionWorker, jobQueue, logger, { client })
174
192
  const { workers: fanoutWorkers, runs: fanoutWorkerRuns } = createWorkers(logger, fanoutWorkerCount, FanoutWorker, jobQueue, logger, { distributor })
@@ -179,6 +197,11 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
179
197
  logger.error({ err }, 'unexpected error in job reaper')
180
198
  })
181
199
 
200
+ memoryStatus('workers initialized')
201
+
202
+ const memInterval = setInterval(() => memoryStatus('memory', 'info'), 60_000)
203
+ memInterval.unref()
204
+
182
205
  const app = express()
183
206
 
184
207
  app.locals = {
@@ -415,8 +438,11 @@ export async function makeApp ({ databaseUrl, origin, bots, logLevel = 'silent',
415
438
  }
416
439
  logger.info('Closing database connection')
417
440
  await connection.close()
441
+ clearInterval(memInterval)
418
442
  logger.info('Done')
419
443
  }
420
444
 
445
+ memoryStatus('app initialized', 'info')
446
+
421
447
  return app
422
448
  }
@@ -44,13 +44,21 @@ export class DistributionWorker extends Worker {
44
44
  'Could not deliver activity due to client error'
45
45
  )
46
46
  if ([408, 425, 429].includes(err.status)) {
47
- this._logger.debug(
48
- { err, activity: activity.id, inbox },
49
- 'Retrying on recoverable status'
50
- )
51
- const recoverable = new RecoverableError(err.message)
52
- recoverable.delay = this.#retryDelay(err.headers, attempts)
53
- throw recoverable
47
+ if (attempts >= DistributionWorker.#MAX_ATTEMPTS) {
48
+ this._logger.warn(
49
+ { err, activity: activity.id, inbox, attempts },
50
+ 'Could not deliver activity due to client error; no more attempts'
51
+ )
52
+ throw err
53
+ } else {
54
+ this._logger.debug(
55
+ { err, activity: activity.id, inbox },
56
+ 'Retrying on recoverable status'
57
+ )
58
+ const recoverable = new RecoverableError(err.message)
59
+ recoverable.delay = this.#retryDelay(err.headers, attempts)
60
+ throw recoverable
61
+ }
54
62
  } else {
55
63
  throw err
56
64
  }
@@ -87,6 +95,12 @@ export class DistributionWorker extends Worker {
87
95
  }
88
96
 
89
97
  #retryDelay (headers, attempts) {
98
+ const headerDelay = this.#headerDelay(headers)
99
+ const attemptsDelay = this.#attemptsDelay(attempts)
100
+ return Math.max(headerDelay, attemptsDelay)
101
+ }
102
+
103
+ #headerDelay (headers) {
90
104
  if (headers?.['retry-after']) {
91
105
  this._logger.debug('using retry-after header')
92
106
  const retryAfter = headers['retry-after']
@@ -96,7 +110,10 @@ export class DistributionWorker extends Worker {
96
110
  return new Date(retryAfter) - Date.now()
97
111
  }
98
112
  }
99
- this._logger.debug('exponential backoff')
113
+ return 0
114
+ }
115
+
116
+ #attemptsDelay (attempts) {
100
117
  return Math.round((2 ** (attempts - 1) * 1000) * (0.5 + Math.random()))
101
118
  }
102
119
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evanp/activitypub-bot",
3
- "version": "0.45.7",
3
+ "version": "0.45.9",
4
4
  "description": "server-side ActivityPub bot framework",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",