Question

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!

Was it helpful?

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

OTHER TIPS

I don't know Python or PyObjc but Cocoa is full of classes that assume there is an autorelease pool in place at all times when you use them.

An autorelease pool is typically created once for each pass of the event loop on the main thread, and when you create a background thread you are required to setup at autorelease pool for it.

So, the first line of code in your thread should be to create an autorelease pool, and the last line should be to tell the pool to delete itself and all the objects it has collected.

Also, if there is any section of code inside your thread that's going to take more than about 1 millisecond to execute, you should be wrapping that operation in an autorelease pool too.

An autorelease pool is just an array of objects that have been created temporarily and need to be deleted asap. It lets you allocate memory without having to worry about deallocating it, because it will be done for you when the pool is flushed half a millisecond later.

Failing to create an autorelease pool will not cause any crashes or bugs, but it will cause memory leaks... which will pretty quickly end up being pushed out to the hard drive as virtual memory by the kernel. I wouldn't worry about it too much if this is just experimental code you're playing around with. But definitely fix it in production code.

In the thread you should create an autorelease pool at the start of the run method and release it at the end, but it would be better to clean up the pool more often than that when the thread is long running (that is, when you start the thread and then keep it running until the end of the program) because the pool keeps temporary objects alive until it is flushed.

When you have a queue or some other mechanism to send requests to the speech thread you could do something like:

def run(self):

    while True:
       request = self.get_work() # fetch from queue, ....

       pool = NSAutoreleasePool.alloc().init()
       self.use_cocoa_apis()
       del pool
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top