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_failmay 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.