Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/reference-guides/data/data-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,7 @@ _Parameters_
- _options_ `Object`: Saving options.
- _options.isAutosave_ `[boolean]`: Whether this is an autosave.
- _options.\_\_unstableFetch_ `[Function]`: Internal use only. Function to call instead of `apiFetch()`. Must return a promise.
- _options.\_\_unstableSkipSyncUpdate_ `[boolean]`: Whether to skip applying the full save response to synced entities.
- _options.throwOnError_ `[boolean]`: If false, this action suppresses all the exceptions. Defaults to false.

### undo
Expand Down
61 changes: 55 additions & 6 deletions lib/compat/wordpress-7.1/class-wp-http-polling-sync-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ class WP_HTTP_Polling_Sync_Server {
*/
const MAX_BODY_SIZE = 16 * MB_IN_BYTES;

/**
* Maximum target size (in bytes) of the response body.
*
* @since 7.1.0
* @var int
*/
const MAX_RESPONSE_BODY_SIZE = 16 * MB_IN_BYTES;

/**
* Per-room headroom for response metadata outside returned update rows.
*
* @since 7.1.0
* @var int
*/
const RESPONSE_BODY_ROOM_HEADROOM = 8 * 1024;

/**
* Maximum number of rooms allowed per request.
*
Expand Down Expand Up @@ -320,8 +336,18 @@ public function handle_request( WP_REST_Request $request ) {
}
}

// Get updates for this client.
$room_response = $this->get_updates( $room, $client_id, $cursor, $is_compactor );
// Get updates for this client without allowing one bloated
// room to make the whole multi-room response too large.
$empty_room_response = array(
'awareness' => $merged_awareness,
'end_cursor' => $cursor,
'room' => $room,
'should_compact' => false,
'total_updates' => 0,
'updates' => array(),
);
$max_update_bytes = $this->get_remaining_response_update_bytes( $response, $empty_room_response );
$room_response = $this->get_updates( $room, $client_id, $cursor, $is_compactor, $max_update_bytes );
$room_response['awareness'] = $merged_awareness;

$response['rooms'][] = $room_response;
Expand Down Expand Up @@ -406,7 +432,7 @@ private function process_sync_update( string $room, int $client_id, int $cursor,
* Check for a newer compaction update first. If one exists, skip this
* compaction to avoid overwriting it.
*/
$updates_after_cursor = $this->storage->get_updates_after_cursor( $room, $cursor );
$updates_after_cursor = $this->storage->get_updates_after_cursor( $room, $cursor, self::MAX_RESPONSE_BODY_SIZE );
$has_newer_compaction = false;

foreach ( $updates_after_cursor as $existing ) {
Expand Down Expand Up @@ -488,6 +514,28 @@ private function add_update( string $room, int $client_id, string $type, string
return true;
}

/**
* Calculates the remaining serialized update budget for a room response.
*
* @since 7.1.0
*
* @param array<string, mixed> $response Response built so far.
* @param array<string, mixed> $empty_room_response Room response without updates.
* @return int Remaining bytes available for serialized update rows.
*/
private function get_remaining_response_update_bytes( array $response, array $empty_room_response ): int {
$candidate_response = $response;
$candidate_response['rooms'][] = $empty_room_response;
$encoded_response = wp_json_encode( $candidate_response );

if ( ! is_string( $encoded_response ) ) {
return 0;
}

$remaining_bytes = self::MAX_RESPONSE_BODY_SIZE - strlen( $encoded_response ) - self::RESPONSE_BODY_ROOM_HEADROOM;
return max( 0, $remaining_bytes );
}

/**
* Gets sync updates for a specific client from a room after a given cursor.
*
Expand All @@ -499,7 +547,8 @@ private function add_update( string $room, int $client_id, string $type, string
* @param string $room Room identifier.
* @param int $client_id Client identifier.
* @param int $cursor Return updates after this cursor.
* @param bool $is_compactor True if this client is nominated to perform compaction.
* @param bool $is_compactor True if this client is nominated to perform compaction.
* @param int $max_update_bytes Maximum serialized update bytes to include.
* @return array{
* end_cursor: int,
* should_compact: bool,
Expand All @@ -508,8 +557,8 @@ private function add_update( string $room, int $client_id, string $type, string
* updates: array<int, array{data: string, type: string}>,
* } Response data for this room.
*/
private function get_updates( string $room, int $client_id, int $cursor, bool $is_compactor ): array {
$updates_after_cursor = $this->storage->get_updates_after_cursor( $room, $cursor );
private function get_updates( string $room, int $client_id, int $cursor, bool $is_compactor, int $max_update_bytes ): array {
$updates_after_cursor = $this->storage->get_updates_after_cursor( $room, $cursor, $max_update_bytes );
$total_updates = $this->storage->get_update_count( $room );

// Filter out this client's updates, except compaction updates.
Expand Down
72 changes: 50 additions & 22 deletions lib/compat/wordpress-7.1/class-wp-sync-post-meta-storage.php
Original file line number Diff line number Diff line change
Expand Up @@ -407,11 +407,12 @@ public function get_update_count( string $room ): int {
*
* @global wpdb $wpdb WordPress database abstraction object.
*
* @param string $room Room identifier.
* @param int $cursor Return updates after this cursor (meta_id).
* @param string $room Room identifier.
* @param int $cursor Return updates after this cursor (meta_id).
* @param int|null $max_update_bytes Optional maximum serialized update bytes to return.
* @return array<int, mixed> Sync updates.
*/
public function get_updates_after_cursor( string $room, int $cursor ): array {
public function get_updates_after_cursor( string $room, int $cursor, ?int $max_update_bytes = null ): array {
global $wpdb;

$post_id = $this->get_storage_post_id( $room );
Expand All @@ -434,34 +435,61 @@ public function get_updates_after_cursor( string $room, int $cursor ): array {
$max_meta_id = $stats ? (int) $stats->max_meta_id : 0;

$this->room_update_counts[ $room ] = $total_updates;
$this->room_cursors[ $room ] = $max_meta_id;
$this->room_cursors[ $room ] = $cursor;

if ( $max_meta_id <= $cursor ) {
if ( $max_meta_id <= $cursor || 0 === $max_update_bytes ) {
return array();
}

$rows = $wpdb->get_results(
$wpdb->prepare(
"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",
$post_id,
self::SYNC_UPDATE_META_KEY,
$cursor,
$max_meta_id
)
);
$updates = array();
$returned_update_bytes = 0;
$last_scanned_update_meta_id = $cursor;
$storage_update_fetch_page_size = 100;

while ( $last_scanned_update_meta_id < $max_meta_id ) {
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT meta_id, 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
LIMIT %d",
$post_id,
self::SYNC_UPDATE_META_KEY,
$last_scanned_update_meta_id,
$max_meta_id,
$storage_update_fetch_page_size
)
);

if ( ! $rows ) {
return array();
}
if ( ! $rows ) {
break;
}

$updates = array();
foreach ( $rows as $row ) {
$decoded = json_decode( $row->meta_value, true );
if ( null !== $decoded ) {
$updates[] = $decoded;
foreach ( $rows as $row ) {
$update_bytes = strlen( $row->meta_value );
if (
null !== $max_update_bytes &&
$returned_update_bytes + $update_bytes > $max_update_bytes
) {
$this->room_cursors[ $room ] = $last_scanned_update_meta_id;
return $updates;
}

$last_scanned_update_meta_id = (int) $row->meta_id;
$returned_update_bytes += $update_bytes;

$decoded = json_decode( $row->meta_value, true );
if ( null !== $decoded ) {
$updates[] = $decoded;
}
}
}

$this->room_cursors[ $room ] = $last_scanned_update_meta_id;

return $updates;
}

Expand Down
Loading
Loading