Fix false error: detect completed pipeline at 90s instead of 300s

The status endpoint waited 300 seconds before checking if the pipeline
had produced output. On Hostinger, PHP's max_execution_time kills the
cron process after ~120s. The pipeline publishes the post before images,
so the post exists but the transient never transitions to 'complete'.
The user sees "Generating…" for 5 minutes, then gives up.

Now the status endpoint proactively checks for recently-created posts
after just 90 seconds. If found, it reports success immediately. The
300-second hard-fail threshold is still there for genuinely dead runs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
peptiderepo
2026-04-18 16:03:01 +07:00
parent 214f57bff2
commit 650d8df325

View File

@@ -2,16 +2,14 @@
declare(strict_types=1); declare(strict_types=1);
/** /**
* Execution handlers for content generation and metrics collection. * Cron/AJAX handlers for content generation, metrics collection, model registry.
* *
* What: Cron handlers, generation AJAX (start + status polling), model registry. * Triggered by: PRAutoBlogger hook registration.
* Who calls it: PRAutoBlogger registers hooks that delegate here. * Dependencies: Pipeline_Runner, Metrics_Collector, Generation_Lock, Logger.
* Dependencies: Pipeline_Runner, Metrics_Collector, Generation_Lock, Logger, Model_Registry.
* *
* @see class-prautoblogger.php — Hook registration that wires these handlers. * @see class-prautoblogger.php — Hook wiring.
* @see core/class-pipeline-runner.php — Actual generation pipeline. * @see core/class-pipeline-runner.php — Actual generation pipeline.
* @see class-generation-lock.php — DB mutex extracted from this class. * @see class-generation-lock.php — DB mutex.
* @see class-ajax-handlers.php — Non-generation AJAX endpoints (images, models, test).
*/ */
class PRAutoBlogger_Executor { class PRAutoBlogger_Executor {
@@ -134,16 +132,7 @@ class PRAutoBlogger_Executor {
] ); ] );
} }
/** /** Cron handler: runs pipeline in background, writes results to transient. */
* Cron handler for manual (background) generation.
*
* Runs in a separate PHP process triggered by WP-Cron, free from
* the web server's connection timeout. Writes progress and final
* results to a transient that the frontend polls.
*
* Side effects: API calls, database writes, WordPress post creation,
* transient updates.
*/
public function on_manual_generation(): void { public function on_manual_generation(): void {
// Keep PHP running even after LiteSpeed closes the HTTP connection. // Keep PHP running even after LiteSpeed closes the HTTP connection.
// On Hostinger shared hosting, the web server kills HTTP connections // On Hostinger shared hosting, the web server kills HTTP connections
@@ -189,19 +178,7 @@ class PRAutoBlogger_Executor {
} }
} }
/** /** AJAX: return generation status for frontend polling. Recovers orphaned runs. */
* AJAX handler: return current generation status for frontend polling.
*
* Lightweight endpoint — reads a transient and returns it. Called every
* few seconds by admin.js while generation is in progress.
*
* Includes fallback detection: if the pipeline produced a post but the
* PHP process was killed before updating the transient (Hostinger's
* 120-second timeout), this endpoint detects the orphaned state and
* recovers gracefully after 180 seconds.
*
* Side effects: may delete stale transient and lock on recovery.
*/
public function on_ajax_generation_status(): void { public function on_ajax_generation_status(): void {
check_ajax_referer( 'prautoblogger_generate_now', 'nonce' ); check_ajax_referer( 'prautoblogger_generate_now', 'nonce' );
@@ -218,6 +195,10 @@ class PRAutoBlogger_Executor {
} }
// Fallback: detect orphaned "running" state when PHP process was killed. // Fallback: detect orphaned "running" state when PHP process was killed.
// On Hostinger shared hosting, set_time_limit() is often ignored and
// PHP kills the cron process at max_execution_time (120–180s). The
// pipeline creates the post first and images last, so the post exists
// even when the process died during image generation.
if ( 'running' === $status['status'] ) { if ( 'running' === $status['status'] ) {
$started = $status['started'] ?? 0; $started = $status['started'] ?? 0;
$elapsed = time() - $started; $elapsed = time() - $started;
@@ -232,9 +213,12 @@ class PRAutoBlogger_Executor {
return; return;
} }
// After 5 minutes, check if pipeline produced output despite the kill. // After 90 seconds, proactively check if the pipeline already
// Pipeline with 3 articles + images takes 3–4 min; wait before declaring orphaned. // produced a post. This catches the common case where the cron
if ( $elapsed > 300 ) { // process was killed by max_execution_time after publishing but
// before updating the transient. Without this, the user stares
// at "Generating…" for 5+ minutes despite the work being done.
if ( $elapsed > 90 ) {
$recent = get_posts( [ $recent = get_posts( [
'post_type' => 'post', 'post_type' => 'post',
'post_status' => [ 'publish', 'draft' ], 'post_status' => [ 'publish', 'draft' ],
@@ -244,10 +228,10 @@ class PRAutoBlogger_Executor {
'orderby' => 'date', 'orderby' => 'date',
'order' => 'DESC', 'order' => 'DESC',
] ); ] );
delete_transient( self::STATUS_TRANSIENT );
PRAutoBlogger_Generation_Lock::release();
if ( ! empty( $recent ) ) { if ( ! empty( $recent ) ) {
delete_transient( self::STATUS_TRANSIENT );
PRAutoBlogger_Generation_Lock::release();
wp_send_json_success( [ wp_send_json_success( [
'status' => 'complete', 'status' => 'complete',
'generated' => 1, 'generated' => 1,
@@ -258,11 +242,18 @@ class PRAutoBlogger_Executor {
] ); ] );
return; return;
} }
wp_send_json_success( [
'status' => 'error', // No post found yet — only declare failure after 5 minutes.
'message' => __( 'Generation process ended without producing content. Check Activity Log.', 'prautoblogger' ), // The pipeline genuinely needs 2–4 min for article + images.
] ); if ( $elapsed > 300 ) {
return; delete_transient( self::STATUS_TRANSIENT );
PRAutoBlogger_Generation_Lock::release();
wp_send_json_success( [
'status' => 'error',
'message' => __( 'Generation process ended without producing content. Check Activity Log.', 'prautoblogger' ),
] );
return;
}
} }
} }