> I went ahead and produced what I hope is a very narrow test case to
> show that I am not explicitly holding onto connections (unless I
> completely misunderstand, which is possible). Here is my test code:
>
> http://paste.pocoo.org/show/91285/
>
Your Session is binding to the same thread local connection that your
"contextual_connect()" method is returning, however while you are
closing out the connection explicitly, you aren't closing the Session,
which keeps the connection opened outside the boundaries of the WSGI
method regardless of the type of pool and configuration in use. The
Session is then garbage collected via asynchronous gc, the connection
is returned to the pool, and the pool's attempt to rollback() the
connection before returning to the pool raises the exception. The
exception does not propagate outwards since it is during garbage
collection. This is why the program keeps running without overall
issue (except for your ISAPI plugin which probably cannot handle that
kind of thing gracefully).
The explicit connection as well as the "threadlocal" strategy are all
unnecessary here. Configuring the sessionmaker() with a bind to a
plain engine i.e. create_engine('sqlite:///mydb.sql'), and making sure
sess.close() is called within the WSGI method are all that's needed.
Pattern here is:
Session = sessionmaker(bind=engine)
sess = Session()
try:
< work with session>
finally:
sess.close()
Alternatively, as I noted previously in http://www.sqlalchemy.org/docs/05/session.html#unitofwork_contextual_lifespan
, using scoped_session in conjunction with Session.remove() at the end
of the request works here as well, as I mentioned this is the practice
that is standard among popular web frameworks such as Pylons.
Pattern here is :
Session = scoped_session(sessionmaker(bind=engine))
sess = Session()
try:
< work with session>
finally:
Session.remove()
Still another way to do this is to eliminate the source of the error
at the pool level - ultimately, the SingletonThreadPool is attempting
to return the connection to the pool and call rollback() on it, which
is why the threaded access fails. If you use NullPool, the connection
is thrown away entirely when closed and nothing is done to it. Any
version of your program will run without errors if NullPool is used -
you'll just get a little overhead in opening more connections which in
the case of file-based sqlite is extremely miniscule. In that case
you can even reuse the same Session object repeatedly across requests
as long as scoped_session is in place to enforce one-thread-per-session.
> Thank you so much for your response, I am extremely grateful.
> However, I am still getting exceptions thrown from SQLite for sharing
> connections across threads.
Unfortuantely I cant reproduce your error now, whereas it was quite
frequent earlier. Attached is a full test script. If the error
you're getting is only with the ISAPI library you're using, that may
be part of the problem.
>
> Are you sure this isn't a problem with pulling the wrong connection
> from the pool?
> I was able to use a NullPool successfully to alleviate my problems.
> However, it still doesn't seem right that I can't get singleton thread
> pooling to work. Am I still doing something wrong?
The SingletonThreadPool in SQLA 0.5 uses Python's threading.local() to
maintain the connection and is extremely simple. SQLA doesn't spawn
any threads and only works within the thread from which is was
invoked, with the huge asterisk that Python's gc.collect() can invoke
cleanup operations in an asynchronous thread, but even in that case
the SingletonThreadPool never associates the connection with the
threading.local() a second time. Just read the source and try
experimenting with it.
If you do test with too many threads, the Python interpreter itself
may be failing in some way. If I run ab against this script with more
than 20 or 30 threads I get a broken socket almost immediately.
>
> When I run the exact same code using PyISAPIe, I get the following log
> file, which shows many exceptions all propagating up to my application
> and hosing it:
Reading Graham's comments on threading:
There are obviously fine grained details when interfacing native C
code with Python threads. Note that the issue he's discussing there
involves the state of a threading.local() being *lost*, which would
not cause the problem we're seeing. We're seeing a mismatch between
the ID of the thread and the threading object itself which is a much
more severe situation.
I've looked into PyISAPIe. Suffice to say this seems to be an
extremely new project. Their homepage is blank:
http://pyisapie.sourceforge.net/
Bug tracker is empty:
http://sourceforge.net/tracker/?group_id=142454
but they do have a google group, with 20 members and about 40 posts:
http://groups.google.com/group/pyisapie/topics
It would be worthwhile to ask about threading.local() there, but it
seems very possible that some interaction on their end is incompatible
with threading.local(). I certainly wouldn't trust native python-
embedded code with that low of a user base on a production site.