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 )