Changeset 3460335
- Timestamp:
- 02/12/2026 08:46:29 PM (6 weeks ago)
- Location:
- alt-text-imagerr-ai
- Files:
-
- 10 edited
- 1 copied
-
tags/1.6.1 (copied) (copied from alt-text-imagerr-ai/trunk)
-
tags/1.6.1/assets/imagerr-settings.js (modified) (2 diffs)
-
tags/1.6.1/imagerr.php (modified) (5 diffs)
-
tags/1.6.1/readme.txt (modified) (2 diffs)
-
tags/1.6.1/src/Async/BackgroundProcess.php (modified) (14 diffs)
-
tags/1.6.1/src/MetaBulkAsync.php (modified) (1 diff)
-
trunk/assets/imagerr-settings.js (modified) (2 diffs)
-
trunk/imagerr.php (modified) (5 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/src/Async/BackgroundProcess.php (modified) (14 diffs)
-
trunk/src/MetaBulkAsync.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
alt-text-imagerr-ai/tags/1.6.1/assets/imagerr-settings.js
r3434623 r3460335 15 15 } 16 16 17 var progressIntervalId = null; 18 19 function fetchBulkStatus() { 20 $.get({ 21 url: imagerr_vars.rest_url + '/bulk-status', 22 data: { 23 _wpnonce: imagerr_vars.nonce, 24 _t: Date.now() 25 }, 26 success: function(response) { 27 // Restore status text in case it was set to "Reconnecting…" by a previous error 28 if (response.processed !== response.total && $('#generate-meta-bulk-status').length) { 29 $('#generate-meta-bulk-status').text(imagerr_vars.i18n.status_generating); 30 } 31 var progressText = response.processed + '/' + response.total + ' ' + imagerr_vars.i18n.images_processed; 32 $('#generate-meta-bulk-progress-text').text(progressText); 33 34 // Calculate percentage for progress bar and ensure it's a valid number 35 var percentage = 0; 36 if (response.total > 0) { 37 percentage = Math.min(100, Math.max(0, (response.processed / response.total) * 100)); 38 } 39 $('#generate-meta-bulk-progress').val(percentage); 40 41 // Update credits display if available 42 if (response.credits !== undefined) { 43 updateCreditsWithAnimation(response.credits); 44 } 45 46 if (response.processed === response.total) { 47 if (progressIntervalId) { 48 clearInterval(progressIntervalId); 49 progressIntervalId = null; 50 } 51 $('#generate-meta-bulk-progress').val(100); 52 $('#generate-meta-bulk-progress-text').text(imagerr_vars.i18n.all_images_processed); 53 resetButtonAndStatus(); 54 fetchErrorImages(); 55 } 56 }, 57 error: function() { 58 // Don't clear interval; show reconnecting so user knows UI is temporarily stale 59 if (progressIntervalId && $('#generate-meta-bulk-status').length) { 60 $('#generate-meta-bulk-status').text(imagerr_vars.i18n.status_reconnecting || 'Reconnecting…'); 61 } 62 } 63 }); 64 } 65 17 66 function startProgressInterval() { 18 var intervalId = setInterval(function() { 19 $.get({ 20 url: imagerr_vars.rest_url + '/bulk-status', 21 data: { 22 _wpnonce: imagerr_vars.nonce 23 }, 24 success: function(response) { 25 var progressText = response.processed + '/' + response.total + ' ' + imagerr_vars.i18n.images_processed; 26 $('#generate-meta-bulk-progress-text').text(progressText); 27 28 // Calculate percentage for progress bar and ensure it's a valid number 29 var percentage = 0; 30 if (response.total > 0) { 31 percentage = Math.min(100, Math.max(0, (response.processed / response.total) * 100)); 32 } 33 $('#generate-meta-bulk-progress').val(percentage); 34 35 // Update credits display if available 36 if (response.credits !== undefined) { 37 updateCreditsWithAnimation(response.credits); 38 } 39 40 if (response.processed === response.total) { 41 clearInterval(intervalId); 42 // Keep the final progress values 43 $('#generate-meta-bulk-progress').val(100); 44 $('#generate-meta-bulk-progress-text').text(imagerr_vars.i18n.all_images_processed); 45 resetButtonAndStatus(); 46 // Get error images after completion 47 fetchErrorImages(); 48 } 49 } 50 }); 51 }, 2000); 67 if (progressIntervalId) { 68 clearInterval(progressIntervalId); 69 } 70 progressIntervalId = setInterval(fetchBulkStatus, 2000); 52 71 } 53 72 … … 165 184 fetchErrorImages(); 166 185 186 // When tab becomes visible, fetch status immediately so progress bar catches up 187 document.addEventListener('visibilitychange', function() { 188 if (document.visibilityState === 'visible' && progressIntervalId !== null) { 189 fetchBulkStatus(); 190 } 191 }); 192 167 193 // Check if we're in a completed state 168 194 if (imagerr_vars.is_generating) { -
alt-text-imagerr-ai/tags/1.6.1/imagerr.php
r3438971 r3460335 3 3 * Plugin Name: AI Image Alt Text Generator – Imagerr AI 4 4 * Description: Generate alt text, titles, descriptions, and captions for your images automatically with AI. 5 * Version: 1.6 5 * Version: 1.6.1 6 6 * Text Domain: alt-text-imagerr-ai 7 7 * Domain Path: /languages … … 346 346 } 347 347 348 // Check if $meta_bulk_async is running .349 $is_generating = $this->meta_bulk_async->is_processing() ;348 // Check if $meta_bulk_async is running or queue has items (e.g. between batches). 349 $is_generating = $this->meta_bulk_async->is_processing() || $this->meta_bulk_async->is_queued(); 350 350 $progress = 0; 351 351 $progress_text = '--'; … … 385 385 wp_enqueue_script( 'imagerr-settings-script', plugin_dir_url( __FILE__ ) . 'assets/imagerr-settings.js', array( 'jquery' ), IMAGERR_VERSION, true ); 386 386 387 // Check if $meta_bulk_async is running .388 $is_generating = $this->meta_bulk_async->is_processing() ;387 // Check if $meta_bulk_async is running or queue has items (e.g. between batches). 388 $is_generating = $this->meta_bulk_async->is_processing() || $this->meta_bulk_async->is_queued(); 389 389 390 390 // Check if we're in selected images mode … … 407 407 'status_generating' => esc_html__( '🏃♂️➡️ Generating...', 'alt-text-imagerr-ai' ), 408 408 'status_stopping_generation' => esc_html__( '🚫 Stopping generation...', 'alt-text-imagerr-ai' ), 409 'status_reconnecting' => esc_html__( 'Reconnecting…', 'alt-text-imagerr-ai' ), 409 410 'image_updated' => esc_html__( '✅ Image updated', 'alt-text-imagerr-ai' ), 410 411 'images_processed' => esc_html__( 'images processed', 'alt-text-imagerr-ai' ), … … 703 704 $credits = $this->meta->get_credits(); 704 705 $status['credits'] = $credits; 705 return new \WP_REST_Response( $status, 200 ); 706 707 // If queue has work but process is not running (e.g. dispatch failed), try to wake it up (throttled). 708 if ( $this->meta_bulk_async->is_queued() && ! $this->meta_bulk_async->is_processing() ) { 709 $wakeup_transient = 'imagerr_meta_bulk_wakeup_attempted'; 710 if ( ! get_site_transient( $wakeup_transient ) ) { 711 set_site_transient( $wakeup_transient, true, 60 ); 712 $this->meta_bulk_async->log_message( 'Dispatched from bulk-status (status check wake-up)' ); 713 $this->meta_bulk_async->dispatch(); 714 } 715 } 716 717 $response = new \WP_REST_Response( $status, 200 ); 718 $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate' ); 719 $response->header( 'Pragma', 'no-cache' ); 720 return $response; 706 721 } 707 722 -
alt-text-imagerr-ai/tags/1.6.1/readme.txt
r3438971 r3460335 5 5 Requires PHP: 5.2 6 6 Requires at least: 4.6 7 Stable tag: 1.6 7 Stable tag: 1.6.1 8 8 Tested up to: 6.9 9 9 License: GPLv2 or later … … 71 71 72 72 == Changelog == 73 = 1.6.1 = 74 * Added extra debug logging to the background process for easier troubleshooting 75 * Refined bulk generation and background processing for better reliability 76 73 77 = 1.6 = 74 78 * Added support for palette type images -
alt-text-imagerr-ai/tags/1.6.1/src/Async/BackgroundProcess.php
r3438971 r3460335 105 105 106 106 /** 107 * Log a message to the debug file (public wrapper for callers outside the process). 108 * 109 * @param string $message The message to log. 110 * @return void 111 */ 112 public function log_message( $message ) { 113 $this->log( $message ); 114 } 115 116 /** 107 117 * Log a message to the debug file. 108 118 * … … 113 123 // Only log if debug is enabled or if debug logs option is enabled 114 124 $debug_enabled = ( defined( 'IMAGERR_DEBUG' ) && IMAGERR_DEBUG ) || get_option( 'imagerr_enable_debug_logs', false ); 115 125 116 126 if ( $debug_enabled ) { 117 127 // Rotate log if it exceeds size limit 118 128 \Imagerr::rotate_log_if_needed(); 119 129 120 130 $log_file = \Imagerr::get_debug_log_path(); 121 131 $timestamp = date( 'Y-m-d H:i:s' ); … … 140 150 $this->schedule_event(); 141 151 152 // Get URL and log it for debugging 153 $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); 154 $this->log( sprintf( 'Dispatching to URL: %s', $url ) ); 155 156 // Check if URL is accessible (common issue with localhost/dev sites) 157 $parsed_url = parse_url( $url ); 158 if ( isset( $parsed_url['host'] ) && in_array( $parsed_url['host'], array( 'localhost', '127.0.0.1', '::1' ), true ) ) { 159 $this->log( 'WARNING: Using localhost URL - async requests may fail. Cron healthcheck will be used as fallback.' ); 160 } 161 142 162 // Perform remote post. 143 163 $result = parent::dispatch(); 144 164 145 165 if ( is_wp_error( $result ) ) { 146 $this->log( sprintf( 'Dispatch failed: %s', $result->get_error_message() ) ); 166 $this->log( sprintf( 'Dispatch failed with WP_Error: %s (code: %s)', $result->get_error_message(), $result->get_error_code() ) ); 167 $this->schedule_fallback_healthcheck(); 147 168 } elseif ( false === $result ) { 148 169 $this->log( 'Dispatch returned false' ); 170 $this->schedule_fallback_healthcheck(); 149 171 } else { 150 $this->log( 'Dispatch successful' ); 151 } 152 172 // Check response code even for non-blocking requests 173 $response_code = wp_remote_retrieve_response_code( $result ); 174 if ( $response_code ) { 175 $this->log( sprintf( 'Dispatch successful - Response code: %d', $response_code ) ); 176 } else { 177 $this->log( 'Dispatch successful (non-blocking request - response code not available)' ); 178 } 179 $response_body = wp_remote_retrieve_body( $result ); 180 $this->log( sprintf( 'Dispatch response body: %s', $response_body !== '' ? $response_body : '(empty)' ) ); 181 $this->log( 'Note: If processing does not start, cron healthcheck will handle it.' ); 182 } 183 153 184 return $result; 154 185 } … … 447 478 448 479 $lock_acquired = set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration ); 449 480 450 481 if ( ! $lock_acquired ) { 451 482 $this->log( sprintf( 'WARNING: Failed to acquire process lock (duration: %d seconds)', $lock_duration ) ); … … 464 495 protected function unlock_process() { 465 496 $deleted = delete_site_transient( $this->identifier . '_process_lock' ); 466 497 467 498 if ( ! $deleted ) { 468 499 $this->log( 'WARNING: Failed to release process lock' ); … … 550 581 $batch->key = $item->{$column}; 551 582 $batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes ); 552 583 553 584 if ( false === $batch->data && is_serialized( $item->{$value_column} ) ) { 554 585 $this->log( sprintf( 'WARNING: Failed to unserialize batch data for key: %s', $batch->key ) ); 555 586 return null; 556 587 } 557 588 558 589 return $batch; 559 590 } catch ( \Exception $e ) { … … 564 595 $items 565 596 ); 566 597 567 598 // Filter out null values 568 599 $batches = array_filter( $batches ); … … 609 640 610 641 $batch = $this->get_batch(); 611 642 612 643 if ( empty( $batch ) || empty( $batch->data ) ) { 613 644 $this->log( 'No batch found or empty batch, stopping' ); 614 645 break; 615 646 } 616 647 617 648 $this->log( sprintf( 'Processing batch %s with %d items', $batch->key, count( $batch->data ) ) ); 618 649 … … 699 730 700 731 if ( $current_memory >= $memory_limit ) { 701 $this->log( sprintf( 702 'Memory limit exceeded: %s / %s (90%% of limit)', 732 $this->log( sprintf( 733 'Memory limit exceeded: %s / %s (90%% of limit)', 703 734 size_format( $current_memory ), 704 735 size_format( $this->get_memory_limit() ) … … 746 777 747 778 if ( time() >= $finish ) { 748 $this->log( sprintf( 749 'Time limit exceeded: %d seconds elapsed (limit: %d seconds)', 779 $this->log( sprintf( 780 'Time limit exceeded: %d seconds elapsed (limit: %d seconds)', 750 781 $elapsed, 751 782 $time_limit … … 828 859 829 860 /** 861 * Schedule a one-off healthcheck soon when immediate dispatch failed. 862 * Ensures the queue resumes within ~30s instead of waiting for the next recurring cron. 863 */ 864 protected function schedule_fallback_healthcheck() { 865 $delay_seconds = (int) apply_filters( $this->identifier . '_fallback_healthcheck_delay', 30 ); 866 $delay_seconds = max( 10, min( 120, $delay_seconds ) ); 867 $when = time() + $delay_seconds; 868 $scheduled = wp_schedule_single_event( $when, $this->cron_hook_identifier ); 869 if ( false === $scheduled || is_wp_error( $scheduled ) ) { 870 $this->log( sprintf( 871 'Fallback healthcheck schedule failed (in %d s). Recurring cron will retry.', 872 $delay_seconds 873 ) ); 874 } else { 875 $this->log( sprintf( 876 'Fallback healthcheck scheduled in %d seconds (hook: %s)', 877 $delay_seconds, 878 $this->cron_hook_identifier 879 ) ); 880 } 881 } 882 883 /** 830 884 * Handle cron healthcheck event. 831 885 * … … 835 889 public function handle_cron_healthcheck() { 836 890 $this->log( 'Cron healthcheck triggered' ); 837 891 838 892 if ( $this->is_processing() ) { 839 893 $lock_time = get_site_transient( $this->identifier . '_process_lock' ); 840 894 $lock_age = $lock_time ? time() - (int) $lock_time : 0; 841 842 $this->log( sprintf( 843 'Process already running (lock age: %d seconds), skipping', 895 896 $this->log( sprintf( 897 'Process already running (lock age: %d seconds), skipping', 844 898 $lock_age 845 899 ) ); … … 861 915 */ 862 916 protected function schedule_event() { 863 if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { 864 wp_schedule_event( time() + ( $this->get_cron_interval() * MINUTE_IN_SECONDS ), $this->cron_interval_identifier, $this->cron_hook_identifier ); 917 $next_scheduled = wp_next_scheduled( $this->cron_hook_identifier ); 918 $interval = $this->get_cron_interval(); 919 920 if ( $next_scheduled ) { 921 $this->log( sprintf( 922 'Cron event already scheduled (hook: %s, interval: %d min). Next run: %s', 923 $this->cron_hook_identifier, 924 $interval, 925 gmdate( 'Y-m-d H:i:s', $next_scheduled ) 926 ) ); 927 return; 928 } 929 930 $first_run = time() + ( $interval * MINUTE_IN_SECONDS ); 931 $scheduled = wp_schedule_event( $first_run, $this->cron_interval_identifier, $this->cron_hook_identifier ); 932 933 if ( false === $scheduled || is_wp_error( $scheduled ) ) { 934 $this->log( sprintf( 935 'Cron schedule FAILED (hook: %s, interval: %d min). wp_schedule_event returned: %s', 936 $this->cron_hook_identifier, 937 $interval, 938 is_wp_error( $scheduled ) ? $scheduled->get_error_message() : 'false' 939 ) ); 940 } else { 941 $this->log( sprintf( 942 'Cron event scheduled successfully (hook: %s, interval: %d min). First run: %s', 943 $this->cron_hook_identifier, 944 $interval, 945 gmdate( 'Y-m-d H:i:s', $first_run ) 946 ) ); 865 947 } 866 948 } … … 874 956 if ( $timestamp ) { 875 957 wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); 958 $this->log( sprintf( 959 'Cron event cleared (hook: %s, was scheduled for: %s)', 960 $this->cron_hook_identifier, 961 gmdate( 'Y-m-d H:i:s', $timestamp ) 962 ) ); 963 } else { 964 $this->log( sprintf( 965 'Cron event clear: nothing scheduled (hook: %s)', 966 $this->cron_hook_identifier 967 ) ); 876 968 } 877 969 } -
alt-text-imagerr-ai/tags/1.6.1/src/MetaBulkAsync.php
r3434623 r3460335 30 30 */ 31 31 protected $action = 'generate_meta_bulk'; 32 33 /** 34 * Cron healthcheck interval in minutes. 35 * Shorter than default (5) so fallback cron resumes queue sooner if dispatch fails. 36 * 37 * @var int 38 */ 39 protected $cron_interval = 1; 32 40 33 41 /** -
alt-text-imagerr-ai/trunk/assets/imagerr-settings.js
r3434623 r3460335 15 15 } 16 16 17 var progressIntervalId = null; 18 19 function fetchBulkStatus() { 20 $.get({ 21 url: imagerr_vars.rest_url + '/bulk-status', 22 data: { 23 _wpnonce: imagerr_vars.nonce, 24 _t: Date.now() 25 }, 26 success: function(response) { 27 // Restore status text in case it was set to "Reconnecting…" by a previous error 28 if (response.processed !== response.total && $('#generate-meta-bulk-status').length) { 29 $('#generate-meta-bulk-status').text(imagerr_vars.i18n.status_generating); 30 } 31 var progressText = response.processed + '/' + response.total + ' ' + imagerr_vars.i18n.images_processed; 32 $('#generate-meta-bulk-progress-text').text(progressText); 33 34 // Calculate percentage for progress bar and ensure it's a valid number 35 var percentage = 0; 36 if (response.total > 0) { 37 percentage = Math.min(100, Math.max(0, (response.processed / response.total) * 100)); 38 } 39 $('#generate-meta-bulk-progress').val(percentage); 40 41 // Update credits display if available 42 if (response.credits !== undefined) { 43 updateCreditsWithAnimation(response.credits); 44 } 45 46 if (response.processed === response.total) { 47 if (progressIntervalId) { 48 clearInterval(progressIntervalId); 49 progressIntervalId = null; 50 } 51 $('#generate-meta-bulk-progress').val(100); 52 $('#generate-meta-bulk-progress-text').text(imagerr_vars.i18n.all_images_processed); 53 resetButtonAndStatus(); 54 fetchErrorImages(); 55 } 56 }, 57 error: function() { 58 // Don't clear interval; show reconnecting so user knows UI is temporarily stale 59 if (progressIntervalId && $('#generate-meta-bulk-status').length) { 60 $('#generate-meta-bulk-status').text(imagerr_vars.i18n.status_reconnecting || 'Reconnecting…'); 61 } 62 } 63 }); 64 } 65 17 66 function startProgressInterval() { 18 var intervalId = setInterval(function() { 19 $.get({ 20 url: imagerr_vars.rest_url + '/bulk-status', 21 data: { 22 _wpnonce: imagerr_vars.nonce 23 }, 24 success: function(response) { 25 var progressText = response.processed + '/' + response.total + ' ' + imagerr_vars.i18n.images_processed; 26 $('#generate-meta-bulk-progress-text').text(progressText); 27 28 // Calculate percentage for progress bar and ensure it's a valid number 29 var percentage = 0; 30 if (response.total > 0) { 31 percentage = Math.min(100, Math.max(0, (response.processed / response.total) * 100)); 32 } 33 $('#generate-meta-bulk-progress').val(percentage); 34 35 // Update credits display if available 36 if (response.credits !== undefined) { 37 updateCreditsWithAnimation(response.credits); 38 } 39 40 if (response.processed === response.total) { 41 clearInterval(intervalId); 42 // Keep the final progress values 43 $('#generate-meta-bulk-progress').val(100); 44 $('#generate-meta-bulk-progress-text').text(imagerr_vars.i18n.all_images_processed); 45 resetButtonAndStatus(); 46 // Get error images after completion 47 fetchErrorImages(); 48 } 49 } 50 }); 51 }, 2000); 67 if (progressIntervalId) { 68 clearInterval(progressIntervalId); 69 } 70 progressIntervalId = setInterval(fetchBulkStatus, 2000); 52 71 } 53 72 … … 165 184 fetchErrorImages(); 166 185 186 // When tab becomes visible, fetch status immediately so progress bar catches up 187 document.addEventListener('visibilitychange', function() { 188 if (document.visibilityState === 'visible' && progressIntervalId !== null) { 189 fetchBulkStatus(); 190 } 191 }); 192 167 193 // Check if we're in a completed state 168 194 if (imagerr_vars.is_generating) { -
alt-text-imagerr-ai/trunk/imagerr.php
r3438971 r3460335 3 3 * Plugin Name: AI Image Alt Text Generator – Imagerr AI 4 4 * Description: Generate alt text, titles, descriptions, and captions for your images automatically with AI. 5 * Version: 1.6 5 * Version: 1.6.1 6 6 * Text Domain: alt-text-imagerr-ai 7 7 * Domain Path: /languages … … 346 346 } 347 347 348 // Check if $meta_bulk_async is running .349 $is_generating = $this->meta_bulk_async->is_processing() ;348 // Check if $meta_bulk_async is running or queue has items (e.g. between batches). 349 $is_generating = $this->meta_bulk_async->is_processing() || $this->meta_bulk_async->is_queued(); 350 350 $progress = 0; 351 351 $progress_text = '--'; … … 385 385 wp_enqueue_script( 'imagerr-settings-script', plugin_dir_url( __FILE__ ) . 'assets/imagerr-settings.js', array( 'jquery' ), IMAGERR_VERSION, true ); 386 386 387 // Check if $meta_bulk_async is running .388 $is_generating = $this->meta_bulk_async->is_processing() ;387 // Check if $meta_bulk_async is running or queue has items (e.g. between batches). 388 $is_generating = $this->meta_bulk_async->is_processing() || $this->meta_bulk_async->is_queued(); 389 389 390 390 // Check if we're in selected images mode … … 407 407 'status_generating' => esc_html__( '🏃♂️➡️ Generating...', 'alt-text-imagerr-ai' ), 408 408 'status_stopping_generation' => esc_html__( '🚫 Stopping generation...', 'alt-text-imagerr-ai' ), 409 'status_reconnecting' => esc_html__( 'Reconnecting…', 'alt-text-imagerr-ai' ), 409 410 'image_updated' => esc_html__( '✅ Image updated', 'alt-text-imagerr-ai' ), 410 411 'images_processed' => esc_html__( 'images processed', 'alt-text-imagerr-ai' ), … … 703 704 $credits = $this->meta->get_credits(); 704 705 $status['credits'] = $credits; 705 return new \WP_REST_Response( $status, 200 ); 706 707 // If queue has work but process is not running (e.g. dispatch failed), try to wake it up (throttled). 708 if ( $this->meta_bulk_async->is_queued() && ! $this->meta_bulk_async->is_processing() ) { 709 $wakeup_transient = 'imagerr_meta_bulk_wakeup_attempted'; 710 if ( ! get_site_transient( $wakeup_transient ) ) { 711 set_site_transient( $wakeup_transient, true, 60 ); 712 $this->meta_bulk_async->log_message( 'Dispatched from bulk-status (status check wake-up)' ); 713 $this->meta_bulk_async->dispatch(); 714 } 715 } 716 717 $response = new \WP_REST_Response( $status, 200 ); 718 $response->header( 'Cache-Control', 'no-store, no-cache, must-revalidate' ); 719 $response->header( 'Pragma', 'no-cache' ); 720 return $response; 706 721 } 707 722 -
alt-text-imagerr-ai/trunk/readme.txt
r3438971 r3460335 5 5 Requires PHP: 5.2 6 6 Requires at least: 4.6 7 Stable tag: 1.6 7 Stable tag: 1.6.1 8 8 Tested up to: 6.9 9 9 License: GPLv2 or later … … 71 71 72 72 == Changelog == 73 = 1.6.1 = 74 * Added extra debug logging to the background process for easier troubleshooting 75 * Refined bulk generation and background processing for better reliability 76 73 77 = 1.6 = 74 78 * Added support for palette type images -
alt-text-imagerr-ai/trunk/src/Async/BackgroundProcess.php
r3438971 r3460335 105 105 106 106 /** 107 * Log a message to the debug file (public wrapper for callers outside the process). 108 * 109 * @param string $message The message to log. 110 * @return void 111 */ 112 public function log_message( $message ) { 113 $this->log( $message ); 114 } 115 116 /** 107 117 * Log a message to the debug file. 108 118 * … … 113 123 // Only log if debug is enabled or if debug logs option is enabled 114 124 $debug_enabled = ( defined( 'IMAGERR_DEBUG' ) && IMAGERR_DEBUG ) || get_option( 'imagerr_enable_debug_logs', false ); 115 125 116 126 if ( $debug_enabled ) { 117 127 // Rotate log if it exceeds size limit 118 128 \Imagerr::rotate_log_if_needed(); 119 129 120 130 $log_file = \Imagerr::get_debug_log_path(); 121 131 $timestamp = date( 'Y-m-d H:i:s' ); … … 140 150 $this->schedule_event(); 141 151 152 // Get URL and log it for debugging 153 $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); 154 $this->log( sprintf( 'Dispatching to URL: %s', $url ) ); 155 156 // Check if URL is accessible (common issue with localhost/dev sites) 157 $parsed_url = parse_url( $url ); 158 if ( isset( $parsed_url['host'] ) && in_array( $parsed_url['host'], array( 'localhost', '127.0.0.1', '::1' ), true ) ) { 159 $this->log( 'WARNING: Using localhost URL - async requests may fail. Cron healthcheck will be used as fallback.' ); 160 } 161 142 162 // Perform remote post. 143 163 $result = parent::dispatch(); 144 164 145 165 if ( is_wp_error( $result ) ) { 146 $this->log( sprintf( 'Dispatch failed: %s', $result->get_error_message() ) ); 166 $this->log( sprintf( 'Dispatch failed with WP_Error: %s (code: %s)', $result->get_error_message(), $result->get_error_code() ) ); 167 $this->schedule_fallback_healthcheck(); 147 168 } elseif ( false === $result ) { 148 169 $this->log( 'Dispatch returned false' ); 170 $this->schedule_fallback_healthcheck(); 149 171 } else { 150 $this->log( 'Dispatch successful' ); 151 } 152 172 // Check response code even for non-blocking requests 173 $response_code = wp_remote_retrieve_response_code( $result ); 174 if ( $response_code ) { 175 $this->log( sprintf( 'Dispatch successful - Response code: %d', $response_code ) ); 176 } else { 177 $this->log( 'Dispatch successful (non-blocking request - response code not available)' ); 178 } 179 $response_body = wp_remote_retrieve_body( $result ); 180 $this->log( sprintf( 'Dispatch response body: %s', $response_body !== '' ? $response_body : '(empty)' ) ); 181 $this->log( 'Note: If processing does not start, cron healthcheck will handle it.' ); 182 } 183 153 184 return $result; 154 185 } … … 447 478 448 479 $lock_acquired = set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration ); 449 480 450 481 if ( ! $lock_acquired ) { 451 482 $this->log( sprintf( 'WARNING: Failed to acquire process lock (duration: %d seconds)', $lock_duration ) ); … … 464 495 protected function unlock_process() { 465 496 $deleted = delete_site_transient( $this->identifier . '_process_lock' ); 466 497 467 498 if ( ! $deleted ) { 468 499 $this->log( 'WARNING: Failed to release process lock' ); … … 550 581 $batch->key = $item->{$column}; 551 582 $batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes ); 552 583 553 584 if ( false === $batch->data && is_serialized( $item->{$value_column} ) ) { 554 585 $this->log( sprintf( 'WARNING: Failed to unserialize batch data for key: %s', $batch->key ) ); 555 586 return null; 556 587 } 557 588 558 589 return $batch; 559 590 } catch ( \Exception $e ) { … … 564 595 $items 565 596 ); 566 597 567 598 // Filter out null values 568 599 $batches = array_filter( $batches ); … … 609 640 610 641 $batch = $this->get_batch(); 611 642 612 643 if ( empty( $batch ) || empty( $batch->data ) ) { 613 644 $this->log( 'No batch found or empty batch, stopping' ); 614 645 break; 615 646 } 616 647 617 648 $this->log( sprintf( 'Processing batch %s with %d items', $batch->key, count( $batch->data ) ) ); 618 649 … … 699 730 700 731 if ( $current_memory >= $memory_limit ) { 701 $this->log( sprintf( 702 'Memory limit exceeded: %s / %s (90%% of limit)', 732 $this->log( sprintf( 733 'Memory limit exceeded: %s / %s (90%% of limit)', 703 734 size_format( $current_memory ), 704 735 size_format( $this->get_memory_limit() ) … … 746 777 747 778 if ( time() >= $finish ) { 748 $this->log( sprintf( 749 'Time limit exceeded: %d seconds elapsed (limit: %d seconds)', 779 $this->log( sprintf( 780 'Time limit exceeded: %d seconds elapsed (limit: %d seconds)', 750 781 $elapsed, 751 782 $time_limit … … 828 859 829 860 /** 861 * Schedule a one-off healthcheck soon when immediate dispatch failed. 862 * Ensures the queue resumes within ~30s instead of waiting for the next recurring cron. 863 */ 864 protected function schedule_fallback_healthcheck() { 865 $delay_seconds = (int) apply_filters( $this->identifier . '_fallback_healthcheck_delay', 30 ); 866 $delay_seconds = max( 10, min( 120, $delay_seconds ) ); 867 $when = time() + $delay_seconds; 868 $scheduled = wp_schedule_single_event( $when, $this->cron_hook_identifier ); 869 if ( false === $scheduled || is_wp_error( $scheduled ) ) { 870 $this->log( sprintf( 871 'Fallback healthcheck schedule failed (in %d s). Recurring cron will retry.', 872 $delay_seconds 873 ) ); 874 } else { 875 $this->log( sprintf( 876 'Fallback healthcheck scheduled in %d seconds (hook: %s)', 877 $delay_seconds, 878 $this->cron_hook_identifier 879 ) ); 880 } 881 } 882 883 /** 830 884 * Handle cron healthcheck event. 831 885 * … … 835 889 public function handle_cron_healthcheck() { 836 890 $this->log( 'Cron healthcheck triggered' ); 837 891 838 892 if ( $this->is_processing() ) { 839 893 $lock_time = get_site_transient( $this->identifier . '_process_lock' ); 840 894 $lock_age = $lock_time ? time() - (int) $lock_time : 0; 841 842 $this->log( sprintf( 843 'Process already running (lock age: %d seconds), skipping', 895 896 $this->log( sprintf( 897 'Process already running (lock age: %d seconds), skipping', 844 898 $lock_age 845 899 ) ); … … 861 915 */ 862 916 protected function schedule_event() { 863 if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { 864 wp_schedule_event( time() + ( $this->get_cron_interval() * MINUTE_IN_SECONDS ), $this->cron_interval_identifier, $this->cron_hook_identifier ); 917 $next_scheduled = wp_next_scheduled( $this->cron_hook_identifier ); 918 $interval = $this->get_cron_interval(); 919 920 if ( $next_scheduled ) { 921 $this->log( sprintf( 922 'Cron event already scheduled (hook: %s, interval: %d min). Next run: %s', 923 $this->cron_hook_identifier, 924 $interval, 925 gmdate( 'Y-m-d H:i:s', $next_scheduled ) 926 ) ); 927 return; 928 } 929 930 $first_run = time() + ( $interval * MINUTE_IN_SECONDS ); 931 $scheduled = wp_schedule_event( $first_run, $this->cron_interval_identifier, $this->cron_hook_identifier ); 932 933 if ( false === $scheduled || is_wp_error( $scheduled ) ) { 934 $this->log( sprintf( 935 'Cron schedule FAILED (hook: %s, interval: %d min). wp_schedule_event returned: %s', 936 $this->cron_hook_identifier, 937 $interval, 938 is_wp_error( $scheduled ) ? $scheduled->get_error_message() : 'false' 939 ) ); 940 } else { 941 $this->log( sprintf( 942 'Cron event scheduled successfully (hook: %s, interval: %d min). First run: %s', 943 $this->cron_hook_identifier, 944 $interval, 945 gmdate( 'Y-m-d H:i:s', $first_run ) 946 ) ); 865 947 } 866 948 } … … 874 956 if ( $timestamp ) { 875 957 wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); 958 $this->log( sprintf( 959 'Cron event cleared (hook: %s, was scheduled for: %s)', 960 $this->cron_hook_identifier, 961 gmdate( 'Y-m-d H:i:s', $timestamp ) 962 ) ); 963 } else { 964 $this->log( sprintf( 965 'Cron event clear: nothing scheduled (hook: %s)', 966 $this->cron_hook_identifier 967 ) ); 876 968 } 877 969 } -
alt-text-imagerr-ai/trunk/src/MetaBulkAsync.php
r3434623 r3460335 30 30 */ 31 31 protected $action = 'generate_meta_bulk'; 32 33 /** 34 * Cron healthcheck interval in minutes. 35 * Shorter than default (5) so fallback cron resumes queue sooner if dispatch fails. 36 * 37 * @var int 38 */ 39 protected $cron_interval = 1; 32 40 33 41 /**
Note: See TracChangeset
for help on using the changeset viewer.