DBIx-Loop
view release on metacpan or search on metacpan
include/dbil_pool.h view on Meta::CPAN
else if (n > 0) (void)POPs;
PUTBACK; FREETMPS; LEAVE;
}
for (;;) {
SV *req = dbil_read_frame_blocking(aTHX_ fd);
SV *reqd, *resp, *frozen;
AV *ra;
int is_query;
SV *sql, *err = NULL, *res;
if (!req) break; /* parent closed: exit */
{ STRLEN l; const char *pv = SvPV_const(req, l); reqd = dbil_thaw(aTHX_ pv, l); }
if (!reqd || !SvROK(reqd) || SvTYPE(SvRV(reqd)) != SVt_PVAV) continue;
ra = (AV *)SvRV(reqd);
is_query = (int)SvIV(*av_fetch(ra, 0, 0));
sql = *av_fetch(ra, 1, 0);
{
SV **bp = av_fetch(ra, 2, 0);
AV *bind = (bp && *bp && SvROK(*bp)) ? (AV *)SvRV(*bp) : NULL;
res = dbh ? dbil_run_dbi(aTHX_ dbh, is_query, sql, bind, &err)
: (err = sv_2mortal(newSVpvs("worker has no database handle")), NULL);
}
{
AV *out = newAV();
av_push(out, newSViv(res ? 1 : 0));
av_push(out, res ? newSVsv(res)
: newSVsv(err ? err : sv_2mortal(newSVpvs("failed"))));
resp = sv_2mortal(newRV_noinc((SV *)out));
}
frozen = dbil_freeze(aTHX_ resp);
if (frozen) (void)dbil_write_frame(aTHX_ fd, frozen);
}
close(fd);
_exit(0);
}
/* send request bytes to worker wi and mark it busy with `future` (+1 taken) */
/* Can anything still take general (non-transaction) work? */
static int dbil_pool_has_live_worker(const dbil_pool *p) {
int wi;
for (wi = 0; wi < p->nw; wi++)
if (p->w[wi].fd >= 0 && !p->w[wi].reserved) return 1;
return 0;
}
static void dbil_pool_send(pTHX_ dbil_pool *p, int wi, SV *bytes, SV *future) {
dbil_worker *w = &p->w[wi];
int reserved;
w->busy = 1;
w->future = SvREFCNT_inc(future);
if (dbil_write_frame(aTHX_ w->fd, bytes) >= 0) return;
/* The write failed, so the frame did not arrive whole and the statement
* provably never ran: dbil_write_frame only returns < 0 having written
* less than all of it.
*
* What matters here is the SLOT, not this one request. A worker killed
* while idle is invisible until something is written to it - death is
* otherwise only ever noticed as EOF on the read side, and an idle
* worker's fd is not readable - so this is the first and only moment we
* learn about it. Leaving the slot alive meant dbil_pool_run kept
* choosing it, being the lowest-numbered idle worker with a live fd, and
* failed every future from then on while the healthy workers sat idle.
* Treat a failed write as the death notice it is. */
reserved = w->reserved;
if (!reserved) {
/* Nothing executed, so this is a retry and not a loss. Put it back at
* the head of the queue so it keeps its place, and let the respawn
* inside worker_died pick it up. */
SvREFCNT_dec(w->future);
w->future = NULL;
w->busy = 0;
av_unshift(p->queue, 2);
av_store(p->queue, 0, newSVsv(bytes));
av_store(p->queue, 1, SvREFCNT_inc(future));
}
/* A reserved slot is a transaction pinned to that one connection. It
* cannot be moved, so leave the future in place for worker_died to fail
* alongside the rest of the transaction. */
dbil_pool_worker_died(aTHX_ p, wi);
/* Respawn is capped and can fail. If it did, and nothing is left to run
* on, fail what we just queued rather than leave callers waiting on a
* pool that can no longer answer. */
if (!reserved && !dbil_pool_has_live_worker(p)) {
while (av_len(p->queue) >= 1) {
SV *qb = av_shift(p->queue);
SV *qf = av_shift(p->queue);
SvREFCNT_dec(qb);
dbil_future_settle_fail(aTHX_ qf,
sv_2mortal(newSVpvs("pool has no live workers")));
SvREFCNT_dec(qf);
}
}
}
/* run one statement on the pool; returns the future (+1) */
static SV *dbil_pool_run(pTHX_ dbil_pool *p, int is_query, SV *sql, AV *bind) {
SV *future = dbil_future_new(aTHX_ "DBIx::Loop::Future");
SV *bytes;
int wi;
{ /* freeze the request tuple */
AV *req = newAV();
av_push(req, newSViv(is_query));
av_push(req, newSVsv(sql));
av_push(req, dbil_bind_rv(aTHX_ bind));
bytes = dbil_freeze(aTHX_ sv_2mortal(newRV_noinc((SV *)req)));
}
if (!bytes) {
dbil_future_settle_fail(aTHX_ future,
sv_2mortal(newSVpvs("could not serialise request")));
return future;
}
for (wi = 0; wi < p->nw; wi++)
if (!p->w[wi].busy && !p->w[wi].reserved && p->w[wi].fd >= 0)
{ dbil_pool_send(aTHX_ p, wi, bytes, future); return future; }
/* all busy: queue (bytes, future) - unless the backpressure cap is hit */
if (p->max_queue > 0 && (av_len(p->queue) + 1) / 2 >= p->max_queue) {
dbil_future_settle_fail(aTHX_ future,
( run in 1.145 second using v1.01-cache-2.11-cpan-14f38c9f855 )