rustclan
rustclan2y ago

trpc rate limiting

hi I am currently having some problems with a race condition in my TRPC nextJS api. Essentially what is happening is I have a enforceGuildPermissions method, which basically checks if the user who is making the request has permission to get the data for that guild. The data is stored in my Redis cache for 3 seconds. This works okay sometimes, but other times because there is 3-4 different trpc requests running for a single page which are guild, role and channel. It causes the last request (channel) to get rate limited by the discord API because they are all running concurrently, this means it doesn't give my caching code chance to update it before the next one runs.
const enforceGuildPermissions = enforceUserLoggedIn.unstable_pipe(
async ({ ctx, next, rawInput }) => {
const guildId: unknown = (rawInput as { guildId?: unknown })?.guildId;
if (!guildId) throw new TRPCError({ code: 'BAD_REQUEST' });

const webUser = await cache.webUsers.get(ctx.session.user.id);
let guilds = webUser?.guilds;
if (!guilds) {
guilds = await getUserGuilds(ctx.session)

}

if (!guilds) throw new TRPCError({ code: 'UNAUTHORIZED' });
const foundGuild = guilds.find((guild) => guild.id === guildId);
if (!foundGuild) throw new TRPCError({ code: 'UNAUTHORIZED' });

return next({
ctx: {
session: { ...ctx.session, user: ctx.session.user },
},
});
}
);

export const guildProcedure = t.procedure.use(enforceGuildPermissions);
const enforceGuildPermissions = enforceUserLoggedIn.unstable_pipe(
async ({ ctx, next, rawInput }) => {
const guildId: unknown = (rawInput as { guildId?: unknown })?.guildId;
if (!guildId) throw new TRPCError({ code: 'BAD_REQUEST' });

const webUser = await cache.webUsers.get(ctx.session.user.id);
let guilds = webUser?.guilds;
if (!guilds) {
guilds = await getUserGuilds(ctx.session)

}

if (!guilds) throw new TRPCError({ code: 'UNAUTHORIZED' });
const foundGuild = guilds.find((guild) => guild.id === guildId);
if (!foundGuild) throw new TRPCError({ code: 'UNAUTHORIZED' });

return next({
ctx: {
session: { ...ctx.session, user: ctx.session.user },
},
});
}
);

export const guildProcedure = t.procedure.use(enforceGuildPermissions);
export const getUserGuilds = async (
session: Session
): Promise<CachedUserGuild[] | null> => {
if (!session.user.accessToken || !session.user.id) return null;

const webUser = await cache.webUsers.get(session.user.id);
if (webUser) return webUser.guilds;

const response = await fetch(discord...)
const guilds = await response.json();
if (!response.ok || guilds.length <= 0) return null;

await cache.webUsers.create(session.user.id, guilds);

return guilds;
};
export const getUserGuilds = async (
session: Session
): Promise<CachedUserGuild[] | null> => {
if (!session.user.accessToken || !session.user.id) return null;

const webUser = await cache.webUsers.get(session.user.id);
if (webUser) return webUser.guilds;

const response = await fetch(discord...)
const guilds = await response.json();
if (!response.ok || guilds.length <= 0) return null;

await cache.webUsers.create(session.user.id, guilds);

return guilds;
};
1 Reply
rustclan
rustclan2y ago
hopefully this makes sense, but I am completely stumped about what I can do to resolve this. A big band aid fix would be to just add an artificial wait:
if (!guilds) {
await new Promise((resolve) => setTimeout(resolve, 1000));
webUser = await cache.webUsers.get(ctx.session.user.id);
guilds = webUser?.guilds;
if (!guilds) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
}
if (!guilds) {
await new Promise((resolve) => setTimeout(resolve, 1000));
webUser = await cache.webUsers.get(ctx.session.user.id);
guilds = webUser?.guilds;
if (!guilds) {
throw new TRPCError({ code: 'UNAUTHORIZED' });
}
}
But obviously this is not very elegant..