GraphQL-Houtou

 view release on metacpan or  search on metacpan

lib/GraphQL/Houtou.xs  view on Meta::CPAN

  IV entry_index,
  SV **resolve_rv_out,
  SV **reject_rv_out
)
{
  gql_runtime_vm_pending_callback_ctx_t *ctx;

  if (gql_runtime_vm_pending_cb_pool_count > 0) {
    IV n = --gql_runtime_vm_pending_cb_pool_count;
    SV *resolve_cv = gql_runtime_vm_pending_cb_pool_resolve[n];
    SV *reject_cv = gql_runtime_vm_pending_cb_pool_reject[n];

    gql_runtime_vm_pending_cb_pool_resolve[n] = NULL;
    gql_runtime_vm_pending_cb_pool_reject[n] = NULL;
    ctx = INT2PTR(
      gql_runtime_vm_pending_callback_ctx_t *,
      CvXSUBANY((CV *)resolve_cv).any_ptr
    );
    ctx->state_sv = state_sv ? SvREFCNT_inc_simple_NN(state_sv) : NULL;
    ctx->frame = frame;
    if (ctx->frame) {
      ctx->frame->refcount++;
    }
    ctx->entry_index = entry_index;
    /* The pool's CV refcounts transfer into the returned RVs. */
    *resolve_rv_out = newRV_noinc(resolve_cv);
    *reject_rv_out = newRV_noinc(reject_cv);
    return ctx;
  }

  {
    CV *resolve_cv;
    CV *reject_cv;

    Newxz(ctx, 1, gql_runtime_vm_pending_callback_ctx_t);
    ctx->state_sv = state_sv ? SvREFCNT_inc_simple_NN(state_sv) : NULL;
    ctx->frame = frame;
    if (ctx->frame) {
      ctx->frame->refcount++;
    }
    ctx->entry_index = entry_index;
    ctx->cv_refcnt = 2;

    resolve_cv = newXS(NULL, gql_runtime_vm_xs_pending_callback, __FILE__);
    CvXSUBANY(resolve_cv).any_ptr = ctx;
    gql_runtime_vm_attach_callback_magic_ptr(aTHX_ (SV *)resolve_cv, &gql_runtime_vm_pending_callback_ctx_vtbl, ctx);
    reject_cv = newXS(NULL, gql_runtime_vm_xs_pending_reject_callback, __FILE__);
    CvXSUBANY(reject_cv).any_ptr = ctx;
    gql_runtime_vm_attach_callback_magic_ptr(aTHX_ (SV *)reject_cv, &gql_runtime_vm_pending_callback_ctx_vtbl, ctx);
    ctx->resolve_cv = (SV *)resolve_cv;
    ctx->reject_cv = (SV *)reject_cv;

    *resolve_rv_out = newRV_noinc((SV *)resolve_cv);
    *reject_rv_out = newRV_noinc((SV *)reject_cv);
    return ctx;
  }
}

/* One arm fired: the promise has settled, so the other arm can never
 * fire. Drop the ctx's references now (parked callbacks must not keep the
 * exec state alive) and park the pair for the next arm to reuse. */
static void
gql_runtime_vm_pending_callback_pair_recycle(
  pTHX_
  gql_runtime_vm_pending_callback_ctx_t *ctx
)
{
  if (!ctx) {
    return;
  }
  if (ctx->state_sv) {
    SvREFCNT_dec(ctx->state_sv);
    ctx->state_sv = NULL;
  }
  if (ctx->frame) {
    gql_runtime_vm_free_block_frame(aTHX_ ctx->frame);
    ctx->frame = NULL;
  }
  ctx->entry_index = -1;
  if (ctx->resolve_cv && ctx->reject_cv && ctx->cv_refcnt == 2
      && gql_runtime_vm_pending_cb_pool_count < GQL_VM_PENDING_CB_POOL_MAX) {
    gql_runtime_vm_pending_cb_pool_resolve[gql_runtime_vm_pending_cb_pool_count] =
      SvREFCNT_inc_simple_NN(ctx->resolve_cv);
    gql_runtime_vm_pending_cb_pool_reject[gql_runtime_vm_pending_cb_pool_count] =
      SvREFCNT_inc_simple_NN(ctx->reject_cv);
    gql_runtime_vm_pending_cb_pool_count++;
  }
}

/* Cancellation counterpart to gql_runtime_vm_pending_callback_pair_recycle:
 * an armed entry whose promise/ticket never settled (the request was
 * abandoned - on_stall died, or a stall was detected) still needs its
 * ctx's state_sv/frame references dropped, exactly like a normal settle
 * would, or they leak (a still-pending DataLoader Ticket's own subscriber
 * list - populated by gql_runtime_vm_subscribe_dataloader_ticket - holds
 * this ctx's resolve/reject CVs alive independent of anything the exec
 * state owns, since the ticket can outlive the request via the
 * DataLoader's own queue). Unlike recycle, this does NOT pool the CV
 * pair: the ticket/promise might still fire it later (a buggy caller
 * holding the loader past abandonment), and handing that live CV to an
 * unrelated future request would corrupt it. Left disarmed, the CV pair
 * is permanently inert instead - gql_runtime_vm_xs_pending_callback and
 * its reject counterpart already no-op once ctx->state_sv/ctx->frame are
 * NULL - trading the leak for a single bounded, harmless CV+ctx. */
static void
gql_runtime_vm_pending_callback_ctx_disarm(
  pTHX_
  gql_runtime_vm_pending_callback_ctx_t *ctx
)
{
  if (!ctx) {
    return;
  }
  if (ctx->state_sv) {
    SvREFCNT_dec(ctx->state_sv);
    ctx->state_sv = NULL;
  }
  if (ctx->frame) {
    gql_runtime_vm_free_block_frame(aTHX_ ctx->frame);
    ctx->frame = NULL;
  }
  ctx->entry_index = -1;
}

/* Shared by gql_runtime_vm_xs_list_pending_callback (a settled item that
 * arrived as a Promise::XS/Ticket) and
 * gql_runtime_vm_async_scheduler_resolve_frame's parent_list_pending
 * branch below (a settled item that was a raw linked child frame):
 * store the item's final native value and decrement unresolved_count.
 * Does not enqueue/drain owner_frame - callers differ on whether that's
 * safe/needed right here (a Promise::XS callback fires from outside any
 * active drain and must kick one off itself if owner_frame is now ready;
 * resolve_frame's branch runs from inside an active drain already and
 * only needs to enqueue, mirroring the existing parent_frame branch). */
static void
gql_runtime_vm_list_pending_store_item_value(
  pTHX_
  gql_runtime_vm_list_pending_t *pending,
  IV index,
  gql_runtime_vm_native_value_t *item_value
)
{
  gql_runtime_vm_native_list_store_at(aTHX_ pending->values_value, index, item_value);
  if (pending->unresolved_count > 0) {
    pending->unresolved_count--;
  }
}

static void
gql_runtime_vm_async_scheduler_resolve_frame(
  pTHX_
  gql_runtime_vm_exec_state_handle_t *s,
  gql_runtime_vm_block_frame_t *frame
)
{
  SV *data_sv = NULL;



( run in 2.144 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )