-
Notifications
You must be signed in to change notification settings - Fork 328
Closed
Labels
P0High priorityHigh priorityTeam SIssues for Squad 1Issues for Squad 1Type: EnhancementImprovement of an existing featureImprovement of an existing feature
Description
Feature Description
- Implement max_execution time tracking
- Scafold single scheduled event - report generation to be created in "Worker" scheduled event email report generation and email sending logic #11278
Do not alter or remove anything below. The following sections will be managed by moderators only.
Acceptance Criteria
- A "Worker" scheduled event is defined, which is spawned by the "Initiator" scheduler (11546).
- The "Worker" sets
googlesitekit_email_reporting_worker_lock_{$frequency}transient with timestamp (scoped to the current frequency) expiring in1minwhen starting. - The "Worker" event tracks its execution time using the
WP_START_TIMESTAMPconstant. - The "Worker" must exit immediately if the current execution time reaches within 10 seconds of the PHP
max_executiontime to prevent interruption, even if there are more reports to send. - The "Worker" event must exit immediately if it is triggered 24 hours after the initial trigger date of the frequency to prevent infinite running.
- When the "Worker" event runs, it first retrieves all CPT posts (
googlesitekit_email_log) for its batch ID to get user IDs and statuses. - The "Worker" must exit immediately without spawning a follow-up event if all user reports in the batch are marked as
sentorfailed(and the attempt count for each failed report is >= 3). - If the exit conditions are not met, the "Worker" spawns a follow-up "Worker" scheduled event for 11 minutes from current one.
- For each user ID being processed in the batch, the "Worker":
- Increments the
send_attemptspost meta by 1. - Rest of the logic will be implemented in "Worker" scheduled event email report generation and email sending logic #11278
- Increments the
Implementation Brief
Add includes\Core\Email_Reporting\Max_Execution_Limiter.php class
- It should accept
$max_execution_timeargument in the constructor and store it in the class property, treat any 0/negative/falsy result as 30 (for default fallback) - Add
should_abort( $initiator_timestamp ):- It checks whether the current time (
microtime( true )) has passed either the execution deadline derived fromWP_START_TIMESTAMPand the configured max-execution budget (minus a 10-second safety window) (eqWP_START_TIMESTAMP + $this->resolve_budget_seconds() - 10) or the initiator timestamp plus 24 hours - to prevent infinite loops we limit it to 24h (eq$initiator_timestamp + DAY_IN_SECONDS)- If either bound is met it returns
trueso the worker exits.
- If either bound is met it returns
- It checks whether the current time (
Add includes\Core\Email_Reporting\Email_Log_Batch_Query
- Define constant
MAX_ATTEMPTS = 3 - Add
get_pending_ids( $batch_id, $max_attempts )- Resolve the batch via a single
WP_Query: - Limit it to the
Email_Log::POST_TYPEpost type - Restrict
post_statusto the three internal statuses ( e.g.Email_Log::STATUS_SCHEDULED,STATUS_SENT,STATUS_FAILED) - Filter by a
meta_queryonbatch_id - Request IDs only, and disable unnecessary caches (
no_found_rows,update_post_meta_cache,update_post_term_cache). - Walk the resulting IDs, reading status via
get_post_status()and attempts viaget_post_meta()track whether every post iscompleteand build a list of “still eligible” IDs (scheduledorfailedwith attempts <self::MAX_ATTEMPTS) in$pending_ids.- Return resulting array
$pending_ids
- Return resulting array
- Resolve the batch via a single
- Add
is_complete- Return the result of
empty( $this->get_pending_ids )
- Return the result of
- Add
increment_attempt( $post_id )- Retrieve the post by
$post_idand update it'sEmail_Log::META_SEND_ATTEMPTSmeta with$current_attempts + 1
- Retrieve the post by
- Add
update_status( $post_id, $status )- Retrieve the post by
$post_idand update it's status with passed$statusargument
- Retrieve the post by
Update Google\Site_Kit\Core\Email_Reporting\Email_Reporting
- In
register()method:- Instantiate
Max_Execution_Limiter,Email_Log_Batch_QueryandWorker_Task - Pass
(int) ini_get( 'max_execution_time' )to theMax_Execution_Limiter - Hook into the
Email_Reporting_Scheduler::ACTION_WORKERand invokehandle_callback_actionfromWorker_Taskinstance.
- Instantiate
Add Core\Email_Reporting\Worker_Task
- It accepts instances of
Email_Log_Batch_QueryandEmail_Reporting_Schedulerin the constructor and sets them as a class property. - Add
handle_worker_action( $batch_id, $frequency, $initiator_timestamp )- Store the transient name in a var, eq
$transient_name = googlesitekit_email_reporting_worker_lock_{$frequency}; - At the start of the function guard against concurrent runs: if
get_transient( $transient_name )is truthy, return immediately. Otherwise set it viaset_transient( $transient_name, time(), MINUTE_IN_SECONDS )and wrap the rest of the method in atry { ... } finally { delete_transient( $transient_name ) }to guarantee cleanup. - Immediately after setting the lock, call guard -
Max_Execution_Limiter::should_abort( $initiator_timestamp )and bail out if it returns true. - Exit immediately if the batch query
Email_Log_Batch_Query::is_complete()returntrue - Otherwise, resolve the batch IDs via
Email_Log_Batch_Query::get_pending_ids() - Schedule the follow-up worker (
Email_Reporting_Scheduler::schedule_worker()forwarding the same parameters passed to the current worker) - Then use the guard
Max_Execution_Limiter::should_abort( $initiator_timestamp )- return early if it istrue. - Otherwise start the loop on returned
$pending_idslist:- Walk the post IDs one by one, calling the guard
should_abort()and return early if it istruebefore each item. - For each post increment its attempt via
Email_Log_Batch_Query::increment_attempt(). - After the loop, and before rest of the logic is implemented in follow up issue, run a
should_abort()guard again and return early if it istrue
- Walk the post IDs one by one, calling the guard
- Store the transient name in a var, eq
Test Coverage
- Add tests for
tests/phpunit/unit/Core/Email_Reporting/Max_Execution_LimiterTest.php:- Verify that it caches the passed limit (
$max_execution_time) and falls back to 30 when$max_execution_timeis 0/false. - Verify that
should_abort()returns false when within both limits and true when either the execution deadline or the 24h initiator window has passed.
- Verify that it caches the passed limit (
- Add tests for
Worker_Taskclass- Test that when no lock is set the worker acquires and clears the lock even on early returns; when the lock already exists it exits immediately.
- Test that Worker exits without rescheduling when the batch query returns no posts or every post is sent/failed with attempts ≥ 3.
- Test that with pending posts present, the worker schedules a follow-up event carrying the same arguments.
- Verify that pending posts have their
send_attemptsmeta incremented, while posts already maxed on attempts remain unchanged.
- Add tests for
Email_Log_Batch_Query:- Verify posts are correctly returned - filter should not return posts under
sentstatus is_completereturnstrueas expected when there are pending IDs otherwise it returnfalseincrement_attemptandupdate_statusare correctly updating the posts with meta/status
- Verify posts are correctly returned - filter should not return posts under
QA Brief
- Enable the feature flag and install a plugin that lets you see WP scheduled events (like WP Crontrol).
- Toggle the feature in the admin settings, when the feature is enabled you should see registered events for the initiator task (filter the hook names by
googlesitekit):
Choose any of the initiator tasks, and click run now option on that event
- Page will reload, at the top of the events list should be a new event that is scheduled for
60/59s-googlesitekit_email_reporting_worker. It should show same frequency in the parameters as the one for initiator you choose to run
Changelog entry
- Implement “Worker” scheduled events for Email Reporting.
Metadata
Metadata
Assignees
Labels
P0High priorityHigh priorityTeam SIssues for Squad 1Issues for Squad 1Type: EnhancementImprovement of an existing featureImprovement of an existing feature