Punk-Queue

 view release on metacpan or  search on metacpan

include/pq_repair.h  view on Meta::CPAN

    double remove_after  = pq_opt_num(aTHX_ self, "remove_after", 172800.0);
    IV n;

    /* 1: workers that stopped heartbeating are unregistered. */
    {
        AV *b = pq_binds(aTHX);
        pq_bind_nv(aTHX_ b, now - missing_after);
        n = pq_do(aTHX_ self, sv_2mortal(newSVpvs(
            "DELETE FROM pq_workers WHERE notified < ?")), b);
        (void)hv_stores(out, "stale_workers", newSViv(n));
    }

    /* 2: active jobs whose worker row is gone go back to inactive with
     * the retries bump - the bump is the point: it invalidates whatever
     * the vanished (or zombie) worker might still report through the
     * optimistic guard. This is the pass that picks up children killed
     * mid-job by the supervisor's hard timeout. */
    {
        AV *b = pq_binds(aTHX);
        pq_bind_nv(aTHX_ b, now);
        n = pq_do(aTHX_ self, sv_2mortal(newSVpvs(
            "UPDATE pq_jobs SET state = 'inactive',"
            " retries = retries + 1, retried = ?, started = NULL,"
            " worker = NULL WHERE state = 'active' AND (worker IS NULL"
            " OR worker NOT IN (SELECT id FROM pq_workers))")), b);
        (void)hv_stores(out, "orphaned_jobs", newSViv(n));
    }

    /* 3: expired inactive jobs are deleted. The claim predicate already
     * refuses them (correctness is immediate); this keeps the table
     * small. Children adjust exactly as remove_job's inactive case. */
    {
        SV *sth;
        AV *b = pq_binds(aTHX), *row;
        AV *victims = (AV *)sv_2mortal((SV *)newAV());
        SSize_t i, nv;

        pq_bind_nv(aTHX_ b, now);
        sth = pq_sth(aTHX_ self, sv_2mortal(newSVpvs(
            "SELECT id FROM pq_jobs WHERE state = 'inactive'"
            " AND expires IS NOT NULL AND expires <= ?")));
        (void)pq_execute(aTHX_ sth, b);
        while ((row = pq_fetchrow(aTHX_ sth))) {
            av_push(victims, newSViv(pq_col_iv(aTHX_ row, 0)));
            SvREFCNT_dec((SV *)row);
        }
        { SV *r = pq_call_meth(aTHX_ sth, "finish", NULL, 0, 1);
          if (r) SvREFCNT_dec(r); }

        nv = av_len(victims) + 1;
        for (i = 0; i < nv; i++)
            (void)pq_remove_job(aTHX_ self,
                                SvIV(*av_fetch(victims, i, 0)));
        (void)hv_stores(out, "expired_jobs", newSViv((IV)nv));
    }

    /* 4: ancient terminal jobs are history, not queue. Straight DELETE -
     * a finished parent's children got their decrements long ago, and
     * failed parents this old have failed children right next to them
     * (pass 5 already ran on them in an earlier repair). Dep rows
     * cascade. */
    {
        AV *b = pq_binds(aTHX);
        pq_bind_nv(aTHX_ b, now - remove_after);
        (void)pq_do(aTHX_ self, sv_2mortal(newSVpvs(
            "DELETE FROM pq_job_logs WHERE job_id IN"
            " (SELECT id FROM pq_jobs"
            "   WHERE state IN ('finished', 'failed') AND finished < ?)")),
            b);
        b = pq_binds(aTHX);
        pq_bind_nv(aTHX_ b, now - remove_after);
        n = pq_do(aTHX_ self, sv_2mortal(newSVpvs(
            "DELETE FROM pq_jobs"
            " WHERE state IN ('finished', 'failed') AND finished < ?")), b);
        (void)hv_stores(out, "ancient_jobs", newSViv(n));
    }

    /* 5: a non-lax child of a terminally failed parent will never run;
     * hanging silently is the one unforgivable outcome, so it fails with
     * a reason. (Terminally failed = state 'failed'; a backoff retry sits
     * in 'inactive' and does not strand anyone.) Each newly failed child
     * is itself a terminally failed parent - its lax children unblock via
     * the same pq_children_adjust every other transition uses - so the
     * pass iterates until a whole chain has settled. */
    {
        IV total = 0;
        for (;;) {
            SV *sth;
            AV *row, *victims = (AV *)sv_2mortal((SV *)newAV());
            SSize_t i, nv;

            sth = pq_sth(aTHX_ self, sv_2mortal(newSVpvs(
                "SELECT id FROM pq_jobs WHERE state = 'inactive'"
                " AND lax = 0 AND id IN (SELECT d.job_id"
                " FROM pq_job_deps d JOIN pq_jobs p ON p.id = d.parent_id"
                " WHERE p.state = 'failed')")));
            (void)pq_execute(aTHX_ sth, NULL);
            while ((row = pq_fetchrow(aTHX_ sth))) {
                av_push(victims, newSViv(pq_col_iv(aTHX_ row, 0)));
                SvREFCNT_dec((SV *)row);
            }
            { SV *r = pq_call_meth(aTHX_ sth, "finish", NULL, 0, 1);
              if (r) SvREFCNT_dec(r); }

            nv = av_len(victims) + 1;
            if (nv == 0) break;

            for (i = 0; i < nv; i++) {
                IV cid = SvIV(*av_fetch(victims, i, 0));
                AV *b = pq_binds(aTHX);
                IV took;
                pq_txn_begin(aTHX_ self);
                pq_bind_nv(aTHX_ b, now);
                pq_bind_iv(aTHX_ b, cid);
                took = pq_do(aTHX_ self, sv_2mortal(newSVpvs(
                    "UPDATE pq_jobs SET state = 'failed', finished = ?,"
                    " result = '\"Punk::Queue: a parent this job depends"
                    " on has failed\"'"
                    " WHERE id = ? AND state = 'inactive'")), b);
                if (took > 0) {
                    pq_children_adjust(aTHX_ self, cid, -1, PQ_KIDS_LAX);
                    total++;
                }
                pq_txn_commit(aTHX_ self);
            }
        }
        (void)hv_stores(out, "stranded_children", newSViv(total));
    }

    /* 6: stale locks. Reads honour expiry everywhere already; this is
     * the table-size half. */
    {
        AV *b = pq_binds(aTHX);
        pq_bind_nv(aTHX_ b, now);
        n = pq_do(aTHX_ self, sv_2mortal(newSVpvs(
            "DELETE FROM pq_locks WHERE expires <= ?")), b);
        (void)hv_stores(out, "stale_locks", newSViv(n));
    }

    /* 7: orphaned log lines. Every in-tree delete path cascades, so a
     * hit here means something ELSE deleted job rows (manual SQL, an
     * older binary) - which is exactly the drift repair exists to mop
     * up. */
    {
        n = pq_do(aTHX_ self, sv_2mortal(newSVpvs(
            "DELETE FROM pq_job_logs WHERE job_id NOT IN"
            " (SELECT id FROM pq_jobs)")), NULL);
        (void)hv_stores(out, "orphaned_logs", newSViv(n));
    }

    /* 7 (--deep): recompute every parents_left from the deps table - the
     * correctness backstop for the whole counter design. Off by default
     * because it is a full scan. */
    if (deep) {
        n = pq_do_pv(aTHX_ self,
            "UPDATE pq_jobs SET parents_left ="
            " (SELECT count(*) FROM pq_job_deps d"
            "   JOIN pq_jobs p ON p.id = d.parent_id"
            "   WHERE d.job_id = pq_jobs.id AND CASE WHEN pq_jobs.lax = 1"
            "   THEN p.state NOT IN ('finished', 'failed')"
            "   ELSE p.state <> 'finished' END)"
            " WHERE state = 'inactive' AND parents_left <>"
            " (SELECT count(*) FROM pq_job_deps d"
            "   JOIN pq_jobs p ON p.id = d.parent_id"
            "   WHERE d.job_id = pq_jobs.id AND CASE WHEN pq_jobs.lax = 1"
            "   THEN p.state NOT IN ('finished', 'failed')"
            "   ELSE p.state <> 'finished' END)");
        (void)hv_stores(out, "recomputed_counters", newSViv(n));
    }

    return newRV_noinc((SV *)out);
}

#endif /* PQ_REPAIR_H */



( run in 0.659 second using v1.01-cache-2.11-cpan-e7c6538aa59 )