Search code examples
nsautoreleasepoolpyobjc

PyObjc autorelease pool


EDIT: Thanks for the advice. I'm still not clear on how the autorelease pools are actually handled.

Here's the actual code:

import platform, time 

if (platform.system().lower() == "darwin"):
    from AppKit import NSSpeechSynthesizer
    from Foundation import NSAutoreleasePool

[class's init function]
def __init__(self):
    if (platform.system().lower() != "darwin"):
        raise NotImplementedError("Mac OS X Speech not available on this platform.")
    self.ve = NSSpeechSynthesizer.alloc().init()

[function that throws the errors normally]
def say(self,text,waitForFinish=False):
    pool = NSAutoreleasePool.alloc().init()
    self.ve.startSpeakingString_(text)
    if (waitForFinish == True):
        while (self.ve.isSpeaking() == True):
            time.sleep(0.1)
    del pool

If I load up the python console and merely import the module, it fails with this traceback:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "audio/__init__.py", line 5, in <module>
    from speech_mac import *
  File "audio/speech_mac.py", line 19, in <module>
    class SpeechSynthesizer(object):
  File "audio/speech_mac.py", line 56, in SpeechSynthesizer
    del pool
NameError: name 'pool' is not defined

It looks like somehow Python is not retaining knowledge of the "pool" variable. I'm really confused as to how all of this works - as I said in the original post, I'm not well versed in ObjC or the OS X frameworks.

I read on Apple's documentation about using NSAutoreleasePools and it sounds like I should be doing exactly what you guys suggested - creating the pool, running the code that normally seems to throw the exception, then destroying the pool. As you can see, though, this isn't working quite the way one would expect it to.

If I leave off del pool then the code does run and the errors are suppressed but, as written in the original post, on unpredictable occasions, when the app is actually exiting, it crashes with the OS X system crash shown in the original post.


I found some great code here on SO to interface directly with Mac OS X's speech synthesizer engine. It basically imports AppKit, instantiates NSSpeechSynthesizer, then passes its methods and stuff through to Python. Works great.

I wrapped the code into a class for easy use.

Only problem is, in my app the speech is running on a separate thread, because it's connected to a wxPython app.

On my console, as is, I get floods of messages like this each time something is spoken:

objc[53229]: Object 0x53d2d30 of class OC_PythonString autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug

The app runs fine, but "just leaking" scares me - sounds like I'm staring down the barrel of a memory leak!

After doing some research, I found out you can instantiate an autorelease pool in Python from pyobjc like this:

from Foundation import NSAutoreleasePool

def __init__(self):
    self.pool = NSAutoreleasePool.alloc().init()

def __del__(self):
    self.pool.release()

Doing this stopped the error messages from appearing, however, and it's completely hit or miss, on app exit now I sometimes get a crash that's bad enough to bring up the OS X crash dialog. The console spits out the following:

objc[71970]: autorelease pool page 0x4331000 corrupted 
  magic 0xf0000000 0xf0000000 0x454c4552 0x21455341
  pthread 0xb0811000

To experiment, I moved the pool allocation into the function that is throwing the original messages whenever it runs (the Speak function). Doing that also suppressed the messages, but this time, when the app exited, I got:

Bus error: 10

with the following in the crash dialog that popped up:

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_PROTECTION_FAILURE at 0x0000000000000010

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0   libobjc.A.dylib                 0x926ec465 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 525
1   com.apple.CoreFoundation        0x99cc3a73 _CFAutoreleasePoolPop + 51
2   com.apple.Foundation            0x90fca116 -[NSAutoreleasePool release] + 125

Looks like the AutoreleasePools are still being freed as the object is destroyed (that makes sense) but it's still crashing.

I'm not familiar deeply with Objective C or the NS foundation classes in OS X, so I'm not sure how to proceed with debugging this.

Advice?

Thanks!


Solution

  • Ok, I modified code like this:

    def say(self,text,waitForFinish=False):
        pool = NSAutoreleasePool.alloc().init()
        self.ve.startSpeakingString_(text)
        del pool
        if (waitForFinish == True):
            while (self.ve.isSpeaking() == True):
                time.sleep(0.1)
    

    Now I'm no longer getting the import error, and the engine is not throwing the pool errors. I'll keep testing and see if I get the random crashes I mentioned before...