NAME

GraphQL::Houtou::Async::Adapter - describe an async backend for Houtou's XS VM

SYNOPSIS

In an adapter distribution:

package GraphQL::Houtou::Async::Adapter::MyPromise;

use parent 'GraphQL::Houtou::Async::Adapter';

my $ADAPTER;

sub adapter {
  return $ADAPTER ||= __PACKAGE__->register(
    name        => 'my_promise',
    class       => 'My::Promise',
    new_pending => \&new_pending,
    all         => \&all,
    then        => \&then,
  );
}

In an application:

use GraphQL::Houtou::Async::Adapter::MyPromise;

my $adapter = GraphQL::Houtou::Async::Adapter::MyPromise->adapter;
my $runtime = $schema->build_native_runtime(async_adapter => $adapter);

DESCRIPTION

This module is the public adapter boundary between the Houtou native VM and promise implementations. Houtou only bundles its Promise::XS fast path. Adapters for other implementations should be released as independent distributions.

Each native runtime owns its adapter callbacks. Adapter objects may be reused across runtimes without process-global registration or adapter limits.

ADAPTER CONTRACT

name

An optional identifier for documentation and diagnostics.

class

The promise class returned by resolvers and adapter callbacks. Subclasses are also recognized.

new_pending

A coderef taking no arguments and returning [ $promise, $resolve ], or [ $promise, $resolve, $reject ] when the adapter is also used by DataLoader. The latter callbacks settle $promise; the VM only needs $resolve.

all

A coderef receiving one array reference. It must accept plain values and backend promises and return a backend promise that resolves to one array reference in the original order.

then

A required coderef called as ($promise, $on_done, $on_fail), where $on_fail may be omitted.

Some promise implementations require callbacks to return another promise. In that case the adapter must wrap plain values returned by Houtou's callbacks. For example, a Future-style adapter needs the equivalent of:

then => sub {
  my ($future, $done, $fail) = @_;
  my @callbacks = (sub { Future->done($done->(@_)) });
  push @callbacks, sub { Future->done($fail->(@_)) } if $fail;
  my $next = $future->then(@callbacks);
  my $keep = $next;
  $future->on_ready(sub { undef $keep }) if !$next->is_ready;
  return $next;
}

WRITING AN ADAPTER IN PERL

For a promise whose then method accepts ordinary callback return values, the adapter can delegate directly to that method:

my $adapter = GraphQL::Houtou::Async::Adapter->register(
  name  => 'promise_es6',
  class => 'Promise::ES6',
  new_pending => sub {
    my ($resolve, $reject);
    my $promise = Promise::ES6->new(sub {
      ($resolve, $reject) = @_;
    });
    return [ $promise, $resolve, $reject ];
  },
  all => sub {
    return Promise::ES6->all($_[0]);
  },
  then => sub {
    my ($promise, @callbacks) = @_;
    return $promise->then(@callbacks);
  },
);

The adapter object should be cached by the adapter module and passed to build_native_runtime through async_adapter. Only 'Promise::XS' has a built-in string form.

WRITING AN ADAPTER IN XS

All three callbacks may be XSUB coderefs. A small Perl bootstrap can therefore register native functions supplied by an external XS distribution:

package GraphQL::Houtou::Async::Adapter::NativePromise;

use XSLoader;
use GraphQL::Houtou::Async::Adapter;

XSLoader::load(__PACKAGE__, our $VERSION);

my $ADAPTER = GraphQL::Houtou::Async::Adapter->register(
  name        => 'native_promise',
  class       => 'Native::Promise',
  new_pending => \&new_pending_xs,
  all         => \&all_xs,
  then        => \&then_xs,
);

sub adapter { $ADAPTER }

The XSUB signatures follow the same contract:

SV * new_pending_xs() /* returns [promise, resolve, reject] */
SV * all_xs(values)
    SV *values
SV * then_xs(promise, on_done, on_fail = &PL_sv_undef)
    SV *promise
    SV *on_done
    SV *on_fail

An XS adapter may call the promise implementation's public C API directly. For example, Future::XS exposes future.h. A Perl adapter for that backend should load Future::XS but use the public Future class, which selects the XS implementation while retaining Future's compatibility methods. Merely moving Perl method calls into an XSUB does not remove their cost; use the backend C API where the benchmark justifies the extra code.

PERFORMANCE

Adapter ownership and dispatch live in XS. The callbacks may themselves be XSUBs, so an XS-backed implementation does not need a Perl callback body. The bundled Promise::XS backend still has a dedicated VM hot path and is the baseline for adapter benchmarks.