Skip to content

Purge vs postUpdates sync race: in-flight sync resurrects a purged post's MFS entry #142

@Rinse12

Description

@Rinse12

Summary

A TOCTOU race between comment purge and the postUpdates MFS sync can leave a purged post's CommentUpdate permanently in the postUpdates MFS tree of a community's IPFS node.

Surfaced as a flaky failure in the remote-libp2pjs CI config on the helia/libp2p dependency upgrade branch:

AssertionError: MFS path /<community>/postUpdates/86400/<postCid>/update still exists after 30s - purge cleanup did not complete
  at test/node-and-browser/publications/comment-moderation/purged.test.ts:292

The slower libp2pjs client transport widens timing windows, so the race lands; the Kubo-RPC / gateway configs (same community-side code) pass. The community-side purge/sync code is not changed by the dependency-upgrade PR — this is a pre-existing concurrency bug exposed by timing.

Root cause

syncIpnsWithDb (sync loop) and storeCommentModeration (pubsub challenge handler) run concurrently with no mutual exclusion. Observed timeline (community-side log):

  1. t0updateCommentsThatNeedToBeUpdated calculates the post's CommentUpdate (post still in DB), producing a row with localMfsPath = /<addr>/postUpdates/86400/<postCid>/update.
  2. t0+~150ms — a purge moderation arrives: storeCommentModeration deletes the post from the DB, queues its MFS path, and rmUnneededMfsPaths removes it from MFS (this also drains _mfsPathsToRemove).
  3. t0+~190ms — the in-flight sync from step 1 reaches syncPostUpdatesWithIpfs and writes the captured (pre-purge) row back to MFS.

Because the post is now gone from the DB, it is never recalculated and never re-purged. Nothing scans the MFS tree to remove entries absent from the DB (calculateNewPostUpdates only reads bucket dirs), so the resurrected entry persists indefinitely.

Fix

In syncPostUpdatesWithIpfs, skip writing post-update rows whose comment no longer exists in the DB (purged mid-cycle), and don't throw when the post-filter list is empty.

Plan

  • Deterministic regression test under test/node/ (capture row → purge → stale sync write → assert not resurrected)
  • Fix in syncPostUpdatesWithIpfs
  • npm run build + test green under local-kubo-rpc

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions