Group
Extension

Punk-Queue/include/pq_claim.h

#ifndef PQ_CLAIM_H
#define PQ_CLAIM_H

/* pq_claim.h - the parts of a claim that are the same on every backend.
 *
 * The two backends claim a job very differently - PostgreSQL does it in one
 * statement with FOR UPDATE SKIP LOCKED and RETURNING, SQLite does
 * select-then-guarded-update inside BEGIN IMMEDIATE - but the *predicate*
 * that decides which job is next, the order the binds go in, and what a
 * claimed row turns into are identical, and must stay identical. A queue
 * whose claim order differs between backends is a queue whose behaviour
 * depends on its deployment.
 *
 * So the divergent files own the transaction shape and the statement
 * skeleton, and this file owns everything inside them. If a difference in
 * claim semantics ever appears between pq_sqlite.h and pq_pg.h, it is a bug
 * in one of them, not a fact about the database.
 *
 * Include after pq_job.h and pq_sql.h, before the per-backend headers. */

/* The shared claim predicate, appended to a statement that has already
 * opened its WHERE with `state = 'inactive' AND `.
 *
 * Order matters for prepare_cached: the text must be byte-identical for a
 * given (nq, nt), which it is, because nothing here depends on the values. */
static void pq_claim_where(pTHX_ SV *sql, SSize_t nq, SSize_t nt) {
    pq_sql_in(aTHX_ sql, "queue", nq);
    if (nt > 0) {
        pq_sql_cat(aTHX_ sql, " AND ");
        pq_sql_in(aTHX_ sql, "task", nt);
    }
    pq_sql_cat(aTHX_ sql,
        " AND delayed <= ? AND parents_left = 0"
        " AND (expires IS NULL OR expires > ?)");
}

/* The ordering. Its own function because "highest priority, then oldest"
 * is a promise to the user, not an implementation detail, and it appears in
 * two statements. `id` is the tiebreaker: it gives FIFO within a priority
 * for free, with no second timestamp column to read. */
static void pq_claim_order(pTHX_ SV *sql) {
    pq_sql_cat(aTHX_ sql, " ORDER BY priority DESC, id LIMIT 1");
}

/* Binds for pq_claim_where, in its placeholder order. */
static void pq_claim_binds(pTHX_ AV *bind, AV *queues, AV *tasks, double now) {
    SSize_t nq = queues ? av_len(queues) + 1 : 0;
    SSize_t nt = tasks  ? av_len(tasks)  + 1 : 0;
    SSize_t i;
    for (i = 0; i < nq; i++) pq_bind_sv(aTHX_ bind, *av_fetch(queues, i, 0));
    for (i = 0; i < nt; i++) pq_bind_sv(aTHX_ bind, *av_fetch(tasks,  i, 0));
    pq_bind_nv(aTHX_ bind, now);      /* delayed <= ? */
    pq_bind_nv(aTHX_ bind, now);      /* expires  > ? */
}

/* A claimed row -> the job hashref the caller gets (+1).
 *
 * The in-memory row is patched to reflect the claim rather than re-read.
 * PostgreSQL's RETURNING gives the post-update row already, SQLite's SELECT
 * gives the pre-update one, and normalising here is what stops that
 * difference reaching the caller. */
static SV *pq_claim_row(pTHX_ AV *row, IV worker_id, double now) {
    HV *h = pq_job_row_hv(aTHX_ row);
    (void)hv_stores(h, "state",   newSVpvs("active"));
    (void)hv_stores(h, "started", newSVnv(now));
    (void)hv_stores(h, "worker",  worker_id ? newSViv(worker_id) : newSV(0));
    return newRV_noinc((SV *)h);
}

/* Both backends need at least one queue to look in, and "none" is a caller
 * bug rather than an empty result - a worker watching no queues would spin
 * forever finding nothing. */
static void pq_claim_check(pTHX_ AV *queues) {
    if (!queues || av_len(queues) < 0)
        croak("Punk::Queue: dequeue needs at least one queue");
}

#endif /* PQ_CLAIM_H */


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.