Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a SharePolicy #62

Merged
merged 1 commit into from
Dec 2, 2023
Merged

Add a SharePolicy #62

merged 1 commit into from
Dec 2, 2023

Conversation

alexjg
Copy link
Collaborator

@alexjg alexjg commented Nov 28, 2023

Problem: we announce documents to all connected peers and we share documents with anyone who knows the document ID. Most applications probably want some control over authorization.

Solution: introduce SharePolicy, which is a trait users implement and pass to Repo::with_share_policy to control when documents are shared.

This is a pretty naive encoding of a SharePolicy trait. In particular it returns a BoxFuture<'static, ..> for each method on SharePolicy, which means we're doing dynamic dispatch on every access control decision. I think this is probably not an issue given that the IO we're doing should dominate runtimes, but if it becomes an issue we can add a generic parameter to Repo and thread that through.

@alexjg alexjg requested a review from gterzian November 28, 2023 16:57
@gterzian
Copy link
Collaborator

Reviewing...

Copy link
Collaborator

@gterzian gterzian 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, and I think the design could be made clearer by moving stuff around and ending up with a state machine approach similar to DocState.

Something like a authorization_states: HashMap<RepoId, ShareAuthorization> to be found on DocumentInfo, and which would contain all data, including futures to poll, with only share_decisions_to_poll: HashSet<(DocumentId, RepoId)> found on the Repo as a way to integrate with the event-loop.

src/repo.rs Outdated Show resolved Hide resolved
src/repo.rs Show resolved Hide resolved
src/repo.rs Outdated Show resolved Hide resolved
src/repo.rs Outdated
&mut self.share_decisions_to_poll,
self.share_policy.as_ref(),
document_id.clone(),
ShareType::Request,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Request is ambiguous here, because it could be that we've succesfully loaded the whole doc from storage. Could have a branch that requests if is_boostrapping, and one that announces otherwise if should_sync.

src/repo.rs Outdated Show resolved Hide resolved
src/repo.rs Show resolved Hide resolved
src/repo.rs Show resolved Hide resolved
@@ -1685,4 +1812,42 @@ impl Repo {
false
}
}

fn enqueue_share_decisions<'a, I: Iterator<Item = &'a RepoId>>(
Copy link
Collaborator

@gterzian gterzian Nov 29, 2023

Choose a reason for hiding this comment

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

I would rather see:

fn enqueue_share_decisions(
    doc_info: &mut DocumentInfo,
    share_authorizations_to_poll: &mut HashSet<(DocumentId, RepoId)>,
    /// the same below
)

Which would only process repos for the document which do not have already an authorization.

src/repo.rs Show resolved Hide resolved
@alexjg
Copy link
Collaborator Author

alexjg commented Nov 30, 2023

Great feedback. I've not done quite what you suggested. Instead I've introduced a PeerConnection enum, which is similar to the ShareAuthorization idea you've suggested but it doesn't track the futures, which I have left on the Repo. There are a few reasons for this:

  • I think we will definitely need multiple futures running at once. The first place this will become necessary is in a change I want to introduce soon where we persist sync states to disc. In this case as well as waiting for authorization we will also need to wait for storage to load the sync state. Tracking the futures in the PeerConnection enum would mean every variant of the enum having to track every combination of futures. It seems cleaner to track the futures at the Repo level.
  • Fairly soon I am planning to extract the core state machine of this libary into a separate module which is independent of IO and runtime so that we can use it via FFI in other platforms (esp. Swift and Kotlin). In this case the future would be an artifact of the integration with async runtimes in Rust.

@gterzian
Copy link
Collaborator

gterzian commented Dec 1, 2023

extract the core state machine of this libary into a separate module

Great idea!

Reviewing...

Copy link
Collaborator

@gterzian gterzian left a comment

Choose a reason for hiding this comment

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

LGTM, with some comments that may need addressing.

@@ -869,6 +976,7 @@ pub struct Repo {
/// to poll in the current loop iteration.
streams_to_poll: HashSet<RepoId>,
sinks_to_poll: HashSet<RepoId>,
share_decisions_to_poll: HashSet<RepoId>,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are these not just the keys of pending_share_decisions, in which case this is redundant?

PeerConnection::PendingAuth { .. } => {
let mut sync_state = SyncState::new();
let document = self.document.read();
let message = document.automerge.generate_sync_message(&mut sync_state);
Copy link
Collaborator

Choose a reason for hiding this comment

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

first apply received sync messages?

So this branch is hit if the peer sends us a sync message while our announce request is pending, in a way it happens when two peers connect, and one of them get permission to announce, and we receive the sync message before the local request to announce has completed?

Problem: we announce documents to all connected peers and we share
documents with anyone who knows the document ID. Most applications
probably want some control over authorization.

Solution: introduce `SharePolicy`, which is a trait users implement and
pass to `Repo::with_share_policy` to control when documents are shared.
@alexjg alexjg merged commit 9b0ca93 into main Dec 2, 2023
8 checks passed
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.

2 participants