Skip to content

Conversation

@sij411
Copy link
Contributor

@sij411 sij411 commented Dec 29, 2025

Summary

Fixes race conditions in @fedify/relay follower management system by replacing array-based storage with individual key-value pairs for atomic operations, and improves type safety following Fedify's design patterns.

Closes #505

Problem

The previous implementation used a single key ["followers"] storing an array of follower actor IDs. This created race conditions in concurrent operations:

Race Condition Scenario 1: Concurrent Additions

  1. Server A reads ["followers"][Alice]
  2. Server B reads ["followers"][Alice]
  3. Server A adds Bob → writes [Alice, Bob]
  4. Server B adds Carol → writes [Alice, Carol]
  5. Result: Bob is lost, final state is [Alice, Carol]

Race Condition Scenario 2: Concurrent Removal

  1. Server A reads ["followers"][Alice, Bob, Carol]
  2. Server B reads ["followers"][Alice, Bob, Carol]
  3. Server A removes Bob → writes [Alice, Carol]
  4. Server B removes Carol → writes [Alice, Bob]
  5. Result: Carol reappears, final state is [Alice, Bob]

Both scenarios stem from the read-modify-write pattern on a shared array.

Solution

1. Individual Key-Value Storage (Commit 04aa2fd)

Changed from:

  • Single key: ["followers"][ActorID_A, ActorID_B, ...]

To:

  • Individual keys: ["follower", ActorID]RelayFollower

This enables atomic operations:

  • Addition: kv.set(["follower", actorId], data) - atomic write
  • Removal: kv.delete(["follower", actorId]) - atomic delete
  • Listing: kv.list(["follower"]) - iterate over all followers

2. Implementation Changes

packages/relay/src/builder.ts:

  • Modified getFollowerActors() to use kv.list(["follower"]) instead of reading followers array
  • Eliminates read-modify-write pattern

packages/relay/src/follow.ts:

  • Simplified handleUndoFollow() to use atomic delete() only
  • Removed followers array manipulation

packages/relay/src/mastodon.ts:

  • Removed followers array read-modify-write in Follow handler
  • Now uses only atomic set() operation

packages/relay/src/litepub.ts:

  • Removed followers array read-modify-write in Accept handler
  • Now updates state directly with atomic set()

Test files:

  • Updated mastodon.test.ts and litepub.test.ts to remove ["followers"] array references
  • Fixed variable naming to match implementation (followerId instead of followActivityId)

3. Comprehensive Test Coverage (Commit 7dbe704)

Added 9 new tests across both relay implementations:

Common tests (4 each for Mastodon and LitePub):

  • Empty list handling
  • Multiple follower additions
  • Deletion exclusion verification
  • Data integrity validation

LitePub-specific test (1):

  • State filtering for pending vs accepted followers

Total: +274 lines of test coverage

4. Type Safety Improvements (Commit 7e97abc)

Replaced unsafe type casting with type predicates following Fedify's established patterns (similar to isActor()):

packages/relay/src/types.ts:

  • Added isRelayFollower() type predicate function
  • Provides both runtime validation and compile-time type narrowing

packages/relay/src/builder.ts:

  • Replaced value as RelayFollower cast with isRelayFollower(value) guard
  • Removed unused RelayFollower type import
  • TypeScript now automatically narrows types after guard check

Benefits:

  • Compile-time type narrowing without unsafe assertions
  • Runtime validation of KV store data
  • Consistent with Fedify's type-safe design philosophy
  • No reliance on runtime type casting

Changes

Files modified:

  • packages/relay/src/builder.ts (-16/+12 lines)
  • packages/relay/src/follow.ts (-8/+2 lines)
  • packages/relay/src/mastodon.ts (-8/+2 lines)
  • packages/relay/src/litepub.ts (-11/+4 lines)
  • packages/relay/src/types.ts (+18 lines)
  • packages/relay/src/mastodon.test.ts (-7/+119 lines)
  • packages/relay/src/litepub.test.ts (-18/+173 lines)

Net changes:

  • First commit: -49 net lines (simplification through atomic operations)
  • Second commit: +274 lines (comprehensive test coverage)
  • Third commit: +17 lines (type safety improvements)

Test Plan

  • All existing tests pass
  • New tests verify list() functionality
  • Race condition scenarios cannot occur with atomic operations
  • Both Mastodon and LitePub relay protocols work correctly
  • Type predicates provide compile-time type narrowing

🤖 Generated with Claude Code

sij411 and others added 2 commits December 29, 2025 17:13
Replaced array-based follower storage with individual key-value pairs
to eliminate race conditions during concurrent follower operations.

Previously, follower list used read-modify-write pattern on a single
["followers"] array, causing data loss when concurrent processes
added or removed followers simultaneously.

Changes:
- Use KvStore.list() for reading followers in builder.ts
- Use atomic set() operations for adding followers
- Use atomic delete() operations for removing followers
- Remove all read-modify-write operations on ["followers"] array
- Update tests to match new storage structure

Fixes fedify-dev#505

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added test cases to verify list() behavior across all scenarios:
- Empty list on initialization
- Multiple follower additions and retrieval
- Correct exclusion after deletions
- Complete actor data preservation
- State filtering (pending vs accepted for LitePub)

These tests ensure the race condition fix using individual key-value
pairs works correctly and list() properly replaces the array-based
approach.

Related to fedify-dev#505

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@issues-auto-labeler issues-auto-labeler bot added breaking change Breaking change component/federation Federation object related component/kv Key–value store related component/relay labels Dec 29, 2025
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @sij411, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses and resolves critical race conditions within the @fedify/relay follower management system. The previous implementation, which stored all follower IDs in a single array, was susceptible to data loss and inconsistencies during concurrent add or remove operations. The solution refactors the storage mechanism to use individual key-value pairs for each follower, allowing for atomic operations that guarantee data integrity and system reliability, significantly improving the robustness of follower management.

Highlights

  • Race Condition Fix: Eliminated race conditions in follower management by switching from a shared array to individual key-value entries for atomic operations.
  • Storage Model Refactor: Transitioned follower storage from a single "followers" array key to individual "follower, ActorID" keys, enabling atomic set and delete operations.
  • Enhanced Test Coverage: Added 9 new, comprehensive tests across Mastodon and LitePub relay implementations, increasing test coverage by 274 lines to validate the new storage mechanism and prevent regressions.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively resolves a critical race condition in follower management by transitioning from a shared array in the key-value store to individual keys for each follower. This change enables atomic add and remove operations, eliminating the problematic read-modify-write pattern. The implementation is clean and simplifies the logic in several places. The accompanying tests are thorough, covering the new storage strategy and ensuring the fix is robust. Overall, this is an excellent and important improvement.

@codecov
Copy link

codecov bot commented Dec 29, 2025

Codecov Report

❌ Patch coverage is 81.25000% with 3 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/relay/src/builder.ts 60.00% 2 Missing ⚠️
packages/relay/src/types.ts 88.88% 1 Missing ⚠️
Files with missing lines Coverage Δ
packages/relay/src/follow.ts 80.35% <100.00%> (+0.35%) ⬆️
packages/relay/src/litepub.ts 85.36% <ø> (-0.47%) ⬇️
packages/relay/src/mastodon.ts 68.49% <ø> (-1.64%) ⬇️
packages/relay/src/mod.ts 100.00% <100.00%> (ø)
packages/relay/src/types.ts 90.00% <88.88%> (-10.00%) ⬇️
packages/relay/src/builder.ts 81.17% <60.00%> (-1.78%) ⬇️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Replaces unsafe type casting with a type predicate function following
Fedify's established patterns (similar to isActor()).

Changes:
- Add isRelayFollower() type predicate in types.ts
- Replace "value as RelayFollower" cast with isRelayFollower(value) in builder.ts
- Remove unused RelayFollower type import

Benefits:
- Compile-time type narrowing after guard check
- Runtime validation of KV store data
- Consistent with Fedify's type-safe design philosophy
- No unsafe type assertions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@sij411
Copy link
Contributor Author

sij411 commented Dec 29, 2025

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This is an excellent pull request that correctly identifies and fixes a critical race condition in follower management. The move from an array-based storage to individual key-value pairs is a solid architectural change that enables atomic operations and improves robustness. I appreciate the comprehensive test coverage added to validate the new approach. The introduction of the isRelayFollower type predicate is also a great improvement for type safety, making the code more reliable. I've left a few minor suggestions in the new test files to further improve type safety by using this new type guard, making the tests consistent with the type-safe philosophy of the PR.

Improves type safety by making isRelayFollower part of the public API
and replacing unsafe type assertions with proper type guards in tests.

Changes:
- Export isRelayFollower from mod.ts for public use
- Add state filtering in builder.ts (only return accepted followers)
- Replace RelayFollower "as any" casts with isRelayFollower guards in tests
- Use Record<string, unknown> for actor properties instead of "as any"

Benefits:
- Public API now includes type predicate for RelayFollower validation
- Test code has compile-time type safety
- Consistent with Fedify's pattern of exporting type predicates
- Runtime validation ensures data integrity from KV store

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Member

@dahlia dahlia left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@dahlia dahlia merged commit 78cf04d into fedify-dev:next Dec 29, 2025
25 of 28 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking change Breaking change component/federation Federation object related component/kv Key–value store related component/relay

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants