Skip to content

Conversation

@iduartgomez
Copy link
Collaborator

@iduartgomez iduartgomez commented Dec 5, 2025

Problem

Issue #2075 identified that local client subscriptions (WebSocket connections) were being registered in the same data structure as network peer subscriptions (ring.seeding_manager.subscribers). This coupling created several problems:

  1. The allow_self hack - The get_broadcast_targets_update() function needed special logic to detect when an UPDATE originated locally vs. from the network, because local clients appeared in the subscriber list alongside remote peers.

  2. Architectural confusion - The ops/ module (network operations) had implicit knowledge of client_events/ concerns, making the code harder to reason about.

  3. Potential for bugs - Mixing local and network subscribers in one structure made it easy to accidentally send network messages to local clients or vice versa.

This Solution

The key insight is that local client subscriptions already have a separate delivery path: the contract executor's update_notifications channels deliver UpdateNotification directly to WebSocket clients. There's no need to also register them in the network subscriber list.

Changes:

  1. complete_local_subscription() (subscribe.rs) - Removed the call to ring.add_subscriber(). Local subscriptions now only fire the LocalSubscribeComplete event to notify the client layer.

  2. get_broadcast_targets_update() (update.rs) - Removed the allow_self workaround. The function now simply filters out the sender to prevent echoing updates back.

  3. Documentation - Added architecture notes to LocalSubscribeComplete, add_subscriber(), and the affected functions explaining the separation.

Architecture After Fix:

Layer Subscription Storage Delivery Mechanism
Network seeding_manager.subscribers Peer-to-peer UPDATE messages
Local Executor's update_notifications Direct WebSocket delivery

Testing

  • Added test_subscribers_for_broadcast_targeting - validates the broadcast filtering logic with real assertions
  • Verified test_get_with_subscribe_flag passes - confirms local subscription + update notification works
  • The test_multiple_clients_subscription (cross-node) remains #[ignore] due to unrelated network timing flakiness (see Fix flaky test_multiple_clients_subscription test #1798)

Fixes

Closes #2075

claude and others added 15 commits December 4, 2025 23:34
- Analyzed test_small_network_get_failure failure modes
- Identified gateway crash root cause (fixed in a283e23)
- Documented PUT operation timeout issues
- Provided recommendations for re-enabling the test
- Suggested modernization using #[freenet_test] macro

Related: freenet#2023, freenet#2043, freenet#2036, freenet#2011
Re-enabled the previously ignored test with key improvements:
- Removed #[ignore] attribute - recent fixes should resolve issues
- Increased PUT timeout: 30s → 90s (accounts for connection delays)
- Increased overall test timeout: 120s → 180s (3 minutes)
- Added detailed error messages for better debugging
- Added documentation of recent fixes that resolved the issues

Recent fixes that should prevent failures:
- a283e23: Fixed gateway crashes during timeout notifications
- 615f02d: Fixed PUT response routing through forwarding peers
- 5734a33: Fixed local caching before forwarding PUTs

Related: freenet#2023
The freenet-ping contract failed to compile because freenet-ping-types
uses freenet_stdlib::time::now() when 'std' feature is disabled, but the
'contract' feature wasn't propagated to freenet-stdlib.

Changes:
- Added 'contract' feature to freenet-ping-types Cargo.toml
- Enabled 'contract' feature in ping contract's types dependency
- This allows WASM contract compilation to access time::now() function

Fixes compilation error when test_small_network_get_failure loads and
compiles the ping contract at runtime.
Co-authored-by: nacho.d.g <iduartgomez@users.noreply.github.com>
Cleaned up redundant comments throughout the test file that were
explaining self-evident code. Kept the TODO comment as it's actionable.

Co-authored-by: nacho.d.g <iduartgomez@users.noreply.github.com>
…enet#2023

This change annotates test_small_network_get_failure with test_log to
capture test execution logs in CI. The test passes locally but fails in
CI, and these logs will help us debug the issue.

Changes:
- Add test-log 0.2 to dev-dependencies in freenet-ping-app
- Replace manual logger setup with #[test_log::test] attribute
- Remove unused LevelFilter import
- Logs will now be captured and displayed on test failure

This will help us understand what's happening during CI test execution
and identify the root cause of issue freenet#2023.
- Update Cargo.lock to include test-log dependency changes
- Fix formatting (remove extra blank line in test function)
- Resolves CI build failure with --locked flag

This addresses the CI error:
"the lock file needs to be updated but --locked was passed"
Removes unnecessary implementation notes that were no longer relevant
after previous fixes to connection management and timeout handling.

Related to issue freenet#2023 investigation.
Update lock file after rebasing PR freenet#2055 onto latest main.
…LuLNaER7JBmDiLQXuDAtpv

