Skip to content

feat: Store an expiry per presence row - #541

Merged
josephfusco merged 9 commits into
WordPress:mainfrom
jaredrethman:feat/per-row-expiry
Sep 24, 2026
Merged

josephfusco merged 9 commits into
WordPress:mainfrom
jaredrethman:feat/per-row-expiry

Conversation

@jaredrethman

@jaredrethman jaredrethman commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Closes #539.

Each row now carries its own expiry, so a writer that knows when its clients leave can write once and remove the row itself, instead of re-stamping it inside the TTL.

Before

sequenceDiagram
    participant W as Writer
    participant DB as wp_presence
    participant R as Reader
    W->>DB: set_presence() stamps date_gmt
    loop every ~120s, or the row vanishes
        W->>DB: re-stamp date_gmt
    end
    R->>DB: live if date_gmt > now - TTL
Loading

After

sequenceDiagram
    participant W as Writer
    participant DB as wp_presence
    participant R as Reader
    W->>DB: set_presence( ..., expires_in ) once
    R->>DB: live if expires_gmt > now
    W->>DB: remove_presence() when the client leaves
    Note over DB: cron deletes expires_gmt <= now
Loading

Shape

  • expires_gmt is always set, so every liveness check is one indexed predicate.
  • The expiry is computed from the clamped $date_gmt, so a backdated row expires on the writer's clock.
  • wp_presence_max_expires_in caps the window, default one hour. The privacy policy text and the personal data export report that cap, since it is the most any row can outlive its last activity.
  • wp_presence_default_ttl applies at write time. Changing it does not re-age stored rows; anyone still present picks up the new value on their next ping.
  • The redundant-write guard no longer skips a row whose expiry falls inside the next tick gap, since a skipped write would leave it to run out.

Most likely to be wrong

  • $timeout on the readers is a no-op. Honouring it means expires_gmt > %s AND date_gmt > %s, the predicate this removes. The parameters stay because dropping a positional argument turns wp_get_presence( $room, 30 ) into a prefix lookup that silently returns nothing. _deprecated_argument() on a non-default value is the natural next step; A site's wp_presence_default_ttl filter overrides a consumer's own timeout window #515 is the case for keeping the narrowing.
  • No migration step. Pre-existing rows carry the zero default, read as expired, and the scheduled cleanup collects them within the minute; wp presence cleanup does it at once.
  • Cleanup boundary is now expires_gmt <= %s; it read date_gmt < %s while reads used >, leaving a row on the cutoff neither live nor deletable.

Tested

Single-site (518 tests, 143 skipped) and multisite (518, 3 skipped)

Use of AI Tools

Claude Code (Fable 5)

@github-actions github-actions Bot added the Public API Adds a hook, route, constant, or global that third parties can depend on label Sep 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🚩 New public surface

  • (filter) wp_presence_max_expires_in

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

🛝 WordPress Playgrounds

Built from 11dc205

5 users 40 users
Single site Launch single site, 5 users Launch single site, 40 users
Multisite Launch multisite, 5 users Launch multisite, 40 users

@codecov

codecov Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.64286% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.95%. Comparing base (f76e3f2) to head (11dc205).

Additional details and impacted files
@@             Coverage Diff              @@
##               main     #541      +/-   ##
============================================
- Coverage     96.02%   95.95%   -0.08%     
  Complexity      258      258              
============================================
  Files            21       21              
  Lines          3123     3141      +18     
============================================
+ Hits           2999     3014      +15     
- Misses          124      127       +3     
Flag Coverage Δ
multisite 95.95% <94.64%> (-0.08%) ⬇️
phpunit 71.60% <94.64%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

github-actions Bot added a commit that referenced this pull request Sep 24, 2026
@jaredrethman
jaredrethman marked this pull request as ready for review September 24, 2026 02:51
@github-actions

Copy link
Copy Markdown
Contributor

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props jaredrethman, joefusco.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

…n window

wp_set_presence takes expires_in, reads and cleanup use expires_gmt, and a connection-backed writer stops re-stamping.
…ute a cutoff

The readers still accept timeout for their callers, so the docblocks say it no longer narrows a result.
The expiry cap defaults to it, and PHPStan cannot see WordPress constants the bootstrap does not list.
…an hour

A row may now outlive the TTL, so the privacy text and export print the longest window any row can carry.
…he README

Consumers read the signature there, and the timeout parameter no longer narrows a read.
…s given

The branch ran on every write but no test naming the helper credited it, so coverage reported a miss.
…se it

Coverage is attributed through covers annotations, so the helper read as uncovered despite two tests driving it.
github-actions Bot added a commit that referenced this pull request Sep 24, 2026
@josephfusco

josephfusco commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Rebased onto main now that #540 landed; the three failures follow from that: wp_presence_expiry_for() passes WP_PRESENCE_DEFAULT_TTL explicitly so wp_presence_default_ttl never runs, and 9cf03f6 marks $timeout unused on the six signatures #540 made authoritative.

github-actions Bot added a commit that referenced this pull request Sep 24, 2026

@josephfusco josephfusco left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, nice first contribution!

@josephfusco
josephfusco merged commit 872a618 into WordPress:main Sep 24, 2026
21 of 22 checks passed
@jaredrethman

Copy link
Copy Markdown
Contributor Author

@josephfusco Thanks for carrying the rebase and the $timeout reconciliation through.

LGTM, nice first contribution!

Hopefully first of many 😄

@josephfusco josephfusco added this to the 0.7.0 milestone Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Public API Adds a hook, route, constant, or global that third parties can depend on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A connection-backed writer has to re-stamp rows to keep them alive

2 participants