On Sep 4, 2014, at 8:17 PM, Lonnie Hutchinson <lonn...@skytap.com> wrote:

> The session executes in one of many web-server threads, but there is no 
> multi-threading with respect to the session or the objects. The session that 
> was closed is within an initializer and upon return a method on the object is 
> executed that creates a new session and tries to attach objects retrieved 
> from the first session. This method does spawn threads but the exception 
> happens before any threads are spawned. The thread target does not take 
> objects, only python strings. only identifiers used in new sessions to query 
> the objects again. The milliseconds I referred to are between the close() of 
> the first session and the add() that fails in the second session.
> 
> The code I say appears to expect the session_id to remain on detached states 
> in certain situations is that the check in Session._attach checks not only 
> the session_id but that the referenced session still exists in _sessions:
>         if state.session_id and \
>                 state.session_id is not self.hash_key and \
>                 state.session_id in _sessions:
>             raise sa_exc.InvalidRequestError(
>                 "Object '%s' is already attached to session '%s' "
>                 "(this is '%s')" % (orm_util.state_str(state),
>                                     state.session_id, self.hash_key))
> 
> I am interested in this in the hopes that sheds light on the source of the 
> intermittent failures.

yeah that code is intended to detect when an object is added to a session that 
hasn't been removed from another.

However, the implementation for session.close() has a direct trace that leads 
to the "session_id" set to None for all objects that are either in the identity 
map, or in the "_new" (pending) collection.  There's no ambiguity there.

> In debugging this I have noticed that after the first session has been 
> closed() and the initializer has returned the session remains in _sessions. 
> However, if I call gc.collect() the session is removed, suggesting it just 
> hasn't been fully cleaned up yet.

that's not really accurate.  There's a collection of all the Sessions in the 
weak referencing map _sessions, and Python's GC may not in fact remove those 
sessions in a deterministic way.  But that has no bearing upon testing objects 
that have been removed from that session, and therefore have no session_id, if 
they belong to that session - their session_id has been cleared out.


> Since it takes both the state referencing the session and the session still 
> existing in _sessions, I can't help but wonder if this is a gc issue. 
> Unfortunately it is not feasible to add gc.collect() calls in our production 
> application as it introduces too much overhead.

I will tell you how this *can* happen.  If you have a Session, and you put 
objects in it, then you *don't* close the session; the session just is 
dereferenced, and is garbage collected at some point by the above mentioned 
weak dictionary.   The objects within that session will *not* have anything 
done to their session_id in that case.   If a new session is started up, and it 
happens to take over the same in-memory ID as the one that was just GC'ed, it 
will have the same hash key, and will then exist within _sessions.  Then you 
move your objects to a third session; they will in fact have a session_id that 
is represented in _sessions, albeit against a different session than they 
started with, and you get the error.

So if that's the case, then the issue here is the result of objects belonging 
to ad-hoc sessions that are not closed() explicitly, then those objects are 
shuttled along to another session, while busy thread mechanics in the 
background keep creating new sessions that occasionally use the same session 
identifier and produce this collision.

There's ways to test for this, like assigning a unique counter to each Session 
within an event, like the begin() event, then using the before_attach event to 
verify that session_id is None, and if not, take a peek at that Session and its 
counter, compare it to something on the state.




-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/d/optout.

Reply via email to