ZE FusionBot v7.9.9
Memory Leaks, Null References, Code Dupes = Fixed/Cleaned
v7.9.9
Fixed memory leaks across TwitchBot, YouTubeBot, DMRelayService, BotServer, TradeModule, Main, DiscordTradeNotifier, and QueueHelper.
Fixed null refs, disposable leaks, and duplicate code across SysCord, Pokepaste, and PokeTradeBotBS.
- TwitchBot.cs
Five event handlers leaking:
- OnMessageSent, OnWhisperSent, OnMessageThrottled, OnWhisperThrottled, OnError
They were subscribed as anonymous lambdas in ConnectInternal(), but the UnsubscribeEventHandlers() method tried to remove them by creating brand-new lambda expressions.
Fix: Added five delegate fields (_logMessageSent, etc.) that store the lambda at subscription time and are used for both += and -=.
- YouTubeBot.cs
- Logger.LogOccurred += Logger_LogOccurred is a static event. The Logger class holds a reference to every YouTubeBot instance that ever subscribed, forever.
- EchoUtil.Forwarders.Add(msg => client.SendMessage(msg)) adds an anonymous lambda to a static List<Action>. That lambda captures client (which in turn holds the YouTubeBot), creating a permanent list.
- client.OnMessagesReceived += Client_OnMessagesReceived is never unsubscribed.
Fix: Added IDisposable, tracked the echo forwarder lambda in _echoForwarder, and Dispose() unsubscribes all.
- DMRelayService.cs
- _client.MessageReceived += HandleMessageAsync was registered against the long-lived DiscordSocketClient with no matching -= anywhere and no IDisposable to trigger cleanup. If a new DMRelayService is created (like on a reconnect) the old one is permanently kept alive by the Discord client.
Fix: Implemented IDisposable with _client.MessageReceived -= HandleMessageAsync in Dispose().
- BotServer.cs
- ScanRemoteInstancesAsync() is called on every web API request for instance info. Each call created a new SemaphoreSlim(...) and never disposed it. SemaphoreSlim holds an unmanaged EventWaitHandle that requires explicit disposal.
- GetProcessPathForId() called Process.GetProcessById() and returned without disposing. Process holds unmanaged handles on the OS.
Fix: Changed both to using var so the handles are released immediately.
- TradeModule.cs
- await new HttpClient().GetStringAsync(file.Url) on every text-trade file uploaded & created a fresh HttpClient instance, used it for one request, and then dropped it with no disposal. HttpClient holds a socket connection and abandoning it means the OS socket lingers in TIME_WAIT until the finalizer eventually runs, potentially hundreds at a time on a busy bot. Whoops.
Fix: using var httpClient = new HttpClient(); before the call.
- Main.cs
- glitterTimer (a WinForms System.Windows.Forms.Timer) is created in InitGlitter() and started, but there was no FormClosed/Dispose handler to stop it. The timer kept its internal window handle alive and continued sending WM_TIMER messages to an already-destroyed window handle after the form closed, producing ObjectDisposedException in the Tick callback and preventing proper GC of the timer object.
Fix: Added FormClosed += (_, __) => { glitterTimer.Stop(); glitterTimer.Dispose(); }; inside InitGlitter().
- DiscordTradeNotifier.cs
Three separate issues in this method that fires every time the LGPE link-code embed is shown:
- The original sprite from pk.Sprite() was overwritten (png = destImage) without disposing the original, leaking the bitmap per Pictocode (3 per call.)
- All three destImage bitmaps in spritearray were composited into outputImage and then abandoned, which leaks 3 more bitmaps per call.
- outputImage itself was saved to disk but never disposed, leaking 1 more additional bitmap per call.
Fix: Stored sprite image in a separate variable and called .Dispose() after drawing; changed outputImage to using var so it auto-disposes at end of the method, then added foreach (img) img.Dispose() for the sprite array after compositing.
- QueueHelper.cs
This method was a duplicate of the DiscordTradeNotifier version, with the same leak.
- SysCord.cs
- Removed the duplicate DiscordSocketClient initialization — the constructor was creating _client twice, immediately overwriting (and leaking) the first one. The single remaining init preserves the //MessageCacheSize = 50, comment.
- Removed the impossible _client == null null-check on a non-nullable readonly field in AnnounceBotStatus.
- Pokepaste.cs
- GetPokePasteHtml(): Added using to the HttpClient so it's disposed after the request completes.
- Line 125 (image loading): Replaced the inline new HttpClient().GetStreamAsync(...).Result (leaked client + blocking call) with a proper using var imageClient + await imageClient.GetByteArrayAsync(...), then loaded the image from a MemoryStream backed by the downloaded bytes.
- combinedImage: Changed var to using var so the returned Bitmap is disposed when the lambda scope exits.
- Added img.Dispose() loop over pokemonImages immediately after CombineImages() finishes with them.
- PokeTradeBotBS.cs
- Removed the duplicated // As long as we got rid of our inject in b1s1... comment.
- Removed the second poke.TradeFinished(this, received) call that immediately followed the first one, which would have fired the trade-complete notification twice.