Node.js host process becomes "locked up" / "clogged" for simple bot

I have developed a simple bot that: 1. Detects when a message could possibly contain media/embeds 2. Reacts with up/down arrows, an "upvote bot" And it works great! It's very funny and useful for my Discord community named "MNSND". However, after a significant amount of time (anywhere from 1 to 24 hours), the bot becomes unresponsive and starts missing messages. I do not believe this to be a Discord.js issue directly, but possibly an issue with my code and how it interacts with Discord.js and the Node.js runtime. It's really weird! Here is the exact current source code and package.json: https://gist.github.com/luavixen/bb8f52535a0f8c8a958bdc41773b642d To be specific, it seems like the Node.js event loop is getting "clogged" or "locked up"? After a significant amount of time, the Node.js process fails to even perform basic asynchronous actions. After even longer, I cannot even terminate the process, I must send a SIGKILL signal. For example, I tried introducing a setTimeout call on program start that, after four hours, prints a message and calls process.exit. This would only ever complete 50% of the time on my development machine! I have reproduced this issue on: 1. Development machine - Windows 11 Laptop - Node.js v20.17.0 2. My personal server - Ubuntu LTS 20.04, hosted on my own hardware - Node.js v18.12.0 3. DigitalOcean droplet - Ubuntu LTS latest - Node.js Docker node:20-alpine To try and mitigate this issue, I put the bot inside of docker:
mnsnd-bot:
image: 'node:20-alpine'
container_name: 'mnsnd-upvote-bot'
pull_policy: always
restart: unless-stopped
network_mode: host
user: '1000:1000'
volumes:
- '/home/lua/apps/mnsnd-upvote-bot:/data'
working_dir: '/data'
command: 'node --enable-source-maps ./index.js'
mnsnd-bot:
image: 'node:20-alpine'
container_name: 'mnsnd-upvote-bot'
pull_policy: always
restart: unless-stopped
network_mode: host
user: '1000:1000'
volumes:
- '/home/lua/apps/mnsnd-upvote-bot:/data'
working_dir: '/data'
command: 'node --enable-source-maps ./index.js'
And created this cronjob:
0 */2 * * * /usr/bin/docker restart mnsnd-upvote-bot >/dev/null 2>&1
0 */2 * * * /usr/bin/docker restart mnsnd-upvote-bot >/dev/null 2>&1
Even still, the bot occasionally locks up and misses messages. It is completely unpredictable. Sometimes, Docker can simply ask the bot to gracefully shut down:
...
2024-09-29T23:58:03.421465376Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 76ms.
2024-09-29T23:58:44.700408912Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 75ms.
2024-09-29T23:59:25.986783539Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 75ms.
(Docker sends SIGTERM)
2024-09-30T00:00:02.081485472Z [mnsnd dbg] Received SIGTERM signal
2024-09-30T00:00:02.082989322Z [mnsnd dbg] Shutting down...
2024-09-30T00:00:02.111893643Z [discord.js dbg] [WS => Manager] Manager was destroyed:
(...)
2024-09-30T00:00:02.199721062Z [node.js dbg] Exiting with code 0...
(Process exits)
(Process created)
2024-09-30T00:00:03.562466635Z [mnsnd dbg] Starting up...
2024-09-30T00:00:03.616097539Z [discord.js dbg] Provided token: MTI1MzE1Mzc5MDE5MDY4MjEyMg.GekIOf.**************************************
2024-09-30T00:00:03.616158268Z [discord.js dbg] Preparing to connect to the gateway...
...
...
2024-09-29T23:58:03.421465376Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 76ms.
2024-09-29T23:58:44.700408912Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 75ms.
2024-09-29T23:59:25.986783539Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 75ms.
(Docker sends SIGTERM)
2024-09-30T00:00:02.081485472Z [mnsnd dbg] Received SIGTERM signal
2024-09-30T00:00:02.082989322Z [mnsnd dbg] Shutting down...
2024-09-30T00:00:02.111893643Z [discord.js dbg] [WS => Manager] Manager was destroyed:
(...)
2024-09-30T00:00:02.199721062Z [node.js dbg] Exiting with code 0...
(Process exits)
(Process created)
2024-09-30T00:00:03.562466635Z [mnsnd dbg] Starting up...
2024-09-30T00:00:03.616097539Z [discord.js dbg] Provided token: MTI1MzE1Mzc5MDE5MDY4MjEyMg.GekIOf.**************************************
2024-09-30T00:00:03.616158268Z [discord.js dbg] Preparing to connect to the gateway...
...
But sometimes, after the bot locks up, Docker must send a SIGKILL:
...
2024-09-28T16:48:23.864248801Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 86ms.
2024-09-28T16:49:05.148599771Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 77ms.
2024-09-28T16:49:09.821037631Z [mnsnd dbg] Msg @swing_racc "`//WHAT GAME I NEEDA KN0W//`" (skipped)
2024-09-28T16:49:13.539030409Z [mnsnd dbg] Msg @swing_racc ":000" (skipped)
(Docker sends SIGTERM)
(Docker sends SIGKILL 10s later)
(Process exits)
(Process created)
2024-09-28T18:00:12.647975190Z [mnsnd dbg] Starting up...
2024-09-28T18:00:12.706542854Z [discord.js dbg] Provided token: MTI1MzE1Mzc5MDE5MDY4MjEyMg.GekIOf.**************************************
2024-09-28T18:00:12.706720060Z [discord.js dbg] Preparing to connect to the gateway...
...
...
2024-09-28T16:48:23.864248801Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 86ms.
2024-09-28T16:49:05.148599771Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 77ms.
2024-09-28T16:49:09.821037631Z [mnsnd dbg] Msg @swing_racc "`//WHAT GAME I NEEDA KN0W//`" (skipped)
2024-09-28T16:49:13.539030409Z [mnsnd dbg] Msg @swing_racc ":000" (skipped)
(Docker sends SIGTERM)
(Docker sends SIGKILL 10s later)
(Process exits)
(Process created)
2024-09-28T18:00:12.647975190Z [mnsnd dbg] Starting up...
2024-09-28T18:00:12.706542854Z [discord.js dbg] Provided token: MTI1MzE1Mzc5MDE5MDY4MjEyMg.GekIOf.**************************************
2024-09-28T18:00:12.706720060Z [discord.js dbg] Preparing to connect to the gateway...
...
I have been working with Node.js for years and years, and I am kinda stumped. Help!
Gist
Implementation of my Discord.js upvote/downvote bot for media/embeds.
Implementation of my Discord.js upvote/downvote bot for media/embeds. - index.ts
8 Replies
d.js toolkit
d.js toolkit2mo ago
- What's your exact discord.js npm list discord.js and node node -v version? - Not a discord.js issue? Check out #other-js-ts. - Consider reading #how-to-get-help to improve your question! - Explain what exactly your issue is. - Post the full error stack trace, not just the top part! - Show your code! - Issue solved? Press the button! - Marked as resolved by OP
lua
luaOP2mo ago
Another example:
2024-09-27T19:57:03.697239292Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 79ms.
2024-09-27T19:57:45.005351995Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 97ms.
(Docker sends SIGTERM)
(Docker sends SIGKILL 10s later)
(Process exits)
(Process created)
2024-09-27T20:00:12.187818342Z [mnsnd dbg] Starting up...
2024-09-27T20:00:12.245994300Z [discord.js dbg] Provided token: MTI1MzE1Mzc5MDE5MDY4MjEyMg.GekIOf.**************************************
2024-09-27T20:00:12.246347208Z [discord.js dbg] Preparing to connect to the gateway...
2024-09-27T19:57:03.697239292Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 79ms.
2024-09-27T19:57:45.005351995Z [discord.js dbg] [WS => Shard 0] Heartbeat acknowledged, latency of 97ms.
(Docker sends SIGTERM)
(Docker sends SIGKILL 10s later)
(Process exits)
(Process created)
2024-09-27T20:00:12.187818342Z [mnsnd dbg] Starting up...
2024-09-27T20:00:12.245994300Z [discord.js dbg] Provided token: MTI1MzE1Mzc5MDE5MDY4MjEyMg.GekIOf.**************************************
2024-09-27T20:00:12.246347208Z [discord.js dbg] Preparing to connect to the gateway...
The only thing I can think of next is to launch it in a debugger, learn a bunch about Discord.js's internals, and watch it and wait for it to lock up again.
matt4499
matt44992mo ago
function containsSupportedLink(message: Message): boolean {
const pattern = /https?:\/\/([\w.]+)((?:\/[^\s/#?]*)+)(\?[^\s#]*)?(#\S*)?/;
let result;

while ((result = pattern.exec(message.content)) !== null) {
let [_, host, path, query, fragment] = result;
host = host.toLowerCase();

if (host.endsWith('x.com') || host.endsWith('twitter.com')) {
if (/^\/\w+\/status\/\d+/i.test(path)) return true;
}

for (const domain of supportedMediaDomains) {
if (host.endsWith(domain) && hasSupportedExtension(path)) return true;
}

for (const domain of supportedDomains) {
if (host.endsWith(domain) && !/^\/*$/.test(path)) return true;
}
}

return false;
}
function containsSupportedLink(message: Message): boolean {
const pattern = /https?:\/\/([\w.]+)((?:\/[^\s/#?]*)+)(\?[^\s#]*)?(#\S*)?/;
let result;

while ((result = pattern.exec(message.content)) !== null) {
let [_, host, path, query, fragment] = result;
host = host.toLowerCase();

if (host.endsWith('x.com') || host.endsWith('twitter.com')) {
if (/^\/\w+\/status\/\d+/i.test(path)) return true;
}

for (const domain of supportedMediaDomains) {
if (host.endsWith(domain) && hasSupportedExtension(path)) return true;
}

for (const domain of supportedDomains) {
if (host.endsWith(domain) && !/^\/*$/.test(path)) return true;
}
}

return false;
}
lua
luaOP2mo ago
I know that it's not my issue, but thanks for bringing that up! @matt4499, I like the way you re-wrote the loop to make it more obvious and I'll copy that over into my codebase. That loop really just iterates over the results of the pattern regex and unless message.content changes, it will always terminate. Additionally, it never actually gets stuck in a loop! Both CPU and memory usage stay way down, and the function in question is entirely synchronous (unless there's something evil going on).
matt4499
matt44992mo ago
👍
lua
luaOP2mo ago
At least that's what I think! I am not /actively/ monitoring those two metrics.
lua
luaOP2mo ago
Retracting my previous statement, huuuuh ??? Note the 25% usage of a 4 core system.
No description
lua
luaOP2mo ago
Apparently this was true at least in my testing, but checking on it in production it is seemingly getting stuck. I'm going to feel like such an idiot if this is the issue LOL. I have rewritten the function as follows, just to be absolutely sure that this possible bug is gone:
function containsSupportedLink(message: Message): boolean {
const pattern = /https?:\/\/([\w.]+)((?:\/[^\s/#?]*)+)(\?[^\s#]*)?(#\S*)?/;
const content = String(message.content);

let i = 0;
let result: RegExpExecArray | null;

while ((result = pattern.exec(content)) != null) {
let [_, host, path, query, fragment] = result; host = host.toLowerCase();

if (host.endsWith('x.com') || host.endsWith('twitter.com')) {
if (/^\/\w+\/status\/\d+/i.test(path)) return true;
}

for (const domain of supportedMediaDomains) {
if (host.endsWith(domain) && hasSupportedExtension(path)) return true;
}

for (const domain of supportedDomains) {
if (host.endsWith(domain) && !/^\/*$/.test(path)) return true;
}

if (i++ > 1000) {
logger.warn('Exceeded link check limit?');
return false;
}
}

return false;
}
function containsSupportedLink(message: Message): boolean {
const pattern = /https?:\/\/([\w.]+)((?:\/[^\s/#?]*)+)(\?[^\s#]*)?(#\S*)?/;
const content = String(message.content);

let i = 0;
let result: RegExpExecArray | null;

while ((result = pattern.exec(content)) != null) {
let [_, host, path, query, fragment] = result; host = host.toLowerCase();

if (host.endsWith('x.com') || host.endsWith('twitter.com')) {
if (/^\/\w+\/status\/\d+/i.test(path)) return true;
}

for (const domain of supportedMediaDomains) {
if (host.endsWith(domain) && hasSupportedExtension(path)) return true;
}

for (const domain of supportedDomains) {
if (host.endsWith(domain) && !/^\/*$/.test(path)) return true;
}

if (i++ > 1000) {
logger.warn('Exceeded link check limit?');
return false;
}
}

return false;
}
I FORGOT THE g YOU'RE RIGHT Ohhhh my god lol. Okay I'm gonna add that too
Want results from more Discord servers?
Add your server