فهرست منبع

fix(core): Add timeout logic to session cache handling

This prevents a bad session cache (e.g. Redis gets disconnected) from badly impacting
response times.
Michael Bromley 2 سال پیش
والد
کامیت
a817a1a672
1فایلهای تغییر یافته به همراه23 افزوده شده و 9 حذف شده
  1. 23 9
      packages/core/src/service/services/session.service.ts

+ 23 - 9
packages/core/src/service/services/session.service.ts

@@ -29,6 +29,7 @@ import { OrderService } from './order.service';
 export class SessionService implements EntitySubscriberInterface {
     private sessionCacheStrategy: SessionCacheStrategy;
     private readonly sessionDurationInMs: number;
+    private readonly sessionCacheTimeoutMs = 50;
 
     constructor(
         private connection: TransactionalConnection,
@@ -65,7 +66,7 @@ export class SessionService implements EntitySubscriberInterface {
             // session cache will be wrong, so we just clear the entire cache. It should however
             // be a very rare occurrence in normal operation, once initial setup is complete.
             if (event.entity instanceof Channel || event.entity instanceof Role) {
-                await this.sessionCacheStrategy.clear();
+                await this.withTimeout(this.sessionCacheStrategy.clear());
             }
         }
     }
@@ -96,7 +97,7 @@ export class SessionService implements EntitySubscriberInterface {
                 invalidated: false,
             }),
         );
-        await this.sessionCacheStrategy.set(this.serializeSession(authenticatedSession));
+        await this.withTimeout(this.sessionCacheStrategy.set(this.serializeSession(authenticatedSession)));
         return authenticatedSession;
     }
 
@@ -115,7 +116,7 @@ export class SessionService implements EntitySubscriberInterface {
         // save the new session
         const newSession = await this.connection.rawConnection.getRepository(AnonymousSession).save(session);
         const serializedSession = this.serializeSession(newSession);
-        await this.sessionCacheStrategy.set(serializedSession);
+        await this.withTimeout(this.sessionCacheStrategy.set(serializedSession));
         return serializedSession;
     }
 
@@ -124,14 +125,14 @@ export class SessionService implements EntitySubscriberInterface {
      * Returns the cached session object matching the given session token.
      */
     async getSessionFromToken(sessionToken: string): Promise<CachedSession | undefined> {
-        let serializedSession = await this.sessionCacheStrategy.get(sessionToken);
+        let serializedSession = await this.withTimeout(this.sessionCacheStrategy.get(sessionToken));
         const stale = !!(serializedSession && serializedSession.cacheExpiry < new Date().getTime() / 1000);
         const expired = !!(serializedSession && serializedSession.expires < new Date());
         if (!serializedSession || stale || expired) {
             const session = await this.findSessionByToken(sessionToken);
             if (session) {
                 serializedSession = this.serializeSession(session);
-                await this.sessionCacheStrategy.set(serializedSession);
+                await this.withTimeout(this.sessionCacheStrategy.set(serializedSession));
                 return serializedSession;
             } else {
                 return;
@@ -168,6 +169,19 @@ export class SessionService implements EntitySubscriberInterface {
         return serializedSession;
     }
 
+    /**
+     * If the session cache is taking longer than say 50ms then something is wrong - it is supposed to
+     * be very fast after all! So we will return undefined and let the request continue without a cached session.
+     */
+    private withTimeout<T>(maybeSlow: Promise<T> | T): Promise<T | undefined> {
+        return Promise.race([
+            new Promise<undefined>(resolve =>
+                setTimeout(() => resolve(undefined), this.sessionCacheTimeoutMs),
+            ),
+            maybeSlow,
+        ]);
+    }
+
     /**
      * Looks for a valid session with the given token and returns one if found.
      */
@@ -205,7 +219,7 @@ export class SessionService implements EntitySubscriberInterface {
             session.activeOrder = order;
             await this.connection.getRepository(ctx, Session).save(session, { reload: false });
             const updatedSerializedSession = this.serializeSession(session);
-            await this.sessionCacheStrategy.set(updatedSerializedSession);
+            await this.withTimeout(this.sessionCacheStrategy.set(updatedSerializedSession));
             return updatedSerializedSession;
         }
         return serializedSession;
@@ -245,7 +259,7 @@ export class SessionService implements EntitySubscriberInterface {
             session.activeChannel = channel;
             await this.connection.rawConnection.getRepository(Session).save(session, { reload: false });
             const updatedSerializedSession = this.serializeSession(session);
-            await this.sessionCacheStrategy.set(updatedSerializedSession);
+            await this.withTimeout(this.sessionCacheStrategy.set(updatedSerializedSession));
             return updatedSerializedSession;
         }
         return serializedSession;
@@ -261,7 +275,7 @@ export class SessionService implements EntitySubscriberInterface {
             .find({ where: { user: { id: user.id } } });
         await this.connection.getRepository(ctx, AuthenticatedSession).remove(userSessions);
         for (const session of userSessions) {
-            await this.sessionCacheStrategy.delete(session.token);
+            await this.withTimeout(this.sessionCacheStrategy.delete(session.token));
         }
     }
 
@@ -273,7 +287,7 @@ export class SessionService implements EntitySubscriberInterface {
         const sessions = await this.connection.getRepository(ctx, Session).find({ where: { activeOrderId } });
         await this.connection.getRepository(ctx, Session).remove(sessions);
         for (const session of sessions) {
-            await this.sessionCacheStrategy.delete(session.token);
+            await this.withTimeout(this.sessionCacheStrategy.delete(session.token));
         }
     }