Re: (long) improving async support in psycopg
Jan Urbański <[email protected]> Wed, 24 Mar 2010 04:38:48 +0100
| Newsgroups | gmane.comp.python.db.psycopg.devel |
|---|---|
| Message-ID | <[email protected]> |
On 23/03/10 01:38, Jan Urbański wrote:
> I'll keep on working on exposing more async features of the C library
> and adding more unit tests (I've started another thread with the fix for
> the test suite for PG 9.0).
Hi again,
here are some notes I took when looking through the async code in psycopg2:
PQsendQuery can return an error is the socket buffer to the server is
full. Currently psycopg2 will react with raising an OperationalError.
Another thing is that you should be calling PQflush after using
PQsendQuery until you get a 0 from PQflush. My idea was to add a method
on the cursor that would call PQflush for you, like issent().
This means that the safe way of doing async access to the DB would be:
c = conn.cursor()
# make sure the socket is writable
# so we don't get an exception from PQsendQuery
_, _, _, = select.select([], [c.fileno()], [])
# execute the async command
c.execute("select * from pg_class", async=True)
# check if the data has been sent
wait = not c.issent()
# while there's still data to be sent to the server,
# wait for the socket to become writable and flush
while wait:
_, _, _ = select.select([], [c.fileno()], [])
wait = not c.issent()
# now wait for the data to arrive at the socket
wait = True
while wait:
_, _, _ = select.select([c.fileno()], [], [])
wait = not c.isready()
print c.fetchall()
Another thing that I'm wondering about: why is the fileno() method doing
PQflush? The comment says it's to make sure the user uses select()
safely, but AFAICS it does not really do that. First of all one PQflush
might not be enough to empty the write buffer and second of all, getting
the file descriptor should not attempt to flush the outgoing buffer
queue. Instead, the issent() method would be used for that.
Next question is about pq_fetch and the loop where it does pq_is_busy
and uses select() internally. Is this loop necessary at all? If the
execute() call on the cursor was synchronous, there's no need to check
the status of the connection, or to do a select. PQexec would block
until all data has been transferred.
If the execute() call was asynchronous, then a client should enter a
isready() loop doing select() on his own, and eventually get all the
result, so that loop would also not be necessary. I think it could just
be dropped, maybe after making sure that pending NOTIFY messages are
processed.
That brings me back to the issue of doing a fetch() on an async cursor
that has not received all data yet. The current code blocks until it
gets all data, but I think that an easier solution would be to just
error out if there's an async cursor that has not consumed all input
yet. Anyone using async cursors would implement that select() loop
anyway, so he will consume all input. That would make it possible to
drop some things like needsfetch from the cursor structure and simplify
the code around that quite a bit.
Another issue are server-side asynchronous cursors. ATM I don't see any
sane way to support them, because after creating a named async cursor
and executing something asynchronously with it, using isready() to loop
will just make you wait for the DECLARE CURSOR to return. After that the
only way to actually do the query is to call fetch*(), which will block.
The problem is that the fetch*() method has to return the result, so
it's too late to ask the user to use select() after that. The only thing
I came up was making async server-side cursors throw errors for calls to
fetch() that are not preceded by a call to a new method called load(n),
that will issue the FETCH query and will make it possible to use
isready() to wait for the completion. After that fetch() would have the
data already on the client side and could throw errors if you try to
access data that has not been loaded yet.
I won't bother you with the details of how an async connection building
procedure could look like, I just want to raise one last issue. The
connection class issues a couple of queries, like SHOW client_encoding
or SHOW default_transaction_isolation. These are issues synchronously
and there is no control over them. That would even be OK, hopefully an
application would not create and close lots of connection during it's
lifetime. The bad thing is the BEGIN query that gets issued
synchronously and even less convenient is that COMMITs are synchronous,
and these could take some time on systems with a certain configuration.
As you can see there are quite a few things to look at to be able to use
psycopg2 as a fully async library (remember that my use case is Twisted,
so every call that can potentially block is bad). I'm beginning to
wonder if it's actually feasible...
Thanks for reading that overly long post,
Jan
_______________________________________________
Psycopg mailing list
[email protected]
http://lists.initd.org/mailman/listinfo/psycopg