Thursday, August 19, 2010

Unexpected Mach port refcounts

I recently found two cases where it was not obvious to me that I would have had ownership of a reference of a Mach port, and resulted in a Mach port “leak”:
  1. mach_thread_self() implies adding a MACH_PORT_RIGHT_SEND on the thread port. It must be followed with a matching mach_port_deallocate call when the reference is no longer needed.
  2. Mach messages received from the kernel after registering with thread_swap_exception_ports may contain Mach ports, depending on the flavor of message. Any such ports must be deallocated by the receiver. If you pass the message along, then you can get away with just transferring ownership along as well, presuming that the next mach port in the chain wants a sufficiently similar flavor of exception message as you do.
In our case, the extra un-deallocated references to the thread port would eventually turn into dead names after the thread died, but would never go away. As a result, if you went through enough threads over enough time, you’d starve your application for Mach ports and it would go unresponsive, crash, or otherwise terminate. In addition, we would incur (via the exception messages) ever increasing refcount on the task port, with somewhat unclear implications.1

Although I was able to use the dtrace example earlier to do some early diagnostics, since mach_port_allocate was not the only generator of Mach ports in the process (e.g., the kernel when sending us exception messages), it was not sufficient. Instead, I ended up using Apple’s own sample, MachPortDump, in combination with some strategically placed breakpoints in gdb to try and narrow down when the ports were coming into existence, and what the ports actually were.2

It really is too bad that Apple deems these APIs to be unworthy of documenting. Admittedly, only a few developers really need to get down into this nitty-gritty, but if the only choices are reading the 17-year-old Programming Under Mach whose API references are out-of-date and whose code samples don’t deallocate these ports either, reading the darwin source (not an option for me), pestering ADC, or stumbling around in the dark, I can wager what will happen, and what the quality of the resulting products will be on the OS. Perhaps the consumers will lay the blame at the application developer’s feet, or at Apple’s, or both. In any case, it makes it hard to do things the right way, which I’m pretty sure we all want. (Or at least want slightly more than doing it the wrong way — not doing it at all isn’t an option.)
1What does happen when you wrap the refcount number on a Mach port? Hm…
2MachPortDump just lists what ports are in the process and what kind/rights it has, not what the port is related to. You have to be a little creative to figure out what the port being incremented belongs to, e.g., (mach_port_t)pthread_mach_thread_np((pthread_t)pthread_self()) to figure out what the current thread’s port is. AFAICT, there’s no Mac OS X analog to netmsgserver to annotate ports.


J-Lo said...

I have this problem, but can't figure out how to call mach_port_deallocate(). Do you have an example? mach_port_deallocate() isn't defined by the same include files, isn't documented as taking thread_t (does that inherit from mach_port_name_t?) and it has another parameter. What's an ipc_space_t? Examples I've seen get it from mach_task_self (what is a task?), but that is documented as increasing the refcount on yet another port, which just moves the problem.

I'm lost in all this Mach jargon.

Nathan Herring said...

mach_port_deallocate is defined in /usr/include/mach/mach_port.h, generated from mach_port.defs in the same directory. The task can be retrieved with mach_task_self(), presuming you're operating on the currently running executable. mach_task_self() doesn't increment the refcount on the task port it returns, AFAICT. Even if it did, you could cache the value of mach_task_self(), use it to free the other port, and then use it to free itself.

The short form is that task is an instance of an application. I think that theoretically you could have multiple tasks in one application, but I have never seen it.

J-Lo said...

Thanks, I will give that a try.

(By the way, that's not my real hair, that's fake hair attached to the hat.)