Crash when closing Panda using ODE TriMeshGeom

It took me a while to whittle this down, but I have this barebones example of a crash that occurs when closing Panda.
One line makes the difference between crashing or not, but it seems like it should work either way.
In the example of the code that crashes, if there are one or two Geoms in the EGG file it will not crash, but if there are three or more it will crash.

This code will crash:

from pandac.PandaModules import *
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject

class Game(DirectObject):
    def __init__(self):
        self.accept('escape', __import__('sys').exit, [0])
        self.world, self.space = OdeWorld(), OdeSimpleSpace()
        model = loader.loadModel('test').getChildren()[0]
        for mesh in model.findAllMatches('**/+GeomNode'):
            meshdata = OdeTriMeshData(mesh)
            trimesh = OdeTriMeshGeom(self.space, meshdata)

Game()
run()

This code will work:

from pandac.PandaModules import *
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject

class Game(DirectObject):
    def __init__(self):
        self.accept('escape', __import__('sys').exit, [0])
        self.world, self.space = OdeWorld(), OdeSimpleSpace()
        model = loader.loadModel('test')
        for mesh in model.findAllMatches('**/+GeomNode'):
            meshdata = OdeTriMeshData(mesh)
            trimesh = OdeTriMeshGeom(self.space, meshdata)

Game()
run()

This is the EGG file used to test:

<Group> grp1 {
  <VertexPool> grp1_verts {
    <Vertex> 0 {
      127.382 1.67284 59.5235
    }
    <Vertex> 1 {
      100.985 3.79684 39.5173
    }
    <Vertex> 2 {
      66.8139 6.25141 56.1555
    }
  }
  <Polygon> {
    <VertexRef> { 0 1 2 <Ref> { grp1_verts } }
  }
}
<Group> grp2 {
  <VertexPool> grp2_verts {
    <Vertex> 0 {
      127.382 1.67284 59.5235
    }
    <Vertex> 1 {
      100.985 3.79684 39.5173
    }
    <Vertex> 2 {
      66.8139 6.25141 56.1555
    }
  }
  <Polygon> {
    <VertexRef> { 0 1 2 <Ref> { grp2_verts } }
  }
}
<Group> grp3 {
  <VertexPool> grp3_verts {
    <Vertex> 0 {
      127.382 1.67284 59.5235
    }
    <Vertex> 1 {
      100.985 3.79684 39.5173
    }
    <Vertex> 2 {
      66.8139 6.25141 56.1555
    }
  }
  <Polygon> {
    <VertexRef> { 0 1 2 <Ref> { grp3_verts } }
  }
}

Unfortunately this small change does not prevent my larger program from crashing.

I wrestled with this too. It was discussed in a previous thread here: [Panda crashing when removing ODE object)

Don’t use sys.exit. Make a customized cleanup function something like this:

def cleanup_and_exit(world, space):
    for geom in [space.getGeom(i) for i in range(space.getNumGeoms())]:
        geom.destroy()
    space.destroy()
    world.destroy()
    base.userExit()

Technically, OdeSpace.destroy() should automatically destroy all contained geoms but apparently there is a bug there.

The solution!
Make sure you hold a reference to the OdeTriMeshData until after you are done with the OdeTriMeshGeom.

Example of working code:

from pandac.PandaModules import *
import direct.directbase.DirectStart
from direct.showbase.DirectObject import DirectObject

class Game(DirectObject):
    def __init__(self):
        self.accept('escape', __import__('sys').exit, [0])
        self.world, self.space = OdeWorld(), OdeSimpleSpace()
        model = loader.loadModel('test')
        self.meshdatas = {}
        for mesh in model.findAllMatches('**/+GeomNode'):
            meshdata = OdeTriMeshData(mesh)
            trimesh = OdeTriMeshGeom(self.space, meshdata)
            self.meshdatas[trimesh] = meshdata

Game()
run()

I was not holding a reference since I figured the OdeTriMeshData was only needed to create the OdeTriMeshGeom, but for some reason the OdeTriMeshGeom keeps a pointer to the OdeTriMeshData even though it doesn’t seem to need it past the initial creation.
The OdeTriMeshData is getting garbage collected, presumably through Python, and then when the OdeTriMeshGeom tries to clean up it goes poof because that data is already deleted.
I still feel that the OdeTriMeshData should not get garbage collected since the OdeTriMeshGeom is supposed to be holding onto it, but this is a working solution at least.

As a side note, the “working” code in the first post worked with 1, 2 or 3 geoms, but not 4.

Ah, with your discoveries, I was able to find the true solution, and commit a fix.

It’s a little more complicated internally. In fact, the OdeTriMeshGeom does keep a pointer to the OdeTriMeshData object for its lifetime. What’s actually happening is at program exit, the table that contains the cache of OdeTriMeshData objects destructs in semi-unpredictable order with the other static program objects. If it happens to destruct before the last OdeTriMeshData object destructs, then when the OdeTriMeshData object eventually destructs it will attempt to access an already-destructed table, and kaboom. Unless you’re lucky and it doesn’t kaboom, because it might not.

This semi-unpredictability explains why it is so complicated to reproduce. It also means that fixes like the above are even still not guaranteed to solve the problem. (But explicitly cleaning up all of the ODE objects before exiting should be reliable.)

David

Anyway good job drwr. Maybe you could tackle the second crash-bug reported in codepad.org/IQthQ8ru )?