WordPress.org

Make WordPress Core

Opened 8 months ago

#46465 new enhancement

Implement logic that performs PHP code after connection with client is closed

Reported by: umchal Owned by:
Milestone: Awaiting Review Priority: normal
Severity: normal Version:
Component: General Keywords:
Focuses: Cc:
PR Number:

Description

Hi there,

It would be great if this can be implemented in the core.

When I write plugins, it is almost unavoidable to perform extra database queries like accessing a custom database table, updating rows, and scheduling WP Cron actions etc. The problem is that it affects the page load time.

I've been wondering how this can be avoided and how the impact on the performance can be made minimum from the user experience perspective.

And I came up with a little technique using header('Content-Length: n') and header('Connection: close' ). Here is a working example plugin.

<?php
/**
 * Plugin Name: Test - HTTP Connection Close
 */

class HTTPConnectionClose {

    public function __construct() {

        add_action( 'init', array( $this, 'startCapturing' ) );
        add_action( 'shutdown', array( $this, 'endCapturing' ), PHP_INT_MIN ); 

    }

    public function startCapturing() {
         ob_start();
    }

    /**
     * @remark      Should be called before `wp_ob_end_flush_all()`.
     */
    public function endCapturing() {

        $_iOutputBufferLength = ( integer ) ob_get_length();
        if ( ! headers_sent() ) {
            @header('Content-Length: ' . $_iOutputBufferLength ) ;
            @header('Connection: close' );
        }
        if ( $_iOutputBufferLength ) {
            ob_end_flush();
        }

    }

}
new HTTPConnectionClose;

Here are some examples that illustrate the benefits of this. You can add this code at the bottom of the above example plugin.

class Test_HTTPConnectionClose {

    public function __construct() {

        add_action( 'shutdown', array( $this, 'testHeavyTask' ), PHP_INT_MAX );
        add_action( 'plugins_loaded', array( $this, 'testWPCronCheck' ) );
        add_action( 'shutdown', array( $this, 'testWPCronSchedule' ) );
        add_action( 'test_custom_action', array( $this, 'testCustomCronTask' ) );

    }

    public function testCustomCronTask() {
        error_log( __METHOD__  . ': doing custom cron task.' );
    }

    /**
     * This causes a couple of database queries. But with the above technique, this will not be seen during the page load.
     */
    public function testWPCronSchedule() {
        $_aArguments  = array();
        $_sActionName = 'test_custom_action';
        if ( wp_next_scheduled( $_sActionName, $_aArguments ) ) {
            return;
        }
        wp_schedule_single_event( time() + 5, $_sActionName, $_aArguments );
    }

    /**
     * Checks registered WP Cron actions in the `shutdown` instead of `init` action
     * to save a little load time in every single page load.
     */
    public function testWPCronCheck() {
        remove_action( 'init', 'wp_cron' );
        if ( ! defined( 'DOING_CRON' ) ) {
                add_action( 'shutdown', 'wp_cron' );
        }
    }

    /**
     * Simulates a heavy task which consumes 10 seconds. But with the above technique, the visitor do not feel any delay caused by this.
     */
    public function testHeavyTask() {
        sleep( 10 ); // sleep 10 seconds
        error_log( 'done a heavy task which takes 10 seconds.' );
    }

}
new Test_HTTPConnectionClose;

As you can see with the above testHeavyTask() method, any heavy sub-routines can be done in the shutdown action. Say, your plugin generates outputs and has a caching mechanism that schedules a task and calls wp-cron.php when the output is expired. With this, even scheduling a WP Cron task will be no longer needed. The plugin can update the cache in the shutdown action within the page load. This saves extra page load and also helps avoid PHP timeout issues in wp-cron.php due to too many registered WP Cron actions. Needless to say, this helps for sites disabling WP Cron.

It's very short and should be technically feasible to implement. Not sure any side effects with this yet but maybe you guys can tell.

Change History (0)

Note: See TracTickets for help on using tickets.