Skip to content

ZE FusionBot v7.9.9

Choose a tag to compare

@Secludedly Secludedly released this 07 May 02:06

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.


  1. 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 -=.

  1. 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.

  1. 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().

  1. 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.

  1. 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.

  1. 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().

  1. 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.

  1. QueueHelper.cs
    This method was a duplicate of the DiscordTradeNotifier version, with the same leak.

  1. 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.

  1. 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.

  1. 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.