GraphQL-Houtou
view release on metacpan or search on metacpan
t/54_frame_leak_regression.t view on Meta::CPAN
);
my $r = $runtime->execute_document(
'query q($id: String, $k: String) { user(id: $id) { name team { name(key: $k) } } }',
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 single root object field promotions');
};
subtest 'repeated args-bearing resolver calls exercise the LazyInfo pool cleanly' => sub {
# Phase 10: gql_runtime_vm_lazy_info_t is pooled instead of Newxz/Safefree
# per call. Every field here declares args, so every call goes through
# the cb5 ABI and builds/tears down a pooled LazyInfo - some resolvers die,
# to also exercise the pool along the field-error path.
my $Item = GraphQL::Houtou::Type::Object->new(
name => 'LazyInfoPoolItem',
fields => {
label => {
type => $String,
args => { prefix => { type => $String } },
resolve => sub {
my (undef, $args, undef, $info) = @_;
die "boom for $info->{field_name}\n" if $args->{prefix} eq 'boom';
return "$args->{prefix}:$info->{path}[-1]";
},
},
},
);
my $pool_schema = GraphQL::Houtou::Schema->new(
query => GraphQL::Houtou::Type::Object->new(
name => 'Query',
fields => {
items => {
type => GraphQL::Houtou::Type::List->new(of => $Item),
args => { ids => { type => GraphQL::Houtou::Type::List->new(of => $String) } },
resolve => sub {
my (undef, $args) = @_;
return [ map { { id => $_ } } @{ $args->{ids} } ];
},
},
},
),
);
for my $i (1 .. 200) {
my $prefix = $i % 5 == 0 ? 'boom' : "p$i";
my $r = $pool_schema->execute(
'query Q($ids: [String], $p: String) { items(ids: $ids) { label(prefix: $p) } }',
variables => { ids => [ map { "id$_" } 1 .. 10 ], p => $prefix },
);
if ($prefix eq 'boom') {
ok $r->{errors} && @{ $r->{errors} } == 10, "iteration $i: every item errors" or last;
} else {
is scalar(@{ $r->{data}{items} }), 10, "iteration $i: every item resolved" or last;
}
}
assert_no_live_frames('200 args-bearing resolver iterations (LazyInfo pool)');
};
subtest 'cancelling a request with a suspended list-item child block stays clean' => sub {
# Phase 11: a list item's own child block now suspends via a raw
# block_frame_t linked directly into list_pending (gql_runtime_vm_
# list_pending_link_child_frame), not a Promise::XS. Abandoning the
# request while that item is still genuinely pending - on_stall dies,
# or a stall is detected - must reach and release that linked child
# frame via gql_runtime_vm_cancel_frame_tree's new LIST_PENDING_PTR
# recursion, or it (and the list_pending itself) leaks.
my $Row = GraphQL::Houtou::Type::Object->new(
name => 'CancelRow',
fields => {
name => { type => $String, resolve => sub { "n:$_[0]{id}" } },
pending => {
type => $String,
args => { key => { type => $String } },
resolve => sub {
my (undef, $args, $ctx) = @_;
return $ctx->{loader}->load($args->{key});
},
},
},
);
my $cancel_schema = GraphQL::Houtou::Schema->new(
query => GraphQL::Houtou::Type::Object->new(
name => 'Query',
fields => {
rows => {
type => GraphQL::Houtou::Type::List->new(of => $Row),
args => { ids => { type => GraphQL::Houtou::Type::List->new(of => $String) } },
resolve => sub {
my (undef, $args) = @_;
return [ map { { id => $_ } } @{ $args->{ids} } ];
},
},
},
),
);
my $runtime = build_native_runtime($cancel_schema, async => 1);
my $loader1 = GraphQL::Houtou::DataLoader->new(
batch => sub { my ($ids) = @_; return [ map { "v:$_" } @$ids ] },
);
my $err1 = do {
local $@;
eval {
$runtime->execute_document(
'query Q($ids: [String], $k: String) { rows(ids: $ids) { name pending(key: $k) } }',
variables => { ids => [ 'a1', 'a2' ], k => 'k1' },
context => { loader => $loader1 },
on_stall => sub { die "on_stall boom\n" },
);
};
$@;
};
like $err1, qr/on_stall boom/, 'the on_stall die propagates';
assert_no_live_frames('after on_stall die with a suspended list item');
my $loader2 = GraphQL::Houtou::DataLoader->new(
( run in 0.420 second using v1.01-cache-2.11-cpan-4ab04211f4c )