GraphQL-Houtou
view release on metacpan or search on metacpan
t/59_on_stall_native_drive.t view on Meta::CPAN
my $err = do {
local $@;
eval {
$runtime->execute_document(
$QUERY,
variables => { id => 'u8', k => 'k8' },
context => { loader => $loader },
on_stall => sub { 0 },
);
};
$@;
};
like $err, qr/GraphQL execution stalled.*on_stall made no progress/s, 'reports the stall';
assert_no_live_frames('after stall detection (DataLoader Ticket pending)');
};
subtest 'multiple loaders via on_stall_for still resolve across rounds' => sub {
my $Post = GraphQL::Houtou::Type::Object->new(
name => 'MultiLoaderPost',
fields => {
title => {
type => $String,
args => { key => { type => $String } },
resolve => sub { my (undef, $a, $ctx) = @_; return $ctx->{titles}->load($a->{key}) },
},
author => {
type => GraphQL::Houtou::Type::Object->new(
name => 'MultiLoaderAuthor',
fields => {
name => {
type => $String,
args => { key => { type => $String } },
resolve => sub { my (undef, $a, $ctx) = @_; return $ctx->{authors}->load($a->{key}) },
},
},
),
resolve => sub { {} },
},
},
);
my $ml_schema = GraphQL::Houtou::Schema->new(
query => GraphQL::Houtou::Type::Object->new(
name => 'Query',
fields => { post => { type => $Post, resolve => sub { {} } } },
),
);
my $ml_runtime = build_native_runtime($ml_schema, async => 1);
my $titles = GraphQL::Houtou::DataLoader->new(batch => sub { return [ map { "title:$_" } @{ $_[0] } ] });
my $authors = GraphQL::Houtou::DataLoader->new(batch => sub { return [ map { "author:$_" } @{ $_[0] } ] });
my $r = $ml_runtime->execute_document(
'query q($tk: String, $ak: String) { post { title(key: $tk) author { name(key: $ak) } } }',
variables => { tk => 't1', ak => 'a1' },
context => { titles => $titles, authors => $authors },
on_stall => GraphQL::Houtou::DataLoader->on_stall_for($titles, $authors),
);
is_deeply $r, { data => { post => { title => 'title:t1', author => { name => 'author:a1' } } } },
'both loaders settle across on_stall_for rounds';
assert_no_live_frames('after multi-loader request');
};
subtest 'repeated nested-suspend requests stay clean over many iterations' => sub {
for my $i (1 .. 200) {
my $loader = GraphQL::Houtou::DataLoader->new(
batch => sub { my ($ids) = @_; return [ map { "v:$_" } @$ids ] },
);
my $r = $runtime->execute_document(
$QUERY,
variables => { id => "u$i", k => "k$i" },
context => { loader => $loader },
on_stall => GraphQL::Houtou::DataLoader->on_stall_for($loader),
);
is_deeply $r, {
data => { user => { name => "n:u$i", team => { name => "v:k$i" } } },
}, "iteration $i resolved" or last;
}
assert_no_live_frames('200 native-drive iterations');
};
# Item 1 (Phase 9): shapes the fast lane does not cover (a runtime directive
# present, or the nesting-depth guard exceeded) fall back to the generic
# executor, which still builds its own Promise::XS internally - but that
# promise is now driven from C too (gql_runtime_vm_drive_promise_with_on_stall_sv),
# not handed back to Perl's _settle_result. These pin the same settle/
# reject/stall/cleanup contract for that fallback path that the subtests
# above already pin for the fast lane.
subtest 'a runtime-directive sibling forces the generic-executor fallback, still driven natively' => sub {
my $loader = GraphQL::Houtou::DataLoader->new(
batch => sub { my ($ids) = @_; return [ map { "v:$_" } @$ids ] },
);
my $directive_schema = GraphQL::Houtou::Schema->new(
query => GraphQL::Houtou::Type::Object->new(
name => 'Query',
fields => {
plain => { type => $String, resolve => sub { 'plain-val' } },
loaded => {
type => $String,
args => { key => { type => $String } },
resolve => sub { my (undef, $a, $ctx) = @_; return $ctx->{loader}->load($a->{key}) },
},
},
),
);
my $directive_runtime = build_native_runtime($directive_schema, async => 1);
my $r = $directive_runtime->execute_document(
'query Q($skipIt: Boolean!, $k: String) { plain @skip(if: $skipIt) loaded(key: $k) }',
variables => { skipIt => 1, k => 'd1' },
context => { loader => $loader },
on_stall => GraphQL::Houtou::DataLoader->on_stall_for($loader),
);
ok !exists($r->{data}{plain}), 'skipped field is absent';
is $r->{data}{loaded}, 'v:d1', 'the pending sibling still resolves via the natively-driven fallback';
assert_no_live_frames('after runtime-directive fallback settles');
};
subtest 'nesting deeper than the recursion guard falls back safely, still driven natively' => sub {
# GQL_VM_FAST_LANE_MAX_NESTING_DEPTH is 16 - one level deeper than that
# forces the generic-executor fallback (see
# t/39_fast_lane_promise_fallback.t's equivalent depth-guard subtest),
# this time with a genuinely pending DataLoader ticket at the bottom so
# the fallback's own Promise::XS response is driven through on_stall.
my $depth = 17;
my $innermost = GraphQL::Houtou::Type::Object->new(
name => 'DeepDriveLevelBottom',
fields => {
v => {
type => $String,
args => { key => { type => $String } },
resolve => sub { my (undef, $a, $ctx) = @_; return $ctx->{loader}->load($a->{key}) },
},
},
);
my @levels = ($innermost);
for (my $lvl = $depth - 1; $lvl >= 0; $lvl--) {
my $child = $levels[0];
unshift @levels, GraphQL::Houtou::Type::Object->new(
name => "DeepDriveLevel$lvl",
( run in 0.593 second using v1.01-cache-2.11-cpan-4ab04211f4c )