WP_Background_Process

Abstract WP_Background_Process class.

Defined (1)

The class is defined in the following location(s).

/includes/libraries/wp-background-process.php  
  1. abstract class WP_Background_Process extends WP_Async_Request { 
  2.  
  3. /** 
  4. * Action 
  5. * (default value: 'background_process') 
  6. * @var string 
  7. * @access protected 
  8. */ 
  9. protected $action = 'background_process'; 
  10.  
  11. /** 
  12. * Start time of current process. 
  13. * (default value: 0) 
  14. * @var int 
  15. * @access protected 
  16. */ 
  17. protected $start_time = 0; 
  18.  
  19. /** 
  20. * Cron_hook_identifier 
  21. * @var mixed 
  22. * @access protected 
  23. */ 
  24. protected $cron_hook_identifier; 
  25.  
  26. /** 
  27. * Cron_interval_identifier 
  28. * @var mixed 
  29. * @access protected 
  30. */ 
  31. protected $cron_interval_identifier; 
  32.  
  33. /** 
  34. * Initiate new background process 
  35. */ 
  36. public function __construct() { 
  37. parent::__construct(); 
  38.  
  39. $this->cron_hook_identifier = $this->identifier . '_cron'; 
  40. $this->cron_interval_identifier = $this->identifier . '_cron_interval'; 
  41.  
  42. add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) ); 
  43. add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) ); 
  44.  
  45. /** 
  46. * Dispatch 
  47. * @access public 
  48. * @return void 
  49. */ 
  50. public function dispatch() { 
  51. // Schedule the cron healthcheck. 
  52. $this->schedule_event(); 
  53.  
  54. // Perform remote post. 
  55. return parent::dispatch(); 
  56.  
  57. /** 
  58. * Push to queue 
  59. * @param mixed $data Data. 
  60. * @return $this 
  61. */ 
  62. public function push_to_queue( $data ) { 
  63. $this->data[] = $data; 
  64.  
  65. return $this; 
  66.  
  67. /** 
  68. * Save queue 
  69. * @return $this 
  70. */ 
  71. public function save() { 
  72. $key = $this->generate_key(); 
  73.  
  74. if ( ! empty( $this->data ) ) { 
  75. update_site_option( $key, $this->data ); 
  76.  
  77. return $this; 
  78.  
  79. /** 
  80. * Update queue 
  81. * @param string $key Key. 
  82. * @param array $data Data. 
  83. * @return $this 
  84. */ 
  85. public function update( $key, $data ) { 
  86. if ( ! empty( $data ) ) { 
  87. update_site_option( $key, $data ); 
  88.  
  89. return $this; 
  90.  
  91. /** 
  92. * Delete queue 
  93. * @param string $key Key. 
  94. * @return $this 
  95. */ 
  96. public function delete( $key ) { 
  97. delete_site_option( $key ); 
  98.  
  99. return $this; 
  100.  
  101. /** 
  102. * Generate key 
  103. * Generates a unique key based on microtime. Queue items are 
  104. * given a unique key so that they can be merged upon save. 
  105. * @param int $length Length. 
  106. * @return string 
  107. */ 
  108. protected function generate_key( $length = 64 ) { 
  109. $unique = md5( microtime() . rand() ); 
  110. $prepend = $this->identifier . '_batch_'; 
  111.  
  112. return substr( $prepend . $unique, 0, $length ); 
  113.  
  114. /** 
  115. * Maybe process queue 
  116. * Checks whether data exists within the queue and that 
  117. * the process is not already running. 
  118. */ 
  119. public function maybe_handle() { 
  120. // Don't lock up other requests while processing 
  121. session_write_close(); 
  122.  
  123. if ( $this->is_process_running() ) { 
  124. // Background process already running. 
  125. wp_die(); 
  126.  
  127. if ( $this->is_queue_empty() ) { 
  128. // No data to process. 
  129. wp_die(); 
  130.  
  131. check_ajax_referer( $this->identifier, 'nonce' ); 
  132.  
  133. $this->handle(); 
  134.  
  135. wp_die(); 
  136.  
  137. /** 
  138. * Is queue empty 
  139. * @return bool 
  140. */ 
  141. protected function is_queue_empty() { 
  142. global $wpdb; 
  143.  
  144. $table = $wpdb->options; 
  145. $column = 'option_name'; 
  146.  
  147. if ( is_multisite() ) { 
  148. $table = $wpdb->sitemeta; 
  149. $column = 'meta_key'; 
  150.  
  151. $key = $this->identifier . '_batch_%'; 
  152.  
  153. $count = $wpdb->get_var( $wpdb->prepare( " 
  154. SELECT COUNT(*) 
  155. FROM {$table} 
  156. WHERE {$column} LIKE %s 
  157. ", $key ) ); 
  158.  
  159. return ( $count > 0 ) ? false : true; 
  160.  
  161. /** 
  162. * Is process running 
  163. * Check whether the current process is already running 
  164. * in a background process. 
  165. */ 
  166. protected function is_process_running() { 
  167. if ( get_site_transient( $this->identifier . '_process_lock' ) ) { 
  168. // Process already running. 
  169. return true; 
  170.  
  171. return false; 
  172.  
  173. /** 
  174. * Lock process 
  175. * Lock the process so that multiple instances can't run simultaneously. 
  176. * Override if applicable, but the duration should be greater than that 
  177. * defined in the time_exceeded() method. 
  178. */ 
  179. protected function lock_process() { 
  180. $this->start_time = time(); // Set start time of current process. 
  181.  
  182. $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute 
  183. $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration ); 
  184.  
  185. set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration ); 
  186.  
  187. /** 
  188. * Unlock process 
  189. * Unlock the process so that other instances can spawn. 
  190. * @return $this 
  191. */ 
  192. protected function unlock_process() { 
  193. delete_site_transient( $this->identifier . '_process_lock' ); 
  194.  
  195. return $this; 
  196.  
  197. /** 
  198. * Get batch 
  199. * @return stdClass Return the first batch from the queue 
  200. */ 
  201. protected function get_batch() { 
  202. global $wpdb; 
  203.  
  204. $table = $wpdb->options; 
  205. $column = 'option_name'; 
  206. $key_column = 'option_id'; 
  207. $value_column = 'option_value'; 
  208.  
  209. if ( is_multisite() ) { 
  210. $table = $wpdb->sitemeta; 
  211. $column = 'meta_key'; 
  212. $key_column = 'meta_id'; 
  213. $value_column = 'meta_value'; 
  214.  
  215. $key = $this->identifier . '_batch_%'; 
  216.  
  217. $query = $wpdb->get_row( $wpdb->prepare( " 
  218. SELECT * 
  219. FROM {$table} 
  220. WHERE {$column} LIKE %s 
  221. ORDER BY {$key_column} ASC 
  222. LIMIT 1 
  223. ", $key ) ); 
  224.  
  225. $batch = new stdClass(); 
  226. $batch->key = $query->$column; 
  227. $batch->data = maybe_unserialize( $query->$value_column ); 
  228.  
  229. return $batch; 
  230.  
  231. /** 
  232. * Handle 
  233. * Pass each queue item to the task handler, while remaining 
  234. * within server memory and time limit constraints. 
  235. */ 
  236. protected function handle() { 
  237. $this->lock_process(); 
  238.  
  239. do { 
  240. $batch = $this->get_batch(); 
  241.  
  242. foreach ( $batch->data as $key => $value ) { 
  243. $task = $this->task( $value ); 
  244.  
  245. if ( false !== $task ) { 
  246. $batch->data[ $key ] = $task; 
  247. } else { 
  248. unset( $batch->data[ $key ] ); 
  249.  
  250. if ( $this->time_exceeded() || $this->memory_exceeded() ) { 
  251. // Batch limits reached. 
  252. break; 
  253.  
  254. // Update or delete current batch. 
  255. if ( ! empty( $batch->data ) ) { 
  256. $this->update( $batch->key, $batch->data ); 
  257. } else { 
  258. $this->delete( $batch->key ); 
  259. } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() ); 
  260.  
  261. $this->unlock_process(); 
  262.  
  263. // Start next batch or complete process. 
  264. if ( ! $this->is_queue_empty() ) { 
  265. $this->dispatch(); 
  266. } else { 
  267. $this->complete(); 
  268.  
  269. /** 
  270. * Memory exceeded 
  271. * Ensures the batch process never exceeds 90% 
  272. * of the maximum WordPress memory. 
  273. * @return bool 
  274. */ 
  275. protected function memory_exceeded() { 
  276. $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory 
  277. $current_memory = memory_get_usage( true ); 
  278. $return = false; 
  279.  
  280. if ( $current_memory >= $memory_limit ) { 
  281. $return = true; 
  282.  
  283. return apply_filters( $this->identifier . '_memory_exceeded', $return ); 
  284.  
  285. /** 
  286. * Get memory limit 
  287. * @return int 
  288. */ 
  289. protected function get_memory_limit() { 
  290. if ( function_exists( 'ini_get' ) ) { 
  291. $memory_limit = ini_get( 'memory_limit' ); 
  292. } else { 
  293. // Sensible default. 
  294. $memory_limit = '128M'; 
  295.  
  296. if ( ! $memory_limit || -1 === $memory_limit ) { 
  297. // Unlimited, set to 32GB. 
  298. $memory_limit = '32000M'; 
  299.  
  300. return intval( $memory_limit ) * 1024 * 1024; 
  301.  
  302. /** 
  303. * Time exceeded. 
  304. * Ensures the batch never exceeds a sensible time limit. 
  305. * A timeout limit of 30s is common on shared hosting. 
  306. * @return bool 
  307. */ 
  308. protected function time_exceeded() { 
  309. $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds 
  310. $return = false; 
  311.  
  312. if ( time() >= $finish ) { 
  313. $return = true; 
  314.  
  315. return apply_filters( $this->identifier . '_time_exceeded', $return ); 
  316.  
  317. /** 
  318. * Complete. 
  319. * Override if applicable, but ensure that the below actions are 
  320. * performed, or, call parent::complete(). 
  321. */ 
  322. protected function complete() { 
  323. // Unschedule the cron healthcheck. 
  324. $this->clear_scheduled_event(); 
  325.  
  326. /** 
  327. * Schedule cron healthcheck 
  328. * @access public 
  329. * @param mixed $schedules Schedules. 
  330. * @return mixed 
  331. */ 
  332. public function schedule_cron_healthcheck( $schedules ) { 
  333. $interval = apply_filters( $this->identifier . '_cron_interval', 5 ); 
  334.  
  335. if ( property_exists( $this, 'cron_interval' ) ) { 
  336. $interval = apply_filters( $this->identifier . '_cron_interval', $this->cron_interval_identifier ); 
  337.  
  338. // Adds every 5 minutes to the existing schedules. 
  339. $schedules[ $this->identifier . '_cron_interval' ] = array( 
  340. 'interval' => MINUTE_IN_SECONDS * $interval,  
  341. 'display' => sprintf( __( 'Every %d minutes', 'woocommerce' ), $interval ),  
  342. ); 
  343.  
  344. return $schedules; 
  345.  
  346. /** 
  347. * Handle cron healthcheck 
  348. * Restart the background process if not already running 
  349. * and data exists in the queue. 
  350. */ 
  351. public function handle_cron_healthcheck() { 
  352. if ( $this->is_process_running() ) { 
  353. // Background process already running. 
  354. exit; 
  355.  
  356. if ( $this->is_queue_empty() ) { 
  357. // No data to process. 
  358. $this->clear_scheduled_event(); 
  359. exit; 
  360.  
  361. $this->handle(); 
  362.  
  363. exit; 
  364.  
  365. /** 
  366. * Schedule event 
  367. */ 
  368. protected function schedule_event() { 
  369. if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { 
  370. wp_schedule_event( time(), $this->cron_interval_identifier, $this->cron_hook_identifier ); 
  371.  
  372. /** 
  373. * Clear scheduled event 
  374. */ 
  375. protected function clear_scheduled_event() { 
  376. $timestamp = wp_next_scheduled( $this->cron_hook_identifier ); 
  377.  
  378. if ( $timestamp ) { 
  379. wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); 
  380.  
  381. /** 
  382. * Cancel Process 
  383. * Stop processing queue items, clear cronjob and delete batch. 
  384. */ 
  385. public function cancel_process() { 
  386. if ( ! $this->is_queue_empty() ) { 
  387. $batch = $this->get_batch(); 
  388.  
  389. $this->delete( $batch->key ); 
  390.  
  391. wp_clear_scheduled_hook( $this->cron_hook_identifier ); 
  392.  
  393.  
  394. /** 
  395. * Task 
  396. * Override this method to perform any actions required on each 
  397. * queue item. Return the modified item for further processing 
  398. * in the next pass through. Or, return false to remove the 
  399. * item from the queue. 
  400. * @param mixed $item Queue item to iterate over. 
  401. * @return mixed 
  402. */ 
  403. abstract protected function task( $item ); 
  404.