Skip to content

Commit f347570

Browse files
committed
Fix a rare bug where a guild/user fetch infinitely gets stuck which causes the bot to not respond to users
- Fix a rare bug where a guild/user fetch infinitely gets stuck which causes the bot to not respond to users -> Affects random users rarely, only when the api restarts
1 parent 336a01e commit f347570

File tree

9 files changed

+50
-34
lines changed

9 files changed

+50
-34
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
],
1010
"dependencies": {
1111
"@sentry/node": "^6.4.1",
12-
"detritus-client": "^0.16.4-beta.5",
12+
"detritus-client": "^0.17.0-beta.0",
1313
"emoji-aware": "^3.0.5",
1414
"juration": "^0.1.1",
1515
"moment": "^2.29.1",

src/commands/prefixed/basecommand.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export interface ContextMetadata {
2626
}
2727

2828
export class BaseCommand<ParsedArgsFinished = Command.ParsedArgs> extends Command.Command<ParsedArgsFinished> {
29+
/* @ts-ignore */
2930
metadata!: CommandMetadata;
3031
permissionsIgnoreClientOwner = true;
3132
triggerTypingAfter = 2000;

src/commands/prefixed/info/activity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export default class ActivityCommand extends BaseCommand {
8686
if (presence.clientStatus) {
8787
const description = [];
8888
for (let key of PRESENCE_CLIENT_STATUS_KEYS) {
89-
let status = (presence.clientStatus as any)[key];
89+
let status = (presence.clientStatus as any)[key] as string | undefined;
9090
if (status) {
9191
if (status in PresenceStatusTexts) {
9292
status = PresenceStatusTexts[status];

src/commands/prefixed/info/playing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ export default class PlayingCommand extends BaseCommand {
228228
if (presence.clientStatus && Object.keys(presence.clientStatus).length) {
229229
const description = [];
230230
for (let key of PRESENCE_CLIENT_STATUS_KEYS) {
231-
let status = (presence.clientStatus as any)[key];
231+
let status = (presence.clientStatus as any)[key] as string | undefined;
232232
if (status) {
233233
if (status in PresenceStatusTexts) {
234234
status = PresenceStatusTexts[status];

src/commands/prefixed/info/users.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export default class UsersCommand extends BaseCommand {
251251
if (presence.clientStatus && Object.keys(presence.clientStatus).length) {
252252
const description = [];
253253
for (let key of PRESENCE_CLIENT_STATUS_KEYS) {
254-
let status = (presence.clientStatus as any)[key];
254+
let status = (presence.clientStatus as any)[key] as string | undefined;
255255
if (status) {
256256
if (status in PresenceStatusTexts) {
257257
status = PresenceStatusTexts[status];

src/stores/guildsettings.ts

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ClusterClient } from 'detritus-client';
22
import { ClientEvents } from 'detritus-client/lib/constants';
3-
import { EventSubscription } from 'detritus-utils';
3+
import { EventSubscription, Timers } from 'detritus-utils';
44

55
import { Store } from './store';
66

@@ -25,11 +25,11 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
2525
async getOrFetch(context: RequestContext, guildId: string): Promise<GuildSettings | null> {
2626
let settings: GuildSettings | null = null;
2727
if (GuildSettingsPromisesStore.has(guildId)) {
28-
const promise = GuildSettingsPromisesStore.get(guildId) as GuildSettingsPromise;
28+
const { promise } = GuildSettingsPromisesStore.get(guildId)!;
2929
settings = await promise;
3030
} else {
3131
if (this.has(guildId)) {
32-
settings = this.get(guildId) as GuildSettings;
32+
settings = this.get(guildId)!;
3333
} else {
3434
settings = await this.fetch(context, guildId);
3535
}
@@ -38,11 +38,17 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
3838
}
3939

4040
async fetch(context: RequestContext, guildId: string): Promise<GuildSettings | null> {
41-
let promise: GuildSettingsPromise;
41+
let promise: Promise<GuildSettings | null>;
4242
if (GuildSettingsPromisesStore.has(guildId)) {
43-
promise = GuildSettingsPromisesStore.get(guildId) as GuildSettingsPromise;
43+
promise = GuildSettingsPromisesStore.get(guildId)!.promise;
4444
} else {
45+
const timeout = new Timers.Timeout();
4546
promise = new Promise(async (resolve) => {
47+
timeout.start(5000, () => {
48+
GuildSettingsPromisesStore.delete(guildId);
49+
resolve(null);
50+
});
51+
4652
const { client } = context;
4753
const guild = client.guilds.get(guildId);
4854
try {
@@ -60,9 +66,10 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
6066
} catch(error) {
6167
resolve(null);
6268
}
69+
timeout.stop();
6370
GuildSettingsPromisesStore.delete(guildId);
6471
});
65-
GuildSettingsPromisesStore.set(guildId, promise);
72+
GuildSettingsPromisesStore.set(guildId, {promise, timeout});
6673
}
6774
return promise;
6875
}
@@ -74,7 +81,7 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
7481
const { channel, shard } = event;
7582
const { guildId } = channel;
7683
if (guildId && this.has(guildId)) {
77-
const settings = this.get(guildId) as GuildSettings;
84+
const settings = this.get(guildId)!;
7885
{
7986
const loggers = settings.loggers.filter((logger) => logger.channelId === channel.id);
8087
for (let logger of loggers) {
@@ -125,7 +132,7 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
125132
const { channelId, guildId, shard } = event;
126133
if (this.has(guildId)) {
127134
const channel = shard.channels.get(channelId);
128-
const settings = this.get(guildId) as GuildSettings;
135+
const settings = this.get(guildId)!;
129136
const loggers = settings.loggers.filter((logger) => logger.channelId === channelId);
130137
if (loggers.length && channel && channel.canManageWebhooks) {
131138
try {
@@ -147,7 +154,7 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
147154
{
148155
const subscription = redis.subscribe(RedisChannels.GUILD_ALLOWLIST_UPDATE, (payload: RedisPayloads.GuildAllowlistUpdate) => {
149156
if (this.has(payload.id)) {
150-
const settings = this.get(payload.id) as GuildSettings;
157+
const settings = this.get(payload.id)!;
151158
settings.merge(payload);
152159
}
153160
});
@@ -156,7 +163,7 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
156163
{
157164
const subscription = redis.subscribe(RedisChannels.GUILD_BLOCKLIST_UPDATE, (payload: RedisPayloads.GuildBlocklistUpdate) => {
158165
if (this.has(payload.id)) {
159-
const settings = this.get(payload.id) as GuildSettings;
166+
const settings = this.get(payload.id)!;
160167
settings.merge(payload);
161168
}
162169
});
@@ -165,7 +172,7 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
165172
{
166173
const subscription = redis.subscribe(RedisChannels.GUILD_DISABLED_COMMAND_UPDATE, (payload: RedisPayloads.GuildDisabledCommandUpdate) => {
167174
if (this.has(payload.id)) {
168-
const settings = this.get(payload.id) as GuildSettings;
175+
const settings = this.get(payload.id)!;
169176
settings.merge(payload);
170177
}
171178
});
@@ -174,7 +181,7 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
174181
{
175182
const subscription = redis.subscribe(RedisChannels.GUILD_LOGGER_UPDATE, (payload: RedisPayloads.GuildLoggerUpdate) => {
176183
if (this.has(payload.id)) {
177-
const settings = this.get(payload.id) as GuildSettings;
184+
const settings = this.get(payload.id)!;
178185
settings.merge(payload);
179186
}
180187
});
@@ -183,7 +190,7 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
183190
{
184191
const subscription = redis.subscribe(RedisChannels.GUILD_PREFIX_UPDATE, (payload: RedisPayloads.GuildPrefixUpdate) => {
185192
if (this.has(payload.id)) {
186-
const settings = this.get(payload.id) as GuildSettings;
193+
const settings = this.get(payload.id)!;
187194
settings.merge(payload);
188195
}
189196
});
@@ -192,7 +199,7 @@ class GuildSettingsStore extends Store<string, GuildSettings> {
192199
{
193200
const subscription = redis.subscribe(RedisChannels.GUILD_SETTINGS_UPDATE, (payload: RedisPayloads.GuildSettingsUpdate) => {
194201
if (this.has(payload.id)) {
195-
const settings = this.get(payload.id) as GuildSettings;
202+
const settings = this.get(payload.id)!;
196203
settings.merge(payload);
197204
}
198205
});
@@ -207,11 +214,11 @@ export default new GuildSettingsStore();
207214

208215

209216

210-
export type GuildSettingsPromise = Promise<GuildSettings | null>;
217+
export type GuildSettingsPromiseItem = {promise: Promise<GuildSettings | null>, timeout: Timers.Timeout};
211218

212-
class GuildSettingsPromises extends Store<string, GuildSettingsPromise> {
213-
insert(guildId: string, promise: GuildSettingsPromise): void {
214-
this.set(guildId, promise);
219+
class GuildSettingsPromises extends Store<string, GuildSettingsPromiseItem> {
220+
insert(guildId: string, item: GuildSettingsPromiseItem): void {
221+
this.set(guildId, item);
215222
}
216223
}
217224

src/stores/users.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ClusterClient, Command, Interaction } from 'detritus-client';
2-
import { EventSubscription } from 'detritus-utils';
2+
import { EventSubscription, Timers } from 'detritus-utils';
33

44
import { Store } from './store';
55

@@ -24,11 +24,11 @@ class UserStore extends Store<string, User> {
2424
async getOrFetch(context: Command.Context | Interaction.InteractionContext, userId: string): Promise<User | null> {
2525
let user: User | null = null;
2626
if (UserPromisesStore.has(userId)) {
27-
const promise = UserPromisesStore.get(userId) as UserPromise;
27+
const { promise } = UserPromisesStore.get(userId)!;
2828
user = await promise;
2929
} else {
3030
if (this.has(userId)) {
31-
user = this.get(userId) as User;
31+
user = this.get(userId)!;
3232
} else {
3333
user = await this.fetch(context, userId);
3434
}
@@ -37,11 +37,17 @@ class UserStore extends Store<string, User> {
3737
}
3838

3939
async fetch(context: Command.Context | Interaction.InteractionContext, userId: string): Promise<User | null> {
40-
let promise: UserPromise;
40+
let promise: Promise<User | null>;
4141
if (UserPromisesStore.has(userId)) {
42-
promise = UserPromisesStore.get(userId) as UserPromise;
42+
promise = UserPromisesStore.get(userId)!.promise;
4343
} else {
44+
const timeout = new Timers.Timeout();
4445
promise = new Promise(async (resolve) => {
46+
timeout.start(5000, () => {
47+
UserPromisesStore.delete(userId);
48+
resolve(null);
49+
});
50+
4551
const discordUser = context.users.get(userId);
4652
try {
4753
let user: User | null = null;
@@ -60,9 +66,10 @@ class UserStore extends Store<string, User> {
6066
} catch(error) {
6167
resolve(null);
6268
}
69+
timeout.stop();
6370
UserPromisesStore.delete(userId);
6471
});
65-
UserPromisesStore.insert(userId, promise);
72+
UserPromisesStore.insert(userId, {promise, timeout});
6673
}
6774
return promise;
6875
}
@@ -86,11 +93,11 @@ export default new UserStore();
8693

8794

8895

89-
export type UserPromise = Promise<User | null>;
96+
export type UserPromiseItem = {promise: Promise<User | null>, timeout: Timers.Timeout};
9097

91-
class UserPromises extends Store<string, UserPromise> {
92-
insert(guildId: string, promise: UserPromise): void {
93-
this.set(guildId, promise);
98+
class UserPromises extends Store<string, UserPromiseItem> {
99+
insert(guildId: string, item: UserPromiseItem): void {
100+
this.set(guildId, item);
94101
}
95102
}
96103

src/utils/formatter/commands/info.user.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export async function createMessage(
173173
if (presence.clientStatus && Object.keys(presence.clientStatus).length) {
174174
const description = [];
175175
for (let key of PRESENCE_CLIENT_STATUS_KEYS) {
176-
let status = (presence.clientStatus as any)[key];
176+
let status = (presence.clientStatus as any)[key] as string | undefined;
177177
if (status) {
178178
if (status in PresenceStatusTexts) {
179179
status = PresenceStatusTexts[status];

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
"target": "esnext"
1010
},
1111
"include": ["src"],
12-
"exclude": ["node_modules"]
12+
"exclude": ["node_modules"],
13+
"useUnknownInCatchVariables": false
1314
}

0 commit comments

Comments
 (0)