-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Event after_commit doesn't execute hooks after each commit() #4794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
At the moment, I don't think any of the weakref usage in registry.py or elsewhere is actually involved with the issue. The function is being GC'ed because the once wrapper is losing references to it, it looks like the fix, at least the most expedient one, would be this:
this is because event.listen(fn) was meant to imply that "fn" is now strongly referenced. this is because it needs to fire off even if it isn't referenced anywhere else. which you might note indicates the collection of listeners will keep growing in your example, even though only one is active. The "once" flag was never intended to manage memory and it was assumed that hooks added with "once", even though they are only invoked once, would last in the collection forever. "once" wasn't intended as a means of adding event hooks per-call that then get automatically deregistered afterwards. But since you are using it that way, maybe it should work anyway. but that's not likely something I can commit to 1.3 if it's even doable since this implies event de-registration within the scope of the event being fired which I'm not sure is possible (haven't tried though). the documentation doesn't state much about "once" too explicitly, that it wasn't intended for this use is sort of implied in "Event registration and removal is not intended to be a "high velocity" operation; it is a configurational operation. For systems that need to quickly associate and deassociate with events at high scale, use a mutable structure that is handled from inside of a single listener.". So basically if I fix the bug using the above strong reference, your application will leak memory. If I figure out automatic de-registration, that would likely be based on the go() wrapper sending out some kind of signal. overall your immediate issue is better served by not using "once" to imply automatic de-registration, and to just register a hook once on your Session and handle swapping out internal state separately. |
eeyeup, this might be not possible-ish, well, not easy-ish:
and we get:
|
OK here is the third and most promising approach, however there is a test failing on this, this registration stuff is EXTREMELY complicated:
|
the above patch means all the go() functions stay in the collection, but if a new one comes in with the same key, meaning the inner function was GC'ed, it gets replaced here. |
the previous approach makes it impossible to detect the same listener function being registered twice in a wrapped scenario (at least I can't get it to work yet, test/base/test_events.py -> test_double_event_wrapped, for #3199). Another approach. Store weakrefs to the functions instead of numerical ids, but then we aren't managing memory anymore, the collection grows unbounded just as if we had the only_once() wrapper strong reference it, so this seems more complicated than it needs to be:
all along, the assumption is, if we are applying a wrapper to the incoming function, the wrapper is still strong referencing the function. so I can "fix" the issue with the first approach but that means once=True is not automatically de-registering or managing memory. |
Mike Bayer has proposed a fix for this issue in the master branch: Strong reference listen function wrapped by "once" https://gerrit.sqlalchemy.org/1393 |
Mike Bayer has proposed a fix for this issue in the rel_1_3 branch: Strong reference listen function wrapped by "once" https://gerrit.sqlalchemy.org/1394 |
Fixed issue in event system where using the ``once=True`` flag with dynamically generated listener functions would cause event registration of future events to fail if those listener functions were garbage collected after they were used, due to an assumption that a listened function is strongly referenced. The "once" wrapped is now modified to strongly reference the inner function persistently, and documentation is updated that using "once" does not imply automatic de-registration of listener functions. Fixes: #4794 Change-Id: I06388083d1633dcc89e8919eb1e51270f966df38 (cherry picked from commit e22d2b0)
Has there been any consideration to add a session.on_commit(..) method? I find that using the ORM events statically isn't very flexible. If I have a user and want to update theirs permissions (assuming relationships like User 1-n UserPermission n-1 Permission), the signal that I want to send after_commit is "user_permissions_updated". At the dao/dal layer, I know exactly what signal I want to send once committed, so it makes sense to be able to register the callback at that time.
If I want to use the
Which is very indirect. A simpler case is something like the flow for verifying a user. In the dao I can do:
But if I don't have the I have tried implementing something similar to the solution of @dmitry-moroz , but I am not sure how robust it is, and am having problems handling rollbacks, etc. It would be very useful to have something similar to this https://docs.djangoproject.com/en/3.2/topics/db/transactions/#performing-actions-after-commit - where you can register callbacks, which are discarded on rollbacks. |
hi @reritom - While I appreciate that you are looking for an API that resembles what the user posting this bug report was originally trying to do, this current document regards a small bug in the event registration system that is unrelated to your topic. Can I ask that going forward you create a new discussion to introduce new topics going forward? you can of course refer to issues that mention similar issues by indicating their number. I've added your question and an appropriate recipe for this use case at #6888. |
@zzzeek Apologies for bringing it up in the wrong place. This was the only thread anywhere that I could find that mentioned the desired API. |
Hi. I have found strange behavior of events in sqlalchemy. If I use
after_commit
event then do commit and continue set new hooks and make new commits - some hooks are not executed.Code:
Full output:
Env:
This bug can be reproduced with old version, e.g. SQLAlchemy==1.1.14 and Python 2.7.9.
Looks like usage of
weakref
inevent/registry.py
is a cause of the problem. Hook is not executed when hisid()
was used before. So, when Python reuse the sameid()
(saves new hook in the same part of memory where previous hook was saved) this new hook will not be executed. Because this hook is seen as "already in listeners" for sqlalchemy, and that is why new hook will not be added to listeners and will not be executed because there is optiononce=True
:)So, as a result you can use following workarounds:
The text was updated successfully, but these errors were encountered: