samplePlayer sometimes leaks memory, sometimes not—with no change to game?

I’ve spent weeks (on and off for a year) trying to fix memory leak(s) that sometimes crash my game. The culprit is samplePlayers not releasing the memory they use—so small sounds leak a little, big ones leak a lot. I’m reading sounds from disk when needed (too much data to pre-load them all) using sound = snd.sampleplayer.new(filename). I keep re-using sound, and setting it to nil or :stop()-ing it first seems to make no difference.

But I realize now that Lua code changes that seemed to help are irrelevant: I’d see constant leaking, make a change and then see none... but it was coincidence. The SAME build leaks to a crash within minutes sometimes, runs fine indefinitely other times—when sitting idle without a user actually playing.

This is true in both the sim and on-device.

Sounds are played randomly, but I’ve saved logs of which sounds were playing and there’s no correlation: the same exact sound files will either leak or not.

But there is ONE pattern: once it starts leaking, relaunching the sim won’t help. It keeps leaking for hours, run after run. Then, randomly, it will STOP leaking! And eventually START leaking again—in the middle of a leak-free run.

It’s almost as though something external to the game—and surviving reboots—is putting it in “leak mode” or not. The mode comes and goes, but not often: either mode can last for hours. I’m not even sure which is more common! (I’ve tested less on-device, but that pattern seems to be true there as well.)

Is this likely to be an SDK issue I can’t address, or does this behavior suggest something more I could look at? I don’t want to report a bug that’s not one and waste Panic’s time.

Thanks in advance!

I’ve been unable to make a simple reproduction case, because the issue seems to happen—or not—regardless of what I do with my code. The moment the issue appears/disappears seems to be coincidental.

(This 2023 thread reports an ADPCM WAV leak (fixed?) but I see a leak with PCM too.)

1 Like

My Amigo Tracker uses sample players to play a dozen samples, up to several times per second, on four channels, and I haven't noticed any memory leaks at all. I'm using PCM instead of ADPCM and only change the samples every few minutes though.

For what it's worth, you could try calling collectgarbage() after setting the sound to nil and see if there's a visible drop in memory allocation. If GC doesn't free up the resources, something in your code is clearly still referencing the old player. In that case I'd look for something like = sound as it sets the reference to whatever table the variable happens to contain at the time.

1 Like

Thanks for the ideas! I added collectgarbage() and saw no difference. I already had an experimental “cleanup” function I ran before ever = snd.___ just as you say, so it was easy to add an explicit collection there:

function falseSignalClear() --Clear before re-using
	falseSignalSound:stop()
	falseSignalSound = nil
	collectgarbage()
end

What’s happening has always been pretty clear in the malloc map:

  • Sound loaded and played: memory occupied
  • Next sound loaded and played in same variable: makes the original memory get freed up

Except when it gets in a "mood” where the same exact code will NOT free up the memory and it builds until a crash. How this “mood” can survive Playdate reboots is beyond me.

So now, even with the collectgarbage(), I was able to see each sound being freed up properly for a few minutes... and then it got in a “mood” and stopped freeing it up—during the same run of the game—and built to a crash.

I’ve even seen this now in a deterministic screen of my game, where the sound files aren’t even randomly-chosen and the code that plays them is different. That screen just started crashing in a way I never saw before, despite no code changes! Memory gobbled, and nothing helped. Then the problem resolved itself—making it feel like it was just luck that I’d never tested that screen when the system was in the “bad mood.”

I wish I could make a repro case, but “good moods” and “bad moods” last for hours sometimes, so I can’t reliably relate what’s happening to code changes: maybe my simple test case DOES show the problem... just not today!

A bug in my code is always the first suspect... but the way the SAME code runs for hours with no problem, then arbitrarily starts crashing EVERY session, has me stumped. You’d think random luck triggering a bug in my code would result in good sessions and leaking sessions alternating more randomly.

But it’s not like that—it’s long runs of repeated good sessions, and long runs of repeated leaking sessions. :person_shrugging: