Skip to content

Commit 26a7110

Browse files
committed
Migration: Re-use async batch infrastructure
1 parent 1810c10 commit 26a7110

File tree

6 files changed

+79
-162
lines changed

6 files changed

+79
-162
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1919

2020
* Outbox now precesses the first batch of followers right away to avoid delays in processing new Activities.
2121
* Post bulk edits no longer create Outbox items, unless author or post status change.
22+
* Version upgrade routines now use the async_batch infrastructure to process upgrades.
2223

2324
### Fixed
2425

includes/class-migration.php

Lines changed: 9 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,6 @@ class Migration {
2222
* Initialize the class, registering WordPress hooks.
2323
*/
2424
public static function init() {
25-
\add_action( 'activitypub_migrate', array( self::class, 'async_migration' ) );
26-
\add_action( 'activitypub_upgrade', array( self::class, 'async_upgrade' ), 10, 99 );
27-
\add_action( 'activitypub_update_comment_counts', array( self::class, 'update_comment_counts' ), 10, 2 );
28-
2925
self::maybe_migrate();
3026
}
3127

@@ -136,13 +132,12 @@ public static function maybe_migrate() {
136132
$version_from_db = ACTIVITYPUB_PLUGIN_VERSION;
137133
}
138134

139-
// Schedule the async migration.
140-
if ( ! \wp_next_scheduled( 'activitypub_migrate', $version_from_db ) ) {
141-
\wp_schedule_single_event( \time(), 'activitypub_migrate', array( $version_from_db ) );
142-
}
143135
if ( \version_compare( $version_from_db, '0.17.0', '<' ) ) {
144136
self::migrate_from_0_16();
145137
}
138+
if ( \version_compare( $version_from_db, '1.0.0', '<' ) ) {
139+
\wp_schedule_single_event( \time(), 'activitypub_async_batch', array( array( self::class, 'migrate_from_0_17' ) ) );
140+
}
146141
if ( \version_compare( $version_from_db, '1.3.0', '<' ) ) {
147142
self::migrate_from_1_2_0();
148143
}
@@ -162,7 +157,7 @@ public static function maybe_migrate() {
162157
self::migrate_to_4_1_0();
163158
}
164159
if ( \version_compare( $version_from_db, '4.5.0', '<' ) ) {
165-
\wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, 'activitypub_update_comment_counts' );
160+
\wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, 'activitypub_async_batch', array( array( self::class, 'update_comment_counts' ) ) );
166161
}
167162
if ( \version_compare( $version_from_db, '4.7.1', '<' ) ) {
168163
self::migrate_to_4_7_1();
@@ -175,8 +170,8 @@ public static function maybe_migrate() {
175170
}
176171
if ( \version_compare( $version_from_db, '5.0.0', '<' ) ) {
177172
Scheduler::register_schedules();
178-
\wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'create_post_outbox_items' ) );
179-
\wp_schedule_single_event( \time() + 15, 'activitypub_upgrade', array( 'create_comment_outbox_items' ) );
173+
\wp_schedule_single_event( \time(), 'activitypub_async_batch', array( array( self::class, 'create_post_outbox_items' ) ) );
174+
\wp_schedule_single_event( \time() + 15, 'activitypub_async_batch', array( array( self::class, 'create_comment_outbox_items' ) ) );
180175
add_action( 'init', 'flush_rewrite_rules', 20 );
181176
}
182177
if ( \version_compare( $version_from_db, '5.2.0', '<' ) ) {
@@ -208,49 +203,6 @@ public static function maybe_migrate() {
208203
self::unlock();
209204
}
210205

211-
/**
212-
* Asynchronously migrates the database structure.
213-
*
214-
* @param string $version_from_db The version from which to migrate.
215-
*/
216-
public static function async_migration( $version_from_db ) {
217-
if ( \version_compare( $version_from_db, '1.0.0', '<' ) ) {
218-
self::migrate_from_0_17();
219-
}
220-
}
221-
222-
/**
223-
* Asynchronously runs upgrade routines.
224-
*
225-
* @param callable $callback Callable upgrade routine. Must be a method of this class.
226-
* @params mixed ...$args Optional. Parameters that get passed to the callback.
227-
*/
228-
public static function async_upgrade( $callback ) {
229-
$args = \func_get_args();
230-
231-
// Bail if the existing lock is still valid.
232-
if ( self::is_locked() ) {
233-
\wp_schedule_single_event( time() + MINUTE_IN_SECONDS, 'activitypub_upgrade', $args );
234-
return;
235-
}
236-
237-
self::lock();
238-
239-
$callback = array_shift( $args ); // Remove $callback from arguments.
240-
$next = \call_user_func_array( array( self::class, $callback ), $args );
241-
242-
self::unlock();
243-
244-
if ( ! empty( $next ) ) {
245-
// Schedule the next run, adding the result to the arguments.
246-
\wp_schedule_single_event(
247-
\time() + 30,
248-
'activitypub_upgrade',
249-
\array_merge( array( $callback ), \array_values( $next ) )
250-
);
251-
}
252-
}
253-
254206
/**
255207
* Updates the custom template to use shortcodes instead of the deprecated templates.
256208
*/
@@ -492,25 +444,11 @@ public static function migrate_to_4_7_2() {
492444
* @see Comment::pre_wp_update_comment_count_now()
493445
* @param int $batch_size Optional. Number of posts to process per batch. Default 100.
494446
* @param int $offset Optional. Number of posts to skip. Default 0.
447+
* @return array|null Array with batch size and offset if there are more posts to process, null otherwise.
495448
*/
496449
public static function update_comment_counts( $batch_size = 100, $offset = 0 ) {
497450
global $wpdb;
498451

499-
// Bail if the existing lock is still valid.
500-
if ( self::is_locked() ) {
501-
\wp_schedule_single_event(
502-
time() + ( 5 * MINUTE_IN_SECONDS ),
503-
'activitypub_update_comment_counts',
504-
array(
505-
'batch_size' => $batch_size,
506-
'offset' => $offset,
507-
)
508-
);
509-
return;
510-
}
511-
512-
self::lock();
513-
514452
Comment::register_comment_types();
515453
$comment_types = Comment::get_comment_type_slugs();
516454
$type_inclusion = "AND comment_type IN ('" . implode( "','", $comment_types ) . "')";
@@ -531,17 +469,10 @@ public static function update_comment_counts( $batch_size = 100, $offset = 0 ) {
531469

532470
if ( count( $post_ids ) === $batch_size ) {
533471
// Schedule next batch.
534-
\wp_schedule_single_event(
535-
time() + MINUTE_IN_SECONDS,
536-
'activitypub_update_comment_counts',
537-
array(
538-
'batch_size' => $batch_size,
539-
'offset' => $offset + $batch_size,
540-
)
541-
);
472+
return array( $batch_size, $offset + $batch_size );
542473
}
543474

544-
self::unlock();
475+
return null;
545476
}
546477

547478
/**

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ For reasons of data protection, it is not possible to see the followers of other
138138
* Added: Show metadata in the New Follower E-Mail.
139139
* Changed: Outbox now precesses the first batch of followers right away to avoid delays in processing new Activities.
140140
* Changed: Post bulk edits no longer create Outbox items, unless author or post status change.
141+
* Changed: Version upgrade routines now use the async_batch infrastructure to process upgrades.
141142
* Fixed: The Outbox purging routine no longer is limited to deleting 5 items at a time.
142143
* Fixed: An issue where the outbox could not send object types other than `Base_Object` (introduced in 5.0.0).
143144
* Fixed: Ellipses now display correctly in notification emails for Likes and Reposts.

tests/includes/class-test-migration.php

Lines changed: 5 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Activitypub\Collection\Outbox;
1111
use Activitypub\Migration;
1212
use Activitypub\Comment;
13+
use Activitypub\Scheduler;
1314

1415
/**
1516
* Test class for Activitypub Migrate.
@@ -390,12 +391,12 @@ public function test_update_comment_counts_with_lock() {
390391
Comment::register_comment_types();
391392

392393
// Create test comments.
393-
$post_id = $this->factory->post->create(
394+
$post_id = self::factory()->post->create(
394395
array(
395396
'post_author' => 1,
396397
)
397398
);
398-
$comment_id = $this->factory->comment->create(
399+
$comment_id = self::factory()->comment->create(
399400
array(
400401
'comment_post_ID' => $post_id,
401402
'comment_approved' => '1',
@@ -413,41 +414,6 @@ public function test_update_comment_counts_with_lock() {
413414
wp_delete_post( $post_id, true );
414415
}
415416

416-
/**
417-
* Test update_comment_counts() with existing valid lock.
418-
*
419-
* @covers ::update_comment_counts
420-
*/
421-
public function test_update_comment_counts_with_existing_valid_lock() {
422-
// Register comment types.
423-
Comment::register_comment_types();
424-
425-
// Set a lock.
426-
Migration::lock();
427-
428-
Migration::update_comment_counts( 10, 0 );
429-
430-
// Verify a scheduled event was created.
431-
$next_scheduled = wp_next_scheduled(
432-
'activitypub_update_comment_counts',
433-
array(
434-
'batch_size' => 10,
435-
'offset' => 0,
436-
)
437-
);
438-
$this->assertNotFalse( $next_scheduled );
439-
440-
// Clean up.
441-
delete_option( 'activitypub_migration_lock' );
442-
wp_clear_scheduled_hook(
443-
'activitypub_update_comment_counts',
444-
array(
445-
'batch_size' => 10,
446-
'offset' => 0,
447-
)
448-
);
449-
}
450-
451417
/**
452418
* Test create post outbox items.
453419
*
@@ -519,43 +485,15 @@ public function test_create_outbox_items_batching() {
519485
$this->assertEquals( 5, count( $outbox_items ) );
520486
}
521487

522-
/**
523-
* Test async upgrade functionality.
524-
*
525-
* @covers ::async_upgrade
526-
* @covers ::lock
527-
* @covers ::unlock
528-
* @covers ::create_post_outbox_items
529-
*/
530-
public function test_async_upgrade() {
531-
// Test that lock prevents simultaneous upgrades.
532-
Migration::lock();
533-
Migration::async_upgrade( 'create_post_outbox_items' );
534-
$scheduled = \wp_next_scheduled( 'activitypub_upgrade', array( 'create_post_outbox_items' ) );
535-
$this->assertNotFalse( $scheduled );
536-
Migration::unlock();
537-
538-
// Test scheduling next batch when callback returns more work.
539-
Migration::async_upgrade( 'create_post_outbox_items', 1, 0 ); // Small batch size to force multiple batches.
540-
$scheduled = \wp_next_scheduled( 'activitypub_upgrade', array( 'create_post_outbox_items', 1, 1 ) );
541-
$this->assertNotFalse( $scheduled );
542-
543-
// Test no scheduling when callback returns null (no more work).
544-
Migration::async_upgrade( 'create_post_outbox_items', 100, 1000 ); // Large offset to ensure no posts found.
545-
$this->assertFalse(
546-
\wp_next_scheduled( 'activitypub_upgrade', array( 'create_post_outbox_items', 100, 1100 ) )
547-
);
548-
}
549-
550488
/**
551489
* Test async upgrade with multiple arguments.
552490
*
553491
* @covers ::async_upgrade
554492
*/
555493
public function test_async_upgrade_multiple_args() {
556494
// Test that multiple arguments are passed correctly.
557-
Migration::async_upgrade( 'update_comment_counts', 50, 100 );
558-
$scheduled = \wp_next_scheduled( 'activitypub_upgrade', array( 'update_comment_counts', 50, 150 ) );
495+
Scheduler::async_batch( array( Migration::class, 'update_comment_counts' ), 50, 100 );
496+
$scheduled = \wp_next_scheduled( 'activitypub_async_batch', array( array( Migration::class, 'update_comment_counts' ), 50, 150 ) );
559497
$this->assertFalse( $scheduled, 'Should not schedule next batch when no comments found' );
560498
}
561499

tests/includes/class-test-scheduler.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
namespace Activitypub\Tests;
99

10+
use Activitypub\Comment;
11+
use Activitypub\Migration;
1012
use Activitypub\Scheduler;
1113
use Activitypub\Collection\Outbox;
1214
use Activitypub\Activity\Base_Object;
@@ -282,4 +284,65 @@ public function test_purge_outbox_with_different_purge_days() {
282284
// Verify posts are deleted.
283285
$this->assertEquals( 0, wp_count_posts( Outbox::POST_TYPE )->publish );
284286
}
287+
288+
/**
289+
* Test update_comment_counts() with existing valid lock.
290+
*
291+
* @covers ::lock
292+
* @covers ::async_batch
293+
*/
294+
public function test_update_comment_counts_with_existing_valid_lock() {
295+
// Register comment types.
296+
Comment::register_comment_types();
297+
298+
$callback = array( Migration::class, 'update_comment_counts' );
299+
$key = \md5( \serialize( $callback ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
300+
301+
// Set a lock.
302+
Scheduler::lock( $key );
303+
304+
Scheduler::async_batch( $callback, 10, 0 );
305+
306+
// Verify a scheduled event was created.
307+
$next_scheduled = wp_next_scheduled( 'activitypub_async_batch', array( $callback, 10, 0 ) );
308+
$this->assertNotFalse( $next_scheduled );
309+
310+
// Clean up.
311+
delete_option( 'activitypub_migration_lock' );
312+
wp_clear_scheduled_hook( 'activitypub_async_batch', array( $callback, 10, 0 ) );
313+
}
314+
315+
/**
316+
* Test async upgrade functionality.
317+
*
318+
* @covers ::async_batch
319+
* @covers ::lock
320+
* @covers ::unlock
321+
*/
322+
public function test_async_upgrade() {
323+
$callback = array( Migration::class, 'create_post_outbox_items' );
324+
$key = \md5( \serialize( $callback ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
325+
326+
// Test that lock prevents simultaneous upgrades.
327+
Scheduler::lock( $key );
328+
Scheduler::async_batch( $callback );
329+
$scheduled = \wp_next_scheduled( 'activitypub_async_batch', array( $callback ) );
330+
$this->assertNotFalse( $scheduled );
331+
Scheduler::unlock( $key );
332+
333+
\remove_action( 'transition_post_status', array( \Activitypub\Scheduler\Post::class, 'schedule_post_activity' ), 33 );
334+
self::factory()->post->create( array( 'meta_input' => array( 'activitypub_status' => 'federated' ) ) );
335+
\add_action( 'transition_post_status', array( \Activitypub\Scheduler\Post::class, 'schedule_post_activity' ), 33, 3 );
336+
337+
// Test scheduling next batch when callback returns more work.
338+
Scheduler::async_batch( $callback, 1, 0 ); // Small batch size to force multiple batches.
339+
$scheduled = \wp_next_scheduled( 'activitypub_async_batch', array( $callback, 1, 1 ) );
340+
$this->assertNotFalse( $scheduled );
341+
342+
// Test no scheduling when callback returns null (no more work).
343+
Scheduler::async_batch( $callback, 100, 1000 ); // Large offset to ensure no posts found.
344+
$this->assertFalse(
345+
\wp_next_scheduled( 'activitypub_async_batch', array( $callback, 100, 1100 ) )
346+
);
347+
}
285348
}

tests/includes/collection/class-test-followers.php

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -358,23 +358,6 @@ public function test_add_duplicate_follower() {
358358
$this->assertCount( 1, $meta );
359359
}
360360

361-
/**
362-
* Tests scheduling of migration.
363-
*
364-
* @covers ::maybe_migrate
365-
*/
366-
public function test_migration_scheduling() {
367-
update_option( 'activitypub_db_version', '0.0.1' );
368-
369-
\Activitypub\Migration::maybe_migrate();
370-
371-
$schedule = \wp_next_scheduled( 'activitypub_migrate', array( '0.0.1' ) );
372-
$this->assertNotFalse( $schedule );
373-
374-
// Clean up.
375-
delete_option( 'activitypub_db_version' );
376-
}
377-
378361
/**
379362
* Data provider for migration test scenarios.
380363
*

0 commit comments

Comments
 (0)