crash after destroying an ODE world/space

It looks like after calling destroy on an ODE world/space pair that have been set up to use auto-collide, any other auto-collide pair will crash when it tries to step. The more objects that are in the system, the faster/more likely it will crash. With 100 bodies it almost always crashes immediately. The crash occurs in OdeSpace::auto_callback as it is trying to make a contract between two bodies which must be from the space which was deleted.
I’m pretty sure this is a bug, but it could be that I’ve missed something in what is necessary to clean up and remove an ODE world/space. Here is the stripped-down code which exposes the crash (after 8 seconds, the world that is populated is destroyed):

from panda3d.core import *
from panda3d.ode import *
loadPrcFileData('', 'window-type none')
loadPrcFileData('', 'audio-library-name null')
from direct.showbase.ShowBase import ShowBase


class Game(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.levels = []
        level1 = Level(self)
        level2 = Level(self)
        for i in range(100):
            Entity(level1, Point3(i, 0, 0))
        taskMgr.add(self.update, 'game update', priority=-99)

    def update(self, task):
        if len(self.levels) > 1 and globalClock.getFrameTime() > 8:
            self.levels[1].destroy()
        return task.cont


class Level(object):
    def __init__(self, game):
        self.game = game
        self.entities = []
        self.world = OdeWorld()
        self.world.initSurfaceTable(1)
        self.world.setSurfaceEntry(0, 0, 0.5, 0.1, 0.1, 0.5, 0.00001, 0, 0)
        self.space = OdeHashSpace()
        self.joint_group = OdeJointGroup()
        self.space.setAutoCollideWorld(self.world)
        self.space.setAutoCollideJointGroup(self.joint_group)
        self.steptime = 1.0 / 60.0
        self.simtime = 0.0 # accumulated time to simulate
        self.game.levels.append(self)
        self.step_task = taskMgr.add(self.step, 'physics step')

    def destroy(self):
        print 'destroying level', self.game.levels.index(self)
        for thing in self.entities[:]:
            thing.destroy()
        self.step_task.remove()
        self.step_task = None
        self.space.destroy()
        self.space = None
        self.world.destroy()
        self.world = None
        self.joint_group.destroy()
        self.joint_group = None
        self.game.levels.remove(self)
        self.game = None

    def step(self, task):
        self.simtime += globalClock.getDt()
        if self.simtime >= self.steptime:
            for i in range(int(self.simtime / self.steptime)):
                print 'stepping level', self.game.levels.index(self)
                self.space.autoCollide()
                self.world.quickStep(self.steptime)
                self.joint_group.empty()
            self.simtime = self.simtime % self.steptime
        return task.cont


class Entity(object):
    def __init__(self, level, bpos):
        self.level = level
        self.body = OdeBody(self.level.world)
        mass = OdeMass()
        mass.setBox(1000, 1, 1, 1)
        self.body.setMass(mass)
        self.geom = OdeBoxGeom(self.level.space, Vec3(1, 1, 1))
        self.geom.setBody(self.body)
        self.body.enable()
        self.body.setPosition(bpos)
        self.level.entities.append(self)

    def destroy(self, task=None):
        self.geom.destroy()
        self.geom = None
        self.body.destroy()
        self.body = None
        self.level.entities.remove(self)
        self.level = None


Game().run()

I don’t understand. Where is the crash occurring exactly? If you destroy the space, how can it crash in autoCollide afterwards?

After the second space is destroyed, the crash occurs in the auto-collide of first space which is not destroyed.

If someone could test this to confirm the crash that would be a great step towards identifying it as a bug or not.

My guess is that there is some data shared between the ODE spaces that should not be.

Sorry, I don’t currently build with ODE, so it’s difficult for me to get to at the moment. If you don’t want this to be forgotten, please file a bug report at the bug tracker.

An update on this issue.
The OdeSpace class is set up with only one static OdeJointGroup shared between all OdeSpaces. That would mean if you assigned a different OdeJointGroup to each space, you would just be overwriting the joint group of the first OdeSpace with the second one.
When the joint group for the second OdeSpace is destroyed, the first OdeSpace will crash when it tries to use the destroyed joint group.
Not destroying the joint group just means that the contacts are getting stored in the wrong joint group (which will not be getting emptied) and it will eventually overflow and crash.
I am working on what I hope is a fix and will file a bug with the patch.