Skip to content

Commit e23a02a

Browse files
dsaspeterwilsonccchriszaratealecgeatchesalecgeatches
authored
Backport RTC fixes from GB trunk to GB 22.8 for a point release (#76834)
* Real Time Collaboration: Introduce filters for the polling intervals. (#76518) * Introduce filters for the RTC polling intervals. Introduces four filters for the polling intervals in the real time collaboration client: * `sync.pollingManager.pollingIntervalNoCollaborators` the interval in milliseconds when there are no collaborators in the room * `sync.pollingManager.pollingIntervalWithCollaborators` the interval in milliseconds when there are collaborators in the room * Remove background tab interval filter * Match filter to constant name --------- Co-authored-by: peterwilsoncc <peterwilsoncc@git.wordpress.org> Co-authored-by: chriszarate <czarate@git.wordpress.org> * RTC: Fix RichTextData deserialization (#76607) * Add failing unit test for RichTextData conversion * Fix RichText attribute type via deserializeBlockAttributes() call in getPostChangesFromCRDTDoc() * Add test for nested RichText value * Modify deserializeAttributeValue() to recurse arrays and object, using block schema for deeply nested strings * Remove deserializeBlockAttributeValues to avoid 3 different deserialization functions * Add factory wrapper for rich text cache, testing Co-authored-by: alecgeatches <alecgeatches@git.wordpress.org> Co-authored-by: chriszarate <czarate@git.wordpress.org> Co-authored-by: ingeniumed <ingeniumed@git.wordpress.org> * RTC: Scroll to collaborator on click (#76561) * Add createCursorRegistry factory and use it for scrolling to cursors * Use createCursorRegistry for scrolling * Localize spoken scroll to cursor announcement * Merge two matching conditionals * Use removeAll in cursor registry ------ Co-authored-by: maxschmeling <maxschmeling@git.wordpress.org> Co-authored-by: ingeniumed <ingeniumed@git.wordpress.org> * RTC: Change RTC option name (#76643) * Change RTC option name * Add backport changelog * Restore option migration * Cover previous option name in setCollaboration * Respect previous value in setting injection * Update migration const * Temporary bridge Co-authored-by: chriszarate <czarate@git.wordpress.org> Co-authored-by: sc0ttkclark <sc0ttkclark@git.wordpress.org> Co-authored-by: maxschmeling <maxschmeling@git.wordpress.org> Co-authored-by: ingeniumed <ingeniumed@git.wordpress.org> * RTC: Backport race condition fix (#76649) * Backport race condition fix * Add backport changelog entry * RTC: Increase polling intervals, increase polling on primary room only (#76704) * Bump polling intervals up by 4x * Reset `hasCollaborators` flag on awareness check * Use the first registered room as the primaryRoom, and only pay attention to awareness results from that entity to avoid poll increases from shared entities like categores * Simplify primaryRoom and enforceConnectionLimit * Fix the collaboration utility setup for collaboration tests --------- Co-authored-by: alecgeatches <alecgeatches@git.wordpress.org> Co-authored-by: ingeniumed <ingeniumed@git.wordpress.org> Co-authored-by: chriszarate <czarate@git.wordpress.org> Co-authored-by: t-hamano <wildworks@git.wordpress.org> * RTC: Use activation hook to enable RTC by default (#76736) * Use activation hook to enable RTC by default * Fix plugin file reference * Directly hook into the `activate_` function for enabling collaboration * Add PR backport for opt-in change --------- Co-authored-by: Alec Geatches <alec.geatches@automattic.com> * RTC: Add E2E "stress test" with complex interactions (#76055) * Add RTC E2E "stress test" with complex interactions * Refactor collab utils to work for n users instead of just 2 users * Attempting to fix the timeout, as well as the text insertion being slightly off * insert the test rather than sequentially type it * Respect the collaborators limit of 3 * Switch the type to insertText * Remove refresh as that causes editor limit exceeded * Check Gamma's movement --------- Co-authored-by: chriszarate <czarate@git.wordpress.org> Co-authored-by: ingeniumed <ingeniumed@git.wordpress.org> Co-authored-by: shekharnwagh <shekharnwagh@git.wordpress.org> * RTC: Use prepared queries instead of `*_post_meta` functions (#76779) ## What? Use prepared queries instead of `*_post_meta` functions. ## Why? Backport of WordPress/wordpress-develop#11325. Prevents post and post meta cache invalidation when RTC requests are handled. - See: https://core.trac.wordpress.org/ticket/64696 - See: https://core.trac.wordpress.org/ticket/64916 * RTC: Remove stale wp_enable_real_time_collaboration option check (#76810) Co-authored-by: shekharnwagh <shekharnwagh@git.wordpress.org> Co-authored-by: chriszarate <czarate@git.wordpress.org> * RTC: Fix editor freeze when replacing code editor content (#76815) * Add failing performance test for huge code editor changes * Use diffLines() when strings are > 10_000 characters * Handle diffLines in diffWithCursor() when cursor information is sent. We don't care about the cursor for huge performance-sensitive updates * Explicitly use "primary" instead of "meta" key * Revert "Explicitly use "primary" instead of "meta" key" This reverts commit 33e7aba. * Fix paste keys for CI, lower timeouts * Remove console.log Co-authored-by: alecgeatches <alecgeatches@git.wordpress.org> Co-authored-by: chriszarate <czarate@git.wordpress.org> * Preferences: Hide collaboration options when RTC is not enabled (#76819) * Preferences: Hide collaboration options when RTC is not enabled Only show the "Show avatar in blocks" and "Show collaboration notifications" preference controls when real-time collaboration is actually enabled for the current post. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Preferences: Consolidate editorStore select call Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: chriszarate <czarate@git.wordpress.org> * Fix navigation block rendering unit test (#76685) * Fix navigation block rendering unit test * Remove semicolon Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: westonruter <westonruter@git.wordpress.org> * Fix navigation block unit test and e2e test (#76692) * Fix navigation block rendering unit test (second try) * Fix e2e test * Revert unncessary change --------- Co-authored-by: Peter Wilson <519727+peterwilsoncc@users.noreply.github.com> Co-authored-by: peterwilsoncc <peterwilsoncc@git.wordpress.org> Co-authored-by: chriszarate <czarate@git.wordpress.org> Co-authored-by: Alec Geatches <alec.geatches@automattic.com> Co-authored-by: alecgeatches <alecgeatches@git.wordpress.org> Co-authored-by: ingeniumed <ingeniumed@git.wordpress.org> Co-authored-by: Max Schmeling <max.schmeling@automattic.com> Co-authored-by: maxschmeling <maxschmeling@git.wordpress.org> Co-authored-by: Chris Zarate <chris.zarate@automattic.com> Co-authored-by: sc0ttkclark <sc0ttkclark@git.wordpress.org> Co-authored-by: t-hamano <wildworks@git.wordpress.org> Co-authored-by: shekharnwagh <shekharnwagh@git.wordpress.org> Co-authored-by: Shekhar Wagh <shekharnwagh@gmail.com> Co-authored-by: Aki Hamano <54422211+t-hamano@users.noreply.github.com> Co-authored-by: westonruter <westonruter@git.wordpress.org>
1 parent 8173777 commit e23a02a

32 files changed

Lines changed: 2763 additions & 247 deletions

File tree

backport-changelog/7.0/11289.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
https://github.com/WordPress/wordpress-develop/pull/11289
2+
3+
* https://github.com/WordPress/gutenberg/pull/76643
4+
* https://github.com/WordPress/gutenberg/pull/76736

backport-changelog/7.0/11292.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
https://github.com/WordPress/wordpress-develop/pull/11292
2+
3+
* https://github.com/WordPress/gutenberg/pull/76649

backport-changelog/7.0/11325.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
https://github.com/WordPress/wordpress-develop/pull/11325
2+
3+
* https://github.com/WordPress/gutenberg/pull/76779

lib/compat/wordpress-7.0/class-gutenberg-rest-autosaves-controller.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public function create_item( $request ) {
101101
* Load the real-time collaboration setting and, when enabled, ensure that an
102102
* an autosave revision is always targeted.
103103
*/
104-
$is_collaboration_enabled = get_option( 'wp_enable_real_time_collaboration' );
104+
$is_collaboration_enabled = get_option( 'wp_collaboration_enabled' );
105105

106106
if ( $is_draft && (int) $post->post_author === $user_id && ! $post_lock && ! $is_collaboration_enabled ) {
107107
/*

lib/compat/wordpress-7.0/class-wp-sync-post-meta-storage.php

Lines changed: 140 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ class WP_Sync_Post_Meta_Storage implements WP_Sync_Storage {
3232
* @since 7.0.0
3333
* @var string
3434
*/
35-
const AWARENESS_META_KEY = 'wp_sync_awareness';
35+
const AWARENESS_META_KEY = 'wp_sync_awareness_state';
3636

3737
/**
3838
* Meta key for sync updates.
3939
*
4040
* @since 7.0.0
4141
* @var string
4242
*/
43-
const SYNC_UPDATE_META_KEY = 'wp_sync_update';
43+
const SYNC_UPDATE_META_KEY = 'wp_sync_update_data';
4444

4545
/**
4646
* Cache of cursors by room.
@@ -71,75 +71,68 @@ class WP_Sync_Post_Meta_Storage implements WP_Sync_Storage {
7171
*
7272
* @since 7.0.0
7373
*
74+
* @global wpdb $wpdb WordPress database abstraction object.
75+
*
7476
* @param string $room Room identifier.
7577
* @param mixed $update Sync update.
7678
* @return bool True on success, false on failure.
7779
*/
7880
public function add_update( string $room, $update ): bool {
81+
global $wpdb;
82+
7983
$post_id = $this->get_storage_post_id( $room );
8084
if ( null === $post_id ) {
8185
return false;
8286
}
8387

84-
// Create an envelope and stamp each update to enable cursor-based filtering.
85-
$envelope = array(
86-
'timestamp' => $this->get_time_marker(),
87-
'value' => $update,
88+
// Use direct database operation to avoid cache invalidation performed by
89+
// post meta functions (`wp_cache_set_posts_last_changed()` and direct
90+
// `wp_cache_delete()` calls).
91+
return (bool) $wpdb->insert(
92+
$wpdb->postmeta,
93+
array(
94+
'post_id' => $post_id,
95+
'meta_key' => self::SYNC_UPDATE_META_KEY,
96+
'meta_value' => wp_json_encode( $update ),
97+
),
98+
array( '%d', '%s', '%s' )
8899
);
89-
90-
return (bool) add_post_meta( $post_id, self::SYNC_UPDATE_META_KEY, $envelope, false );
91100
}
92101

93102
/**
94-
* Retrieves all sync updates for a given room.
103+
* Gets awareness state for a given room.
95104
*
96105
* @since 7.0.0
97106
*
107+
* @global wpdb $wpdb WordPress database abstraction object.
108+
*
98109
* @param string $room Room identifier.
99-
* @return array<int, array{ timestamp: int, value: mixed }> Sync updates.
110+
* @return array<int, mixed> Awareness state.
100111
*/
101-
private function get_all_updates( string $room ): array {
102-
$this->room_cursors[ $room ] = $this->get_time_marker() - 100; // Small buffer to ensure consistency.
112+
public function get_awareness_state( string $room ): array {
113+
global $wpdb;
103114

104115
$post_id = $this->get_storage_post_id( $room );
105116
if ( null === $post_id ) {
106117
return array();
107118
}
108119

109-
$updates = get_post_meta( $post_id, self::SYNC_UPDATE_META_KEY, false );
110-
111-
if ( ! is_array( $updates ) ) {
112-
$updates = array();
113-
}
114-
115-
// Filter out any updates that don't have the expected structure.
116-
$updates = array_filter(
117-
$updates,
118-
static function ( $update ): bool {
119-
return is_array( $update ) && isset( $update['timestamp'], $update['value'] ) && is_int( $update['timestamp'] );
120-
}
120+
// Use direct database operation to avoid updating the post meta cache.
121+
// ORDER BY meta_id DESC ensures the latest row wins if duplicates exist
122+
// from a past race condition in set_awareness_state().
123+
$meta_value = $wpdb->get_var(
124+
$wpdb->prepare(
125+
"SELECT meta_value FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s ORDER BY meta_id DESC LIMIT 1",
126+
$post_id,
127+
self::AWARENESS_META_KEY
128+
)
121129
);
122130

123-
$this->room_update_counts[ $room ] = count( $updates );
124-
125-
return $updates;
126-
}
127-
128-
/**
129-
* Gets awareness state for a given room.
130-
*
131-
* @since 7.0.0
132-
*
133-
* @param string $room Room identifier.
134-
* @return array<int, mixed> Awareness state.
135-
*/
136-
public function get_awareness_state( string $room ): array {
137-
$post_id = $this->get_storage_post_id( $room );
138-
if ( null === $post_id ) {
131+
if ( null === $meta_value ) {
139132
return array();
140133
}
141134

142-
$awareness = get_post_meta( $post_id, self::AWARENESS_META_KEY, true );
135+
$awareness = json_decode( $meta_value, true );
143136

144137
if ( ! is_array( $awareness ) ) {
145138
return array();
@@ -153,27 +146,61 @@ public function get_awareness_state( string $room ): array {
153146
*
154147
* @since 7.0.0
155148
*
149+
* @global wpdb $wpdb WordPress database abstraction object.
150+
*
156151
* @param string $room Room identifier.
157152
* @param array<int, mixed> $awareness Serializable awareness state.
158153
* @return bool True on success, false on failure.
159154
*/
160155
public function set_awareness_state( string $room, array $awareness ): bool {
156+
global $wpdb;
157+
161158
$post_id = $this->get_storage_post_id( $room );
162159
if ( null === $post_id ) {
163160
return false;
164161
}
165162

166-
// update_post_meta returns false if the value is the same as the existing value.
167-
update_post_meta( $post_id, self::AWARENESS_META_KEY, $awareness );
168-
return true;
163+
// Use direct database operation to avoid cache invalidation performed by
164+
// post meta functions (`wp_cache_set_posts_last_changed()` and direct
165+
// `wp_cache_delete()` calls).
166+
//
167+
// If two concurrent requests both see no row and both INSERT, the
168+
// duplicate is harmless: get_awareness_state() reads the latest row
169+
// (ORDER BY meta_id DESC).
170+
$meta_id = $wpdb->get_var(
171+
$wpdb->prepare(
172+
"SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s ORDER BY meta_id DESC LIMIT 1",
173+
$post_id,
174+
self::AWARENESS_META_KEY
175+
)
176+
);
177+
178+
if ( $meta_id ) {
179+
return (bool) $wpdb->update(
180+
$wpdb->postmeta,
181+
array( 'meta_value' => wp_json_encode( $awareness ) ),
182+
array( 'meta_id' => $meta_id ),
183+
array( '%s' ),
184+
array( '%d' )
185+
);
186+
}
187+
188+
return (bool) $wpdb->insert(
189+
$wpdb->postmeta,
190+
array(
191+
'post_id' => $post_id,
192+
'meta_key' => self::AWARENESS_META_KEY,
193+
'meta_value' => wp_json_encode( $awareness ),
194+
),
195+
array( '%d', '%s', '%s' )
196+
);
169197
}
170198

171199
/**
172200
* Gets the current cursor for a given room.
173201
*
174202
* The cursor is set during get_updates_after_cursor() and represents the
175-
* point in time just before the updates were retrieved, with a small buffer
176-
* to ensure consistency.
203+
* highest meta_id seen for the room's sync updates.
177204
*
178205
* @since 7.0.0
179206
*
@@ -210,6 +237,8 @@ private function get_storage_post_id( string $room ): ?int {
210237
'post_status' => 'publish',
211238
'name' => $room_hash,
212239
'fields' => 'ids',
240+
'orderby' => 'ID',
241+
'order' => 'ASC',
213242
)
214243
);
215244

@@ -243,17 +272,6 @@ private function get_storage_post_id( string $room ): ?int {
243272
return null;
244273
}
245274

246-
/**
247-
* Gets the current time in milliseconds as a comparable time marker.
248-
*
249-
* @since 7.0.0
250-
*
251-
* @return int Current time in milliseconds.
252-
*/
253-
private function get_time_marker(): int {
254-
return (int) floor( microtime( true ) * 1000 );
255-
}
256-
257275
/**
258276
* Gets the number of updates stored for a given room.
259277
*
@@ -267,65 +285,103 @@ public function get_update_count( string $room ): int {
267285
}
268286

269287
/**
270-
* Retrieves sync updates from a room for a given client and cursor. Updates
271-
* from the specified client should be excluded.
288+
* Retrieves sync updates from a room after the given cursor.
272289
*
273290
* @since 7.0.0
274291
*
292+
* @global wpdb $wpdb WordPress database abstraction object.
293+
*
275294
* @param string $room Room identifier.
276-
* @param int $cursor Return updates after this cursor.
295+
* @param int $cursor Return updates after this cursor (meta_id).
277296
* @return array<int, mixed> Sync updates.
278297
*/
279298
public function get_updates_after_cursor( string $room, int $cursor ): array {
280-
$all_updates = $this->get_all_updates( $room );
281-
$updates = array();
299+
global $wpdb;
282300

283-
foreach ( $all_updates as $update ) {
284-
if ( $update['timestamp'] > $cursor ) {
285-
$updates[] = $update;
286-
}
301+
$post_id = $this->get_storage_post_id( $room );
302+
if ( null === $post_id ) {
303+
$this->room_cursors[ $room ] = 0;
304+
$this->room_update_counts[ $room ] = 0;
305+
return array();
287306
}
288307

289-
// Sort by timestamp to ensure order.
290-
usort(
291-
$updates,
292-
fn ( $a, $b ) => $a['timestamp'] <=> $b['timestamp']
308+
// Capture the current room state first so the returned cursor is race-safe.
309+
$stats = $wpdb->get_row(
310+
$wpdb->prepare(
311+
"SELECT COUNT(*) AS total_updates, COALESCE( MAX(meta_id), 0 ) AS max_meta_id FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s",
312+
$post_id,
313+
self::SYNC_UPDATE_META_KEY
314+
)
293315
);
294316

295-
return wp_list_pluck( $updates, 'value' );
317+
$total_updates = $stats ? (int) $stats->total_updates : 0;
318+
$max_meta_id = $stats ? (int) $stats->max_meta_id : 0;
319+
320+
$this->room_update_counts[ $room ] = $total_updates;
321+
$this->room_cursors[ $room ] = $max_meta_id;
322+
323+
if ( $max_meta_id <= $cursor ) {
324+
return array();
325+
}
326+
327+
$rows = $wpdb->get_results(
328+
$wpdb->prepare(
329+
"SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s AND meta_id > %d AND meta_id <= %d ORDER BY meta_id ASC",
330+
$post_id,
331+
self::SYNC_UPDATE_META_KEY,
332+
$cursor,
333+
$max_meta_id
334+
)
335+
);
336+
337+
if ( ! $rows ) {
338+
return array();
339+
}
340+
341+
$updates = array();
342+
foreach ( $rows as $row ) {
343+
$decoded = json_decode( $row->meta_value, true );
344+
if ( null !== $decoded ) {
345+
$updates[] = $decoded;
346+
}
347+
}
348+
349+
return $updates;
296350
}
297351

298352
/**
299353
* Removes updates from a room that are older than the given cursor.
300354
*
301355
* @since 7.0.0
302356
*
357+
* @global wpdb $wpdb WordPress database abstraction object.
358+
*
303359
* @param string $room Room identifier.
304-
* @param int $cursor Remove updates with markers < this cursor.
360+
* @param int $cursor Remove updates with meta_id < this cursor.
305361
* @return bool True on success, false on failure.
306362
*/
307363
public function remove_updates_before_cursor( string $room, int $cursor ): bool {
364+
global $wpdb;
365+
308366
$post_id = $this->get_storage_post_id( $room );
309367
if ( null === $post_id ) {
310368
return false;
311369
}
312370

313-
$all_updates = $this->get_all_updates( $room );
371+
$deleted_rows = $wpdb->query(
372+
$wpdb->prepare(
373+
"DELETE FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key = %s AND meta_id < %d",
374+
$post_id,
375+
self::SYNC_UPDATE_META_KEY,
376+
$cursor
377+
)
378+
);
314379

315-
// Remove all updates for the room and re-store only those that are newer than the cursor.
316-
if ( ! delete_post_meta( $post_id, self::SYNC_UPDATE_META_KEY ) ) {
380+
if ( false === $deleted_rows ) {
317381
return false;
318382
}
319383

320-
// Re-store envelopes directly to avoid double-wrapping by add_update().
321-
$add_result = true;
322-
foreach ( $all_updates as $envelope ) {
323-
if ( $add_result && $envelope['timestamp'] >= $cursor ) {
324-
$add_result = (bool) add_post_meta( $post_id, self::SYNC_UPDATE_META_KEY, $envelope, false );
325-
}
326-
}
327-
328-
return $add_result;
384+
return true;
329385
}
330386
}
331387
}

0 commit comments

Comments
 (0)