Monday, May 19, 2014

Android: Stop your ringtones before sending them to the garbage collector

For some time, I've been aware that the warning "MediaPlayer finalized without being released" was occasionally showing up in my app's logcat output. This was a bit mystifying since I don't use MediaPlayer anywhere, but there were more important things to work on.

Today I finally got fed up and tried to figure out what was going on. It turns out that ringtones are the culprit. RingtoneManager.getRingtone() calls open() on the Ringtone object it returns. Ringtone.open() sets up a MediaPlayer. And this MediaPlayer is only released if you call Ringtone.stop() before letting the thing get GC'ed. Note that you do not actually need to play the ringtone for this to be true. As far as I can tell, none of this is documented.

Anyway, I have no idea how much harm there is in neglecting to stop your ringtones and release their media players, but doing so will at least get rid of a warning message.

Monday, May 12, 2014

A trick for tracking down tough memory leaks in VC++

Memory leaks are a common problem in C++. Even if you use smart pointers everywhere, you can still leak memory if you create a shared_ptr cycle. If this happens in a debug build and you've enabled leak detection as described here, Visual Studio will alert you in the output at shutdown:
Detected memory leaks!
Dumping objects ->
C:\PROGRAM FILES\VISUAL STUDIO\MyProjects\leaktest\leaktest.cpp(20) : {10021} 
normal block at 0x00780E80, 64 bytes long.
 Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
Object dump complete.



The filename and line number may or may not be present, and may or may not be useful. If they identify one of your own files, and if there aren't too many different paths to the indicated location, they might be all you need to know. But if they point to something in the guts of vector or make_shared, they tell you practically nothing.

That "{10021}," on the other hand, has potential. What it's telling you is that the block returned by dynamic allocation #10021 was never freed. Unfortunately, by this point it's too late to say what allocation #10021 was. You can run the program again and use this technique to break when it occurs, but if there's any indeterminacy in what you allocate and when, it's not going to be the same call stack. In particular, if your application is multi-threaded (and what applications aren't anymore), none of the allocation numbers after the second thread spawns will be stable from one run to the next.

The third-party tool Visual Leak Detector is designed to help you in these situations, but (at least in my experience) it doesn't always give the right answer. So here is a sort of brute-force approach that can be used when all else fails.

The brute-force leak detector


(tested with VS2008-2012; things may be different in 2013)
  1. Get debug symbol info for the VC++ runtime if you don't have it already. The quickest way is Tools > Options > Debugging > Symbols > check "Microsoft Symbol Servers."
  2. Comment out code that spawns nonessential threads if they aren't part of the problem, and do anything else you can think of to temporarily reduce concurrency. The goal is to get to a place where the leaked allocation's number, while not completely stable, is at least restricted to a known, reasonably narrow range.
  3. Set a breakpoint in dbgheap.c at the top of _heap_alloc_dbg_impl(). If intellisense is working, you can get to this file by typing _crtBreakAlloc somewhere in your code and doing a "Go To Definition" on it. Otherwise check "[drive]\Program Files (x86)\Microsoft Visual Studio [version]\VC\crt\src."
  4. Right-click the breakpoint dot and choose "Condition..." Make the condition "_lRequestCurr >= [minimum] && _lRequestCurr <= [maximum]," where "[minimum]" and "[maximum]" are the endpoints for the range you observed in step #1.
  5. Right-click the breakpoint dot again and select "When Hit...". Choose "Print a message" and make that message "***{_lRequestCurr} : $CALLSTACK". Make sure "Continue execution"  is checked.
  6. Start the program. It will run much more slowly than usual due to the need to evaluate the tracepoint each time the code passes over it. If you already have the leak narrowed down to a particular area of the code, it will help to disable the tracepoint until the suspect code is reached.
  7. Once you're sure you've done whatever it takes to trigger the leak, shut the program down. Note the allocation numbers in Visual Studio's memory leak messages.
Every time a dynamic allocation happens, the tracepoint will dump the allocation number to the output followed by its callstack. So once the program exits and you see a message like the one above indicating that allocation 10021 was leaked, you can simply search the output for "***10021" to find the corresponding callstack. With any luck, that will tell you what you need to know.