--- Coro-Mysql/Mysql.pm 2009/05/30 06:58:22 1.2 +++ Coro-Mysql/Mysql.pm 2011/02/17 02:05:14 1.9 @@ -22,39 +22,62 @@ =head2 CAVEAT -Note that this module must be linked against exactly the same -F library as DBD::mysql, otherwise it will not work. +Note that this module must be linked against exactly the same (shared, +possibly not working with all OSes) F library as +DBD::mysql, otherwise it will not work. Also, while this module makes database handles non-blocking, you still cannot run multiple requests in parallel on the same database handle. If you want to run multiple queries in parallel, you have to create multiple -database connections, one for each thread that runs queries. +database connections, one for each thread that runs queries. Not doing so +can corrupt your data - use a Coro::Semaphore when in doubt. If you make sure that you never run two or more requests in parallel, you -cna freely share the database handles between threads, of course. +can freely share the database handles between threads, of course. Also, this module uses a number of "unclean" techniques (patching an -internal libmysql structure for one thing) and was hacked within a few -hours on a long flight to Malaysia. +internal libmysql structure for one thing) and was initially hacked within +a few hours on a long flight to Malaysia. It does, however, check whether it indeed got the structure layout correct, so you should expect perl exceptions or early crashes as opposed -to data corruption when something goes wrong. +to data corruption when something goes wrong during patching. =head2 SPEED This module is implemented in XS, and as long as mysqld replies quickly enough, it adds no overhead to the standard libmysql communication -routines (which are very badly written). +routines (which are very badly written, btw.). In fact, since it has a +more efficient buffering and allows requests to run in parallel, it often +decreases the actual time to run many queries considerably. For very fast queries ("select 0"), this module can add noticable overhead -(around 15%) as it tries to switch to other coroutines when mysqld doesn't -deliver the data instantly. +(around 15%, 7% when EV can be used) as it tries to switch to other +coroutines when mysqld doesn't deliver the data immediately, although, +again, when running queries in parallel, they will usually execute faster. -For most types of queries, there will be no overhead, especially on +For most types of queries, there will be no extra latency, especially on multicore systems where your perl process can do other things while mysqld does its stuff. +=head2 LIMITATIONS + +This module only supports "standard" mysql connection handles - this +means unix domain or TCP sockets, and excludes SSL/TLS connections, named +pipes (windows) and shared memory (also windows). No support for these +connection types is planned, either. + +=head1 CANCELLATION + +Cancelling a thread that is within a mysql query will likely make the +handle unusable. As far as Coro::Mysql is concerned, the handle can be +safely destroyed, but it's not clear how mysql itself will react to a +cancellation. + +=head1 FUNCTIONS + +Coro::Mysql offers a single user-accessible function: + =over 4 =cut @@ -68,7 +91,9 @@ use Carp qw(croak); use Guard; -use Coro::Handle (); +use AnyEvent (); +use Coro (); +use Coro::AnyEvent (); # not necessary with newer Coro versions # we need this extra indirection, as Coro doesn't support # calling SLF-like functions via call_sv. @@ -77,7 +102,7 @@ sub writable { &Coro::Handle::FH::writable } BEGIN { - our $VERSION = '0.1'; + our $VERSION = '1.02'; require XSLoader; XSLoader::load Coro::Mysql::, $VERSION; @@ -91,22 +116,29 @@ After that, it returns the patched handle - you should always use the newly returned database handle. +It is safe to call this function on any database handle (or just about any +value), but it will only do anything to L handles, others are +returned unchanged. That means it is harmless when applied to database +handles of other databases. + =cut sub unblock { my ($DBH) = @_; - my $sock = $DBH->{sock}; - open my $fh, "+>&" . $DBH->{sockfd} - or croak "Coro::Mysql unable to clone mysql fd"; + if ($DBH->{Driver}{Name} eq "mysql") { + my $sock = $DBH->{sock}; + + open my $fh, "+>&" . $DBH->{sockfd} + or croak "Coro::Mysql unable to clone mysql fd"; - $fh = Coro::Handle::unblock $fh; + if (AnyEvent::detect ne "AnyEvent::Impl::EV" || !_use_ev) { + require Coro::Handle; + $fh = Coro::Handle::unblock ($fh); + } - _patch $sock, $DBH->{sockfd}, tied ${$fh}; - $DBH->{private_Coro_Mysql} = guard { - _unpatch $sock; - undef $fh; - }; + _patch $sock, $DBH->{sockfd}, $fh, tied ${$fh}; + } $DBH } @@ -115,6 +147,56 @@ =back +=head1 USAGE EXAMPLE + +This example uses L and L to implement a +function C, that connects to a database, uses C on the +resulting handle and then makes sure that C<$PApp::SQL::DBH> is set to the +(per-thread) database handle when the given thread is running (it does not +restore any previous value of $PApp::SQL::DBH, however): + + use Coro; + use Coro::Mysql; + use PApp::SQL; + + sub with_db($$$&) { + my ($database, $user, $pass, $cb) = @_; + + my $dbh = DBI->connect ($database, $user, $pass)->Coro::Mysql::unblock + or die $DBI::errstr; + + Coro::on_enter { $PApp::SQL::DBH = $dbh }; + + $cb->(); + } + +This function makes it possible to easily use L with +L, without worrying about database handles. + + # now start 10 threads doing stuff + async { + + with_db "DBI:mysql:test", "", "", sub { + sql_exec "update table set col = 5 where id = 7"; + + my $st = sql_exec \my ($id, $name), + "select id, name from table where name like ?", + "a%"; + + while ($st->fetch) { + ... + } + + my $id = sql_insertid sql_exec "insert into table values (1,2,3)"; + # etc. + }; + + } for 1..10; + +=head1 SEE ALSO + +L, L (a user friendly but efficient wrapper around DBI). + =head1 AUTHOR Marc Lehmann