Skip to content

append event can fire after the parent object is already gc'ed #2046

Closed
@sqlalchemy-bot

Description

@sqlalchemy-bot
Collaborator

Migrated issue, originally created by Anonymous

I've noticed a small problem going from 0.5.5 to 0.6.6 which the attached unit test should illustrate. It runs without errors on 0.5.5 but the first test errors out on 0.6.6. The second test shows a workaround. No biggie but curious, I think. Here's the output when running this test:

sas% python test_sa66.py
sqlalchemy.version: 0.5.5
..
----------------------------------------------------------------------
Ran 2 tests in 0.031s

OK



sas% python test_sa66.py
sqlalchemy.version: 0.6.6
F.
======================================================================
FAIL: test_01_failing_on_sa66 (__main__.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_sa66.py", line 30, in test_01_failing_on_sa66
    self.assertEqual(1, self.db.m2m.count())
AssertionError: 1 != 0

----------------------------------------------------------------------
Ran 2 tests in 0.033s

FAILED (failures=1)

Attachments: test_sa66.py

Activity

sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Anonymous wrote:

Bugger, I wanted to CC myself but turns out I can't modify after submission :) Here's my address; sas@abstracture.de if you'd be so kind and add it. Thanks!

sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

OK well, yeah, nothing has actually "changed" in SQLA since 0.5 regarding what happens here, its a garbage collection artifact allowing it to work in 0.5. If I do this to 0.5:

--- a/lib/sqlalchemy/orm/state.py	Wed Sep 15 12:02:46 2010 -0400
+++ b/lib/sqlalchemy/orm/state.py	Thu Feb 10 10:24:56 2011 -0500
@@ -277,6 +277,8 @@
 
         self.modified = True
         if self._strong_obj is None:
+            import gc
+            gc.collect()
             self._strong_obj = self.obj()
 
     def commit(self, dict_, keys):

it fails there too.

Will poke around and see if we can find some way to keep obj() (in this case it's your MappedFoo) from falling out of scope while its receiving an append event, though that suggests artificially creating a reference, always dangerous.

sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Changes by Michael Bayer (@zzzeek):

  • removed labels: access
  • added labels: orm
  • changed title from "Regression in 0.6.6" to "append event can fire after the parent object is a"
  • set milestone to "0.7.0"
  • set assignee to "zzzeek"
sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

OK thanks for reporting this I'm happy with the solution which is to raise an exception for this condition. It's a warning in 0.6 to ensure existing code that "works" regardless continues to run.

e155b7b
036d2c1

sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Changes by Michael Bayer (@zzzeek):

  • removed labels: low priority
  • added labels: high priority
  • changed status to closed
sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

that should be d8aa338 for the 0.6 branch.

sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Changes by Michael Bayer (@zzzeek):

  • changed milestone from "0.7.0" to "0.6.7"
sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Anonymous wrote:

Thanks for the quick response and fix! Just to understand the implications, you're saying (in CHANGES) that

foo.first().bars.append(b)

should fail, because in this rare case append is sent to an already dereferenced parent object. I suppose the 'proper' way to write this is then

f = foo.first()
f.bars.append(b)

as I did in my work-around. I usually don't chain calls like that anyway but I'm still surprised that 'chaining' vs 'not-chaining' behaves in such a fundamentally different way. Is this due to the way mapped objects work? I wouldn't have expected anything to be garbage collectable on that line (but then again I don't know much about the details of SQLAlchemy).

Just curious to understand what's happening :)

sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Michael Bayer (@zzzeek) wrote:

Replying to guest:

Thanks for the quick response and fix! Just to understand the implications, you're saying (in CHANGES) that
as I did in my work-around. I usually don't chain calls like that anyway but I'm still surprised that 'chaining' vs 'not-chaining' behaves in such a fundamentally different way. Is this due to the way mapped objects work? I wouldn't have expected anything to be garbage collectable on that line (but then again I don't know much about the details of SQLAlchemy).

Just curious to understand what's happening :)

nothing to do with SQLAlchemy, except that SQLA needs a reference to the parent object when an append occurs.

You can try this at home!

class fancylist(list):
    def append(self, obj):
        print "appending an obj!"
        list.append(self, obj)

class Foo(object):
    def __del__(self):
        print "foo is gc'ed !"
    def __init__(self):
        self.bars = fancylist()

def give_me_a_foo():
    return Foo()

give_me_a_foo().bars.append(5)

output:

classics-MacBook-Pro:sqlalchemy classic$ python test.py
foo is gc'ed !
appending an obj!

The "foo is gc'ed" message prints before the "append" message does - Foo is gone by the time you're appending. If anything, the fact that this also happens with SQLA is because we've worked so hard to not get in the way of normal Python gc behavior ;)

sqlalchemy-bot

sqlalchemy-bot commented on Feb 10, 2011

@sqlalchemy-bot
CollaboratorAuthor

Anonymous wrote:

Oh, I see, it never occurred to me that first() returns a new object that's not being referenced. In my mind it was more akin to foo0, i.e. a reference to an object retained elsewhere. Thanks for clearing that up!

added this to the 0.6.7 milestone on Nov 27, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sqlalchemy-bot

        Issue actions

          append event can fire after the parent object is already gc'ed · Issue #2046 · sqlalchemy/sqlalchemy