Changes: fetchavail_arrayref is retracted from the proposal.

On Thu, 2004-09-02 at 18:24, Jonathan Leffler wrote:
> On Thu, 2 Sep 2004 23:31:22 +0100, Tim Bunce <[EMAIL PROTECTED]> wrote:
> > On Thu, Sep 02, 2004 at 01:52:59PM +0100, Matt Sergeant wrote:
> > > On 1 Sep 2004, at 03:13, david nicol wrote:

> > > > summary: define an optional, standard non-blocking interface
> > > > by adding ready(), fileno(), and fetchavail() methods.
> > > > *fileno* would return:
> > > >An array ref rather than one number, so handles that have multiple
> > > >connections open can report them all.

> > > Yes, this would work. Having each DBD implement their own internal
> > > select() wouldn't work as well, because two event loops are worse than
> > > one.

calling select() does not imply you have your own event loop.  Select
can be called with a timeout of zero, in which case it does not block,
and can be used within another event loop. We can strongly reccommend
that the timeout attribute should be set to 0 to have non-blocking
polling.

> > I haven't had a chance to read through all the comments in this thread yet,
> > but I'll point out two flies in the ointment:
> > 
> > 1. Many database API's don't offer access to the filehandle.

> And some API's don't even use files for communicating with the database server.

returning the file descriptor number of the session filehandle of course
only makes sense when it makes sense.  In a threaded environment, or
even in a fork-friendly environment, setting up a C<pipe> just to
provide a fd that the fully asynchronous driver can write a trigger byte
to when something becomes ready is certainly possible. 

> Informix can use shared memory connections -- and uses semaphores for
> synchronization.

I worry that
I'm getting repetitive here and might do better to sit back and watch
for a while rather than keeping up with this thread.  I will try to
have this one be the last repetition of the proposal for a while
except for altering it in response to constructive criticisms.

The implementation is the driver implementor's problem. An optional
extension to the DBI interface standard needs to

  * not break anything  ("first, do no harm")

  * be general enough to apply everywhere

  * be trivial to map current NB implementations to

  * play nice with external event loops using select() 

  * ease creating a DBD::NonBlockingWrapper for using the nonblocking
    interface against a back-end that does not support it

Currently, DBI applications can block for all steps of the DBI
interaction.  In a nonblocking DBD, this behavior can be imitated
by not defining a Timeout attribute, or defining a Timeout of C<undef>.
This mode is called "Synchronous" and is the only mode available with
current DBI.

The two asynchronous modes are called "deferred asynchronous" and 
"fully asynchronous."  The Sybase documentation referred to earlier in
this thread defines them well.  The interface I am proposing works the
same, to the DBI coder, in both modes.

The current DBI fetch methods will continue to block, unless this
behavior is overruled by an extension to this extension, which would
redefine the current fetch methods to return an error.  I think 
redefining fetch to fail EWOULDBLOCK is a bad idea because programming
around the lack of non-blocking sockets on platforms that do not support
them is complex.  So, what is needed is a way to know if a fetch() will
block or not, before we call fetch.  My proposal for a method which
answers that question is $sth->ready().

In a synchronous environment, calling ready() is an error, providing
instant legacy support: legacy (synchronous) drivers will throw a "Can't
locate object method "ready" via package ..." error which can be trapped
with block eval.

In a deferred asynchronous environment, calling ready() gives the driver
a chance to non-blockingly poll for data and see if there is enough data
that the next expected call (fetch, or perhaps execute) will not block.

In a fully asynchronous environment, the driver thread (or process) 
sets a flag somewhere the ready() method can check.

To support an external event loop, the fileno() method is introduced.
An FD returned by fileno() becoming readable DOES NOT imply that the
next expected call will not block. It merely indicates that the driver
would appreciate a time-slice, which can be given to it by calling
ready().  

Having a file descriptor actually involved in the back-end
implementation is not needed:  Here is a working fileno() method that
can be switched on and off:

   {
      my ($RH, $WH); # make these per-object for per-object granularity
     INIT { 
        pipe $RH, $WH;
        my $oldfh = select($WH); $| = 1; select($oldfh)
      }
      sub fileno{ [fileno $RH]}; 
      sub _fileno_inc(){ print $WH, 'a' };
      sub _fileno_dec(){ my $dummy; sysread $RH, $dummy,1 };
   }

For per-object granularity, the INIT code would go in the constructor
and the fileno method would do something like [fileno shift()->{RH}].

To get the external event loop to poll our driver, call _fileno_inc(),
and when the statement handles goes inactive, call _fileno_dec().

Hey! brainstorm! The fetchavail method is not needed if ready() is
defined to return the number of rows currently available, which can be
provided to fetchall_arrayref as the $max_rows parameter.

That reduces the proposal to:

    * new attribute Timeout
    * new method ready
    * new method fileno

And when you don't mind blocking, like between prepare() and execute(),
just skip waiting for ready to turn true.

Yes, writing a compliant non-blocking driver will be complex. I imagine
that non-blocking drivers will, at least initially, exist separately
 from blocking drivers, perhaps with a _nb appended to their names:

    $dbh = DBI->connect("dbi:Oracle_nb:host=myhost.com;sid=ORCL",
                         $user, $passwd, {RaiseError => 1} );

However, writing a fully asynchronous wrapper for any current driver
is tractable. If my proposal is approved, I'll even write one. It will
fork on connect() and use pipes for IPC, providing plenty of room for
improvement. Or maybe IPC::ShareLite. DBD::Async will work like so:

    $dbh = DBI->connect("dbi:Async:Oracle:host=myhost.com;sid=ORCL",
                        $user, $passwd, {RaiseError => 1, Timeout=>0});

and will implement ready() and fileno() in addition to everything
else mandated in the DBI specification.

Reply via email to