a cache for slack profile pictures and emojis

bug: DST leading to skipped task error message spiral

node-cron@v4 throws an error when a scheduled task is skipped, and that caused the process to enter a while loop that logged each error, leading to a cpu spike that clogged the process and suspended operations. steps have been taken to prevent future issues by changing timezone to utc and wrapping the scheduled tasks in try catch blocks

dunkirk.sh 29c3e44a 1b067fb5

verified
+41 -18
+32 -18
src/cache.ts
··· 729 729 * @private 730 730 */ 731 731 private setupPurgeSchedule() { 732 + const cronOptions = { timezone: "Etc/UTC" }; 733 + 732 734 // Run purge every hour at 45 minutes (only expired items, analytics cleanup) 733 735 schedule("45 * * * *", async () => { 734 - await this.purgeExpiredItems(); 735 - await this.lazyUserCleanup(); 736 - }); 736 + try { 737 + await this.purgeExpiredItems(); 738 + await this.lazyUserCleanup(); 739 + } catch (error) { 740 + console.error("Error during purge schedule:", error); 741 + } 742 + }, cronOptions); 737 743 738 - // Schedule emoji updates daily on the hour 744 + // Schedule emoji updates every hour on the hour 739 745 schedule("0 * * * *", async () => { 740 - console.log("Scheduled emoji update starting..."); 741 - if (this.onEmojiExpired) { 742 - this.onEmojiExpired(); 743 - console.log("Scheduled emoji update completed"); 746 + try { 747 + console.log("Scheduled emoji update starting..."); 748 + if (this.onEmojiExpired) { 749 + await this.onEmojiExpired(); 750 + console.log("Scheduled emoji update completed"); 751 + } 752 + } catch (error) { 753 + console.error("Error during emoji update schedule:", error); 744 754 } 745 - }); 755 + }, cronOptions); 746 756 747 - // Run VACUUM daily at 3am to reclaim disk space 748 - schedule("0 3 * * *", () => { 749 - console.log("Running scheduled VACUUM..."); 750 - this.db.run("VACUUM"); 751 - console.log("VACUUM completed"); 752 - }); 757 + // Run VACUUM daily at 8am UTC (3am EST / 4am EDT) 758 + schedule("0 8 * * *", () => { 759 + try { 760 + console.log("Running scheduled VACUUM..."); 761 + this.db.run("VACUUM"); 762 + console.log("VACUUM completed"); 763 + } catch (error) { 764 + console.error("Error during VACUUM:", error); 765 + } 766 + }, cronOptions); 753 767 } 754 768 755 769 /** ··· 806 820 * @private 807 821 */ 808 822 private async lazyUserCleanup(): Promise<void> { 809 - const currentHour = new Date().getHours(); 810 - // Only run during off-peak hours (3-5 AM) and not every time 811 - if (currentHour >= 3 && currentHour < 5 && Math.random() < 0.1) { 823 + const currentHour = new Date().getUTCHours(); 824 + // Only run during off-peak hours (8-10 UTC = 3-5 AM EST / 4-6 AM EDT) and not every time 825 + if (currentHour >= 8 && currentHour < 10 && Math.random() < 0.1) { 812 826 // 10% chance 813 827 const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000; 814 828 const result = this.db.run("DELETE FROM users WHERE expiration < ?", [
+9
src/index.ts
··· 159 159 process.on("SIGINT", shutdown); 160 160 process.on("SIGTERM", shutdown); 161 161 162 + // Prevent unhandled errors from crashing the process 163 + process.on("unhandledRejection", (reason) => { 164 + console.error("Unhandled promise rejection:", reason); 165 + }); 166 + 167 + process.on("uncaughtException", (error) => { 168 + console.error("Uncaught exception:", error); 169 + }); 170 + 162 171 export { cache, slackApp };