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:
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user