message: Add a timeout system to avoid this function causing messages to queue up
diff: @@ -8,29 +8,53 @@ interface RoomConfigContent {
  }
  const MAX_CACHE_SIZE = 512;
  +const STATE_TIMEOUT_MS = 2000;
  const log = getLogger("RoomConfig");
  export class RoomConfig {
  - public static readonly STATE_EVENT_TYPE = 'org.matrix.appservice-irc.room-config';
  + public static readonly STATE_EVENT_TYPE = 'org.matrix.appservice-irc.config';
  private cache = new QuickLRU<string, RoomConfigContent|undefined>({maxSize: MAX_CACHE_SIZE});
  constructor(private bridge: Bridge) { }
  + /**
  + * Fetch the state for the room, preferring a keyed state event over a global one.
  + * This request will time out after `STATE_TIMEOUT_MS` if the state could not be fetched in time.
  + * @param roomId The Matrix room ID
  + * @param ircRoom The IRC room we want the configuration for.
  + * @returns A content object containing the configuration, or null if the event was not found or the
  + * request timed out.
  + */
  private async getRoomState(roomId: string, ircRoom?: IrcRoom): Promise<RoomConfigContent|null> {
  const cacheKey = `${roomId}:${ircRoom?.getId() || 'global'}`;
  let keyedConfig = this.cache.get(cacheKey);
  if (keyedConfig) {
  return keyedConfig;
  }
  + const internalFunc = async () => {
  const intent = this.bridge.getIntent();
  - keyedConfig = ircRoom && await intent.getStateEvent(roomId, RoomConfig.STATE_EVENT_TYPE, ircRoom.getId(), true);
  + keyedConfig = ircRoom &&
  + await intent.getStateEvent(roomId, RoomConfig.STATE_EVENT_TYPE, ircRoom.getId(), true);
  if (!keyedConfig) {
  // Fall back to an empty key
  keyedConfig = await intent.getStateEvent(roomId, RoomConfig.STATE_EVENT_TYPE, '', true);
  }
  - log.debug(`Stored new config for ${cacheKey}:`, keyedConfig || 'No config set');
  + log.debug(
  + `Stored new config for ${cacheKey}: ${keyedConfig ? 'No config set' : JSON.stringify(keyedConfig)}`
  + );
  this.cache.set(cacheKey, keyedConfig || undefined);
  return keyedConfig as RoomConfigContent|null;
  }
  + // We don't want to spend too long trying to fetch the state, so return null.
  + return Promise.race([
  + internalFunc(),
  + new Promise<null>(res => setTimeout(() => res(null), STATE_TIMEOUT_MS)),
  + // We *never* want this function to throw, as it's critical for the bridging of messages.
  + // Instead we return null for any errors.
  + ]).catch(ex => {
  + log.warn(`Failed to fetch state for ${cacheKey}`, ex);
  + return null;
  + })
  + }
  /**
  * Invalidate the cache for a room. Provide the key
  