Skip to content

Commit 958c54b

Browse files
committed
Repo: split RepoEvent::NewDoc into a request and new event
Context: the automerge-repo JS implementation supports a request workflow for syncing with a document we don't have. In this workflow the requesting peer sends a "request" message which is identical to the current sync message except tagged with a different message type. Responding peers can then either respond with a sync message or with an "unavailable" message, which allows the receiver to tell the difference between a document the other end doesn't have and a document the other end has but which is empty. Problem: in the repo loop we have no way of telling the difference between a request for a new document and announcing a document we have. This is because both situations are expressed using the RepoEvent::NewDoc event. Solution: split `NewDoc` into a `RequestDoc` and `NewDoc` event. This allows us to send request messages for `RequestDoc` and sync messages for `NewDoc`.
1 parent 78a7612 commit 958c54b

10 files changed

+124
-154
lines changed

examples/distributed_bakery.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ async fn main() {
399399
}
400400

401401
// The initial document.
402-
let doc_handle = repo_handle.new_document();
402+
let doc_handle = repo_handle.new_document().await;
403403
doc_handle.with_doc_mut(|doc| {
404404
let mut tx = doc.transaction();
405405
reconcile(&mut tx, &bakery).unwrap();

examples/tcp-example.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async fn request_doc(State(state): State<Arc<AppState>>, Json(document_id): Json
4343

4444
#[debug_handler]
4545
async fn new_doc(State(state): State<Arc<AppState>>) -> Json<DocumentId> {
46-
let doc_handle = state.repo_handle.new_document();
46+
let doc_handle = state.repo_handle.new_document().await;
4747
let our_id = state.repo_handle.get_repo_id();
4848
doc_handle.with_doc_mut(|doc| {
4949
let mut tx = doc.transaction();

src/repo.rs

+95-125
Original file line numberDiff line numberDiff line change
@@ -134,40 +134,30 @@ impl RepoHandle {
134134
}
135135

136136
/// Create a new document.
137-
pub fn new_document(&self) -> DocHandle {
137+
pub fn new_document(&self) -> impl Future<Output = DocHandle> {
138138
let document_id = DocumentId::random();
139139
let document = new_document();
140-
let doc_info = self.new_document_info(document, DocState::Sync(vec![]));
141-
let handle = DocHandle::new(
142-
self.repo_sender.clone(),
143-
document_id.clone(),
144-
doc_info.document.clone(),
145-
doc_info.handle_count.clone(),
146-
self.repo_id.clone(),
147-
);
140+
let (future, resolver) = new_repo_future_with_resolver();
148141
self.repo_sender
149-
.send(RepoEvent::NewDoc(document_id, doc_info))
142+
.send(RepoEvent::NewDoc(
143+
document_id,
144+
SharedDocument {
145+
automerge: document,
146+
},
147+
resolver,
148+
))
150149
.expect("Failed to send repo event.");
151-
// TODO: return a future to make-up for the unboundedness of the channel.
152-
handle
150+
future
153151
}
154152

155153
/// Boostrap a document, first from storage, and if not found over the network.
156154
pub fn request_document(
157155
&self,
158156
document_id: DocumentId,
159-
) -> RepoFuture<Result<Option<DocHandle>, RepoError>> {
160-
let document = new_document();
157+
) -> impl Future<Output = Result<Option<DocHandle>, RepoError>> {
161158
let (fut, resolver) = new_repo_future_with_resolver();
162-
let doc_info = self.new_document_info(
163-
document,
164-
DocState::Bootstrap {
165-
resolvers: vec![resolver],
166-
storage_fut: None,
167-
},
168-
);
169159
self.repo_sender
170-
.send(RepoEvent::NewDoc(document_id, doc_info))
160+
.send(RepoEvent::RequestDoc(document_id, resolver))
171161
.expect("Failed to send repo event.");
172162
fut
173163
}
@@ -186,15 +176,6 @@ impl RepoHandle {
186176
fut
187177
}
188178

189-
fn new_document_info(&self, document: Automerge, state: DocState) -> DocumentInfo {
190-
let document = SharedDocument {
191-
automerge: document,
192-
};
193-
let document = Arc::new(RwLock::new(document));
194-
let handle_count = Arc::new(AtomicUsize::new(1));
195-
DocumentInfo::new(state, document, handle_count)
196-
}
197-
198179
/// Add a network adapter, representing a connection with a remote repo.
199180
pub fn new_remote_repo(
200181
&self,
@@ -215,7 +196,12 @@ impl RepoHandle {
215196
/// Events sent by repo or doc handles to the repo.
216197
pub(crate) enum RepoEvent {
217198
/// Start processing a new document.
218-
NewDoc(DocumentId, DocumentInfo),
199+
NewDoc(DocumentId, SharedDocument, RepoFutureResolver<DocHandle>),
200+
/// Request a document we don't have
201+
RequestDoc(
202+
DocumentId,
203+
RepoFutureResolver<Result<Option<DocHandle>, RepoError>>,
204+
),
219205
/// A document changed.
220206
DocChange(DocumentId),
221207
/// A document was closed(all doc handles dropped).
@@ -246,7 +232,8 @@ pub(crate) enum RepoEvent {
246232
impl fmt::Debug for RepoEvent {
247233
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248234
match self {
249-
RepoEvent::NewDoc(_, _) => f.write_str("RepoEvent::NewDoc"),
235+
RepoEvent::NewDoc(_, _, _) => f.write_str("RepoEvent::NewDoc"),
236+
RepoEvent::RequestDoc(_, _) => f.write_str("RepoEvent::RequestDoc"),
250237
RepoEvent::DocChange(_) => f.write_str("RepoEvent::DocChange"),
251238
RepoEvent::DocClosed(_) => f.write_str("RepoEvent::DocClosed"),
252239
RepoEvent::AddChangeObserver(_, _, _) => f.write_str("RepoEvent::AddChangeObserver"),
@@ -367,17 +354,6 @@ impl DocState {
367354
}
368355
}
369356

370-
fn get_bootstrap_resolvers(
371-
&mut self,
372-
) -> Vec<RepoFutureResolver<Result<Option<DocHandle>, RepoError>>> {
373-
match self {
374-
DocState::Bootstrap { resolvers, .. } => mem::take(resolvers),
375-
_ => unreachable!(
376-
"Trying to get boostrap resolvers from a document that cannot have any."
377-
),
378-
}
379-
}
380-
381357
fn resolve_bootstrap_fut(&mut self, doc_handle: Result<DocHandle, RepoError>) {
382358
match self {
383359
DocState::Bootstrap { resolvers, .. } => {
@@ -468,38 +444,6 @@ impl DocState {
468444
}
469445
}
470446

471-
fn add_boostrap_storage_fut(
472-
&mut self,
473-
fut: BoxFuture<'static, Result<Option<Vec<u8>>, StorageError>>,
474-
) {
475-
match self {
476-
DocState::Bootstrap {
477-
resolvers: _,
478-
ref mut storage_fut,
479-
} => {
480-
assert!(storage_fut.is_none());
481-
*storage_fut = Some(fut);
482-
}
483-
_ => unreachable!(
484-
"Trying to add a boostrap load future for a document that does not need one."
485-
),
486-
}
487-
}
488-
489-
fn add_boostrap_resolvers(
490-
&mut self,
491-
incoming: &mut Vec<RepoFutureResolver<Result<Option<DocHandle>, RepoError>>>,
492-
) {
493-
match self {
494-
DocState::Bootstrap {
495-
ref mut resolvers, ..
496-
} => {
497-
resolvers.append(incoming);
498-
}
499-
_ => unreachable!("Unexpected adding of boostrap resolvers."),
500-
}
501-
}
502-
503447
fn poll_pending_save(&mut self, waker: Arc<RepoWaker>) {
504448
assert!(matches!(*waker, RepoWaker::Storage { .. }));
505449
match self {
@@ -645,10 +589,6 @@ impl DocumentInfo {
645589
}
646590
}
647591

648-
fn is_boostrapping(&self) -> bool {
649-
self.state.is_bootstrapping()
650-
}
651-
652592
fn start_pending_removal(&mut self) {
653593
self.state = match &mut self.state {
654594
DocState::Error | DocState::LoadPending { .. } | DocState::Bootstrap { .. } => {
@@ -1330,61 +1270,91 @@ impl Repo {
13301270
fn handle_repo_event(&mut self, event: RepoEvent) {
13311271
tracing::trace!(event = ?event, "Handling repo event");
13321272
match event {
1333-
// TODO: simplify handling of `RepoEvent::NewDoc`.
1334-
// `NewDoc` could be broken-up into two events: `RequestDoc` and `NewDoc`,
1335-
// the doc info could be created here.
1336-
RepoEvent::NewDoc(document_id, mut info) => {
1337-
if info.is_boostrapping() {
1338-
tracing::trace!("adding bootstrapping document");
1339-
if let Some(existing_info) = self.documents.get_mut(&document_id) {
1340-
if matches!(existing_info.state, DocState::Bootstrap { .. }) {
1341-
let mut resolvers = info.state.get_bootstrap_resolvers();
1342-
existing_info.state.add_boostrap_resolvers(&mut resolvers);
1343-
} else if matches!(existing_info.state, DocState::Sync(_)) {
1344-
existing_info.handle_count.fetch_add(1, Ordering::SeqCst);
1345-
let handle = DocHandle::new(
1346-
self.repo_sender.clone(),
1347-
document_id.clone(),
1348-
existing_info.document.clone(),
1349-
existing_info.handle_count.clone(),
1350-
self.repo_id.clone(),
1351-
);
1352-
info.state.resolve_bootstrap_fut(Ok(handle));
1353-
} else {
1354-
tracing::warn!(state=?info.state, "newdoc event received for existing document with incorrect state");
1355-
info.state.resolve_bootstrap_fut(Err(RepoError::Incorrect(format!("newdoc event received for existing document with incorrect state: {:?}", info.state))));
1356-
}
1357-
return;
1358-
} else {
1273+
RepoEvent::NewDoc(document_id, document, mut resolver) => {
1274+
assert!(
1275+
self.documents.get(&document_id).is_none(),
1276+
"NewDoc event should be sent with a fresh document ID and only be sent once"
1277+
);
1278+
let shared = Arc::new(RwLock::new(document));
1279+
let handle_count = Arc::new(AtomicUsize::new(1));
1280+
let info =
1281+
DocumentInfo::new(DocState::Sync(vec![]), shared.clone(), handle_count.clone());
1282+
self.documents.insert(document_id.clone(), info);
1283+
resolver.resolve_fut(DocHandle::new(
1284+
self.repo_sender.clone(),
1285+
document_id.clone(),
1286+
shared.clone(),
1287+
handle_count.clone(),
1288+
self.repo_id.clone(),
1289+
));
1290+
Self::enqueue_share_decisions(
1291+
self.remote_repos.keys(),
1292+
&mut self.pending_share_decisions,
1293+
&mut self.share_decisions_to_poll,
1294+
self.share_policy.as_ref(),
1295+
document_id,
1296+
ShareType::Announce,
1297+
);
1298+
}
1299+
RepoEvent::RequestDoc(document_id, mut resolver) => {
1300+
let info = self
1301+
.documents
1302+
.entry(document_id.clone())
1303+
.or_insert_with(|| {
1304+
let handle_count = Arc::new(AtomicUsize::new(1));
13591305
let storage_fut = self.storage.get(document_id.clone());
1360-
info.state.add_boostrap_storage_fut(storage_fut);
1306+
let mut info = DocumentInfo::new(
1307+
DocState::Bootstrap {
1308+
resolvers: vec![],
1309+
storage_fut: Some(storage_fut),
1310+
},
1311+
Arc::new(RwLock::new(SharedDocument {
1312+
automerge: new_document(),
1313+
})),
1314+
handle_count.clone(),
1315+
);
13611316
info.poll_storage_operation(
13621317
document_id.clone(),
13631318
&self.wake_sender,
13641319
&self.repo_sender,
13651320
&self.repo_id,
13661321
);
13671322

1368-
let share_type = if info.is_boostrapping() {
1369-
Some(ShareType::Request)
1370-
} else if info.state.should_sync() {
1371-
Some(ShareType::Announce)
1372-
} else {
1373-
None
1374-
};
1375-
if let Some(share_type) = share_type {
1376-
Self::enqueue_share_decisions(
1377-
self.remote_repos.keys(),
1378-
&mut self.pending_share_decisions,
1379-
&mut self.share_decisions_to_poll,
1380-
self.share_policy.as_ref(),
1381-
document_id.clone(),
1382-
share_type,
1383-
);
1384-
}
1323+
info
1324+
});
1325+
1326+
match &mut info.state {
1327+
DocState::Bootstrap { resolvers, .. } => resolvers.push(resolver),
1328+
DocState::Sync(_) => {
1329+
info.handle_count.fetch_add(1, Ordering::SeqCst);
1330+
let handle = DocHandle::new(
1331+
self.repo_sender.clone(),
1332+
document_id.clone(),
1333+
info.document.clone(),
1334+
info.handle_count.clone(),
1335+
self.repo_id.clone(),
1336+
);
1337+
resolver.resolve_fut(Ok(Some(handle)));
1338+
}
1339+
DocState::LoadPending { resolvers, .. } => resolvers.push(resolver),
1340+
DocState::PendingRemoval(_) => resolver.resolve_fut(Ok(None)),
1341+
DocState::Error => {
1342+
resolver.resolve_fut(Err(RepoError::Incorrect(
1343+
"request event called for document which is in error state".to_string(),
1344+
)));
13851345
}
13861346
}
1387-
self.documents.insert(document_id, info);
1347+
1348+
if info.state.is_bootstrapping() {
1349+
Self::enqueue_share_decisions(
1350+
self.remote_repos.keys(),
1351+
&mut self.pending_share_decisions,
1352+
&mut self.share_decisions_to_poll,
1353+
self.share_policy.as_ref(),
1354+
document_id.clone(),
1355+
ShareType::Request,
1356+
);
1357+
}
13881358
}
13891359
RepoEvent::DocChange(doc_id) => {
13901360
// Handle doc changes: sync the document.

tests/interop/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fn sync_two_repos(port: u16) {
4545
});
4646
tracing::trace!("connected conn1");
4747

48-
let doc_handle_repo1 = repo1_handle.new_document();
48+
let doc_handle_repo1 = repo1_handle.new_document().await;
4949
doc_handle_repo1
5050
.with_doc_mut(|doc| {
5151
doc.transact(|tx| {

tests/network/document_changed.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ async fn test_document_changed_over_sync() {
2222
let repo_handle_2_clone = repo_handle_2.clone();
2323

2424
// Create a document for one repo.
25-
let document_handle_1 = repo_handle_1.new_document();
25+
let document_handle_1 = repo_handle_1.new_document().await;
2626

2727
// Spawn a task that awaits the requested doc handle,
2828
// and then edits the document.
@@ -99,7 +99,7 @@ async fn test_document_changed_locally() {
9999
let expected_repo_id = repo_handle_1.get_repo_id().clone();
100100

101101
// Create a document for the repo.
102-
let doc_handle = repo_handle_1.new_document();
102+
let doc_handle = repo_handle_1.new_document().await;
103103

104104
// spawn a task which edits the document
105105
tokio::spawn({

tests/network/document_list.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ async fn test_list_all() {
1414
let repo_handle = repo.run();
1515

1616
// Create a document for one repo.
17-
let document_handle = repo_handle.new_document();
17+
let document_handle = repo_handle.new_document().await;
1818
let document_id = document_handle.document_id();
1919

2020
// Edit the document.
@@ -58,7 +58,7 @@ async fn test_list_all_errors_on_shutdown() {
5858
let repo_handle = repo.run();
5959

6060
// Create a document for one repo.
61-
let document_handle = repo_handle.new_document();
61+
let document_handle = repo_handle.new_document().await;
6262
let document_id = document_handle.document_id();
6363

6464
// Edit the document.

tests/network/document_load.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ async fn test_loading_document_found_immediately() {
1515
let repo_handle = repo.run();
1616

1717
// Create a document for one repo.
18-
let document_handle = repo_handle.new_document();
18+
let document_handle = repo_handle.new_document().await;
1919

2020
// Edit the document.
2121
let doc_data = document_handle.with_doc_mut(|doc| {
@@ -66,7 +66,7 @@ async fn test_loading_document_found_async() {
6666
let repo_handle = repo.run();
6767

6868
// Create a document for one repo.
69-
let document_handle = repo_handle.new_document();
69+
let document_handle = repo_handle.new_document().await;
7070

7171
// Edit the document.
7272
let doc_data = document_handle.with_doc_mut(|doc| {

0 commit comments

Comments
 (0)