fix: critical operation state management issues (freenet#1977)
- Remove local subscriber registration from seeding_manager in
  complete_local_subscription() - local clients no longer pollute the
  network peer subscription list
- Remove the allow_self hack from get_broadcast_targets_update() - this
  workaround was only needed because local clients were mixed with
  network peers
- Add architecture documentation explaining the separation:
  - Network subscriptions: seeding_manager.subscribers for peer-to-peer
    UPDATE propagation
  - Local subscriptions: executor's update_notifications channels for
    direct WebSocket client delivery

This clean separation eliminates cross-module awareness between ops/
and client_events/, making the code easier to reason about and debug.

Fixes freenet#2075
…et#2075)

Add tests that document and validate the separation between local client
subscriptions and network peer subscriptions:

- test_local_subscription_does_not_register_in_seeding_manager_architecture:
  Documents that complete_local_subscription does NOT register in seeding_manager

- test_subscribers_for_broadcast_targeting:
  Validates the broadcast filtering logic used by get_broadcast_targets_update

These tests serve as architectural documentation and regression prevention
for the decoupling implemented in Issue freenet#2075.
- Add test_subscribers_for_broadcast_targeting to validate the filtering
  logic used by get_broadcast_targets_update after Issue freenet#2075 refactor
- Update test_multiple_clients_subscription comment to note that Issue freenet#2075
  fix should help but test still has unrelated network timing flakiness
@CLAassistant
Copy link

CLAassistant commented Dec 5, 2025

CLA assistant check
All committers have signed the CLA.

@freenet freenet deleted a comment from claude bot Dec 5, 2025
@iduartgomez iduartgomez marked this pull request as ready for review December 5, 2025 07:48
@iduartgomez iduartgomez changed the title feat: separate network from local subscriptions fix: separate network from local subscriptions Dec 5, 2025
// should improve reliability. However, the test still exhibits flakiness due to
// network timing/transaction timeout issues unrelated to the subscription logic.
// See issue #1798 for historical context.
#[ignore]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'll have an agent work on re-enabling this test

@sanity
Copy link
Collaborator

sanity commented Dec 5, 2025

@claude please review this PR

@claude
Copy link

claude bot commented Dec 5, 2025

Claude finished @sanity's task —— View job


Code Review Complete

I've completed a detailed review of PR #2219. This is an excellent architectural improvement that cleanly separates local client subscriptions from network peer subscriptions. The PR is well-structured with comprehensive documentation, proper testing, and clear reasoning.


Summary

Verdict: ✅ Approve with Minor Suggestions

This PR successfully addresses issue #2075 by removing the architectural confusion between local and network subscriptions. The changes are:

  • Architecturally sound - proper separation of concerns
  • Well-documented - excellent architecture notes throughout
  • Properly tested - new unit test validates the broadcast filtering logic
  • Minimal risk - removes code (the allow_self hack) rather than adding complexity

Detailed Analysis

1. Architecture Changes (subscribe.rs:280-313)

The removal of ring.add_subscriber() from complete_local_subscription() is the core of this fix:

// REMOVED: op_manager.ring.add_subscriber(&key, subscriber.clone(), None)
// NOW: Just notify client layer via LocalSubscribeComplete event

Strengths:

  • Clean separation: local subscriptions now only use LocalSubscribeComplete event
  • Updates flow through executor's update_notifications channels (separate from network)
  • Eliminates mixing of concerns between ops/ and client_events/ modules

Verification needed:

  • Confirm that the executor's update_notifications mechanism handles all local subscription cases
  • The PR description mentions send_update_notification in runtime.rs but this file wasn't changed - might be worth adding a comment reference there too

2. Broadcast Filtering (update.rs:743-806)

The get_broadcast_targets_update() simplification is beautiful:

Before:

let allow_self = self_addr.as_ref().map(|me| me == sender).unwrap_or(false);
// ... complex filter checking is_sender, is_self, allow_self

After:

.filter(|pk| pk.socket_addr().as_ref() != Some(sender))

Strengths:

  • Much clearer intent: "don't echo updates back to sender"
  • Removes the allow_self hack that was needed when local clients were in the subscriber list
  • The architecture note explains the separation clearly

Observations:

  • The new logic is simpler and easier to reason about
  • No edge cases for "local auto-subscribes" since they're handled separately now

3. Documentation Quality

The architecture notes added throughout are excellent:

  • complete_local_subscription() (subscribe.rs:282-295)
  • get_broadcast_targets_update() (update.rs:746-752)
  • add_subscriber() (seeding.rs:109-119)
  • LocalSubscribeComplete event (message.rs:409-420)

All consistently reference issue #2075 and explain the separation clearly.

Suggestion: Consider adding a cross-reference in runtime.rs near the send_update_notification function to point back to this architecture, since it's mentioned in multiple doc comments.


4. Test Coverage

The new test_subscribers_for_broadcast_targeting() (seeding.rs:350-413) is well-designed:

What it validates:

  • Network subscribers are correctly stored in seeding_manager
  • Broadcast targeting correctly filters out the sender
  • The foundation for UPDATE propagation works correctly

Strengths:

Minor suggestion: Consider adding a negative test case that explicitly verifies local clients are not in the network subscriber list (though this is implicitly verified by the test setup).


5. Test Comment Update (operations.rs:639-644)

The updated comment for test_multiple_clients_subscription is accurate:

// Issue #2075 decoupled local client subscriptions from network subscriptions, which
// should improve reliability. However, the test still exhibits flakiness due to
// network timing/transaction timeout issues unrelated to the subscription logic.

Observation: The comment correctly attributes the remaining flakiness to network timing issues (see #1798), not to this PR's changes. This is honest and helpful for future debugging.


Potential Issues & Questions

1. Backward Compatibility 🔍

Question: Are there any scenarios where code outside the files changed might still assume local subscriptions are in ring.subscribers?

  • Checked: get_broadcast_targets_update is the main consumer (fixed in this PR)
  • The separation appears complete, but worth confirming no other code paths check ring.subscribers_of() expecting to find local clients

2. Update Delivery Path 🔍

Question: Can you confirm the executor's update_notifications path handles all edge cases?

Specifically:

  • What happens if a local client subscribes but the contract isn't cached yet?
  • Does the executor notification happen for both local and remote UPDATEs?
  • Are there any race conditions where an UPDATE could arrive before the subscription is fully established?

The PR assumes this path works correctly (which it likely does), but since we're removing the network-based path for local clients, it's worth explicit verification.

3. Test Gap 🤔

The test_get_with_subscribe_flag mentioned in the PR description isn't shown in the diff. Consider:

  • Adding explicit assertions in that test confirming local subscription works without network registration
  • Or creating a new focused test: "local subscription delivers updates without network broadcast"

Code Quality Notes

Positive patterns:

  • Consistent architecture notes across all changed files
  • Clear separation of concerns (network vs. local)
  • Good use of logging (tracing::debug) to explain what's happening
  • Test validates the exact logic being changed

Minor style observations:

  • The upstream_addr parameter docstring in add_subscriber() (seeding.rs:115-118) is excellent - clear explanation of why it exists
  • The formatting and structure are consistent with the rest of the codebase

Recommendations

Must-Have (Blockers):

None - the PR is ready to merge as-is.

Should-Have (Strongly Recommended):

  1. Add cross-reference in runtime.rs (seeding.rs:109-119)

  2. Verify update delivery path

    • Confirm (via test or code review) that executor notifications handle all cases
    • Especially: local subscribe → local update, local subscribe → remote update

Nice-to-Have (Optional Improvements):

  1. Add negative test case

    • Test that explicitly verifies local clients are NOT in network subscriber list
    • Would make the separation even more explicit
  2. Add integration test

    • Test: local client subscribes, receives updates, without network registration
    • Would complement the unit test in seeding.rs
  3. Architecture diagram

    • Consider adding a simple ASCII diagram in ARCHITECTURE.md showing:
      • Network subscriptions → seeding_manager.subscribers → UPDATE broadcast
      • Local subscriptions → executor.update_notifications → WebSocket delivery

Summary Table

Aspect Rating Notes
Architecture ✅ Excellent Clean separation of concerns
Code Quality ✅ Excellent Simpler, more maintainable
Documentation ✅ Excellent Comprehensive architecture notes
Testing ✅ Good New unit test validates core logic
Risk ✅ Low Removes complexity, well-documented

Conclusion

This is a well-executed architectural cleanup that makes the codebase more maintainable and easier to reason about. The removal of the allow_self hack and the clear separation between network and local subscriptions are significant improvements.

The PR description is also exemplary - it explains the problem, the solution, the reasoning, and the testing strategy clearly.

Recommendation: Approve and merge after considering the "Should-Have" suggestions above (especially verifying the executor notification path handles all cases).


@freenet freenet deleted a comment from claude bot Dec 5, 2025
@freenet freenet deleted a comment from claude bot Dec 5, 2025
@freenet freenet deleted a comment from iduartgomez Dec 5, 2025
@sanity
Copy link
Collaborator

sanity commented Dec 5, 2025

⚠️ Do NOT merge until @iduartgomez has reviewed

iduartgomez pushed a commit to iduartgomez/freenet-core that referenced this pull request Dec 5, 2025
Address review comments from PR freenet#2219:

1. Add comprehensive documentation to send_update_notification() in
   runtime.rs explaining the separation between local and network
   subscription delivery mechanisms.

2. Add explicit assertions and documentation in test_get_with_subscribe_flag
   confirming that local subscriptions work without network registration.

3. Update test_multiple_clients_subscription comment to document the
   remote update scenario (local subscribe → remote update) and its
   relationship to the local subscription test.

The architecture uses two distinct paths:
- Local subscriptions: executor notification channels (send_update_notification)
- Network subscriptions: ring.seeding_manager.subscribers (broadcast targeting)

This completes the documentation requested in the PR review.
@iduartgomez iduartgomez closed this Dec 5, 2025
@iduartgomez iduartgomez deleted the claude/fix-issue-2075-01TQv7gf28DV7hpSQhA91cgf branch December 5, 2025 23:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decouple local client subscriptions from network subscriptions

4 participants