|
|
@@ -17,6 +17,8 @@ class Worker {
|
|
|
|
|
|
this.handlers = {}
|
|
|
this.running = false
|
|
|
+ this._consuming = false
|
|
|
+ this._consumeTag = null
|
|
|
|
|
|
this.taskQueue = TASK_QUEUE
|
|
|
this.resultQueue = 'runforge_task_result_queue'
|
|
|
@@ -166,7 +168,9 @@ class Worker {
|
|
|
retry: options.retry ?? 0
|
|
|
}
|
|
|
|
|
|
- await channel.sendToQueue(
|
|
|
+ // 这里不要直接用传入的 channel:断线后它可能已 close
|
|
|
+ await mq.sendToQueueSafe(
|
|
|
+ this.channelName,
|
|
|
this.taskQueue,
|
|
|
Buffer.from(JSON.stringify(payload)),
|
|
|
{ persistent: true, contentType: 'application/json' }
|
|
|
@@ -732,13 +736,11 @@ class Worker {
|
|
|
if (noticeType === 'bot' && user.bot_umo) {
|
|
|
const ch = await mq.getChannel(this.noticeQueue)
|
|
|
await ch.assertQueue(this.noticeQueue, { durable: true })
|
|
|
- ch.sendToQueue(
|
|
|
+ await mq.sendToQueueSafe(
|
|
|
+ this.noticeQueue,
|
|
|
this.noticeQueue,
|
|
|
Buffer.from(JSON.stringify(payload)),
|
|
|
- {
|
|
|
- persistent: true,
|
|
|
- contentType: 'application/json'
|
|
|
- }
|
|
|
+ { persistent: true, contentType: 'application/json' }
|
|
|
)
|
|
|
return { delivered: true, via: 'bot' }
|
|
|
}
|
|
|
@@ -900,15 +902,40 @@ class Worker {
|
|
|
try {
|
|
|
this.initHandlers()
|
|
|
|
|
|
- const channel = await mq.getChannel(this.channelName)
|
|
|
+ await this.startConsumeLoop()
|
|
|
|
|
|
- await channel.prefetch(5)
|
|
|
+ this.logger.info('哪吒乐跑 Worker 启动成功(JKES)')
|
|
|
+ } catch (err) {
|
|
|
+ this.logger.error('哪吒乐跑 Worker 启动失败: ' + (err.stack || err))
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- await assertRunforgeTaskIngress(channel, this.logger)
|
|
|
- await channel.assertQueue(this.resultQueue, { durable: true })
|
|
|
- await channel.assertQueue(this.deadQueue, { durable: true })
|
|
|
+ async startConsumeLoop() {
|
|
|
+ if (!this.running) return
|
|
|
+ if (this._consuming) return
|
|
|
+ this._consuming = true
|
|
|
+
|
|
|
+ const channel = await mq.getChannel(this.channelName)
|
|
|
+
|
|
|
+ channel.on('close', () => {
|
|
|
+ // close 事件可能重复触发;这里仅触发一次重启
|
|
|
+ if (!this.running) return
|
|
|
+ this._consuming = false
|
|
|
+ this.logger.warn('Worker channel 已关闭,准备重启消费')
|
|
|
+ setTimeout(() => {
|
|
|
+ this.startConsumeLoop().catch((e) => {
|
|
|
+ this.logger.error('重启 Worker 消费失败: ' + (e?.stack || e))
|
|
|
+ })
|
|
|
+ }, 1000)
|
|
|
+ })
|
|
|
+
|
|
|
+ await channel.prefetch(5)
|
|
|
|
|
|
- const handleTaskMessage = async (msg) => {
|
|
|
+ await assertRunforgeTaskIngress(channel, this.logger)
|
|
|
+ await channel.assertQueue(this.resultQueue, { durable: true })
|
|
|
+ await channel.assertQueue(this.deadQueue, { durable: true })
|
|
|
+
|
|
|
+ const handleTaskMessage = async (msg) => {
|
|
|
if (!msg) return
|
|
|
|
|
|
let content
|
|
|
@@ -960,51 +987,69 @@ class Worker {
|
|
|
}
|
|
|
|
|
|
if (retry < this.maxRetry && this.isRetryableTaskError(err)) {
|
|
|
- await channel.sendToQueue(
|
|
|
- this.taskQueue,
|
|
|
- Buffer.from(
|
|
|
- JSON.stringify({
|
|
|
- ...content,
|
|
|
- retry: retry + 1
|
|
|
- })
|
|
|
- ),
|
|
|
- { persistent: true }
|
|
|
- )
|
|
|
+ try {
|
|
|
+ await mq.sendToQueueSafe(
|
|
|
+ this.channelName,
|
|
|
+ this.taskQueue,
|
|
|
+ Buffer.from(
|
|
|
+ JSON.stringify({
|
|
|
+ ...content,
|
|
|
+ retry: retry + 1
|
|
|
+ })
|
|
|
+ ),
|
|
|
+ { persistent: true, contentType: 'application/json' }
|
|
|
+ )
|
|
|
+ } catch (e) {
|
|
|
+ this.logger.error(
|
|
|
+ `[${traceId}] 重试消息投递失败(将直接 ack,避免进程崩溃):${e?.message || e}`
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
this.log(traceId, 'RETRY', `重试第${retry + 1}次`)
|
|
|
} else {
|
|
|
- await channel.sendToQueue(
|
|
|
- this.deadQueue,
|
|
|
- Buffer.from(JSON.stringify(content)),
|
|
|
- { persistent: true }
|
|
|
- )
|
|
|
+ try {
|
|
|
+ await mq.sendToQueueSafe(
|
|
|
+ this.channelName,
|
|
|
+ this.deadQueue,
|
|
|
+ Buffer.from(JSON.stringify(content)),
|
|
|
+ { persistent: true, contentType: 'application/json' }
|
|
|
+ )
|
|
|
+ } catch (e) {
|
|
|
+ this.logger.error(
|
|
|
+ `[${traceId}] 死信投递失败(将直接 ack,避免进程崩溃):${e?.message || e}`
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
this.log(traceId, 'DEAD', '进入死信队列')
|
|
|
}
|
|
|
|
|
|
- await this.sendResult(channel, {
|
|
|
- id,
|
|
|
- success: false,
|
|
|
- error: err.message
|
|
|
- })
|
|
|
+ try {
|
|
|
+ await this.sendResult(channel, {
|
|
|
+ id,
|
|
|
+ success: false,
|
|
|
+ error: err.message
|
|
|
+ })
|
|
|
+ } catch (e) {
|
|
|
+ this.logger.error(
|
|
|
+ `[${traceId}] 结果投递失败(忽略):${e?.message || e}`
|
|
|
+ )
|
|
|
+ }
|
|
|
|
|
|
channel.ack(msg)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- await channel.consume(this.taskQueue, handleTaskMessage, { noAck: false })
|
|
|
-
|
|
|
- this.logger.info('哪吒乐跑 Worker 启动成功(JKES)')
|
|
|
- } catch (err) {
|
|
|
- this.logger.error('哪吒乐跑 Worker 启动失败: ' + err.stack)
|
|
|
- }
|
|
|
+ const ok = await channel.consume(this.taskQueue, handleTaskMessage, { noAck: false })
|
|
|
+ this._consumeTag = ok?.consumerTag || null
|
|
|
}
|
|
|
|
|
|
async sendResult(channel, data) {
|
|
|
- channel.sendToQueue(
|
|
|
+ // 结果队列同样可能因断线导致 channel 关闭,这里用安全投递兜底
|
|
|
+ await mq.sendToQueueSafe(
|
|
|
+ this.channelName,
|
|
|
this.resultQueue,
|
|
|
Buffer.from(JSON.stringify(data)),
|
|
|
- { persistent: true }
|
|
|
+ { persistent: true, contentType: 'application/json' }
|
|
|
)
|
|
|
}
|
|
|
|