-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Document added to repo doesn't seem to get state from web socket peer until it sends state of own #106
Comments
I'm seeing what I think is a variant of the same problem. In my app, starting with an empty local repo but documents at sync.automerge.org (also a local file system storage provider but I don't think that's relevant):
This quick mod is working for me so far (certainly not proposing this as the fix – I'm not familiar enough with the code for that):
|
Nice sleuthing @luxmentis, thank you. |
I jumped back in and read around on this, but I'm not yet confident on the sequence of calls to the repo that's leading to the failure scenario - and if these two patterns are identical. Can you help by giving me a little more detail in terms of the state of the two repositories, and what specific calls have been invoked on either side, and in which order? In the detail from @luxmentis, I think what I'm hearing is:
Is that correct @luxmentis? |
@jessegrosjean For the cloudCount scenario, could you point out what's happening when "you click the "Automerge Repo" checkbox?" What's that doing, and in what order in CloudCount? I got a bit lost reading through the additional layers in your example app space. Are there two repos in play here "A" and "B", both on local machines, or three "A", B", and "C" - the first two being macs and the last being an instance of the hosted automerge-repo in Javascript (sync.automerge.org or such?) I first read it as possible just two macs talking over Bonjour, but then I saw "websocket" and thought perhaps there's a third in here, and this is an issue with relaying updates through the server. |
@heckj Hmm, actually let's ignore my "first doc works, second doc doesn't" statement for the moment (which was partly about the order of things in my client code; explanation below). Now I wonder if I'm misunderstanding the API. Here's a simple example: import XCTest
@testable import AutomergeRepo
import Combine
final class SyncTest: XCTestCase {
private var subs = [AnyCancellable]()
func testExample() async throws {
let webSocketProvider = WebSocketProvider(WebSocketProviderConfiguration(reconnectOnError: true, loggingAt: .errorOnly))
let repo = await Repo(sharePolicy: .agreeable, storage: InMemoryStorage(), networks: [webSocketProvider])
try await webSocketProvider.connect(to: URL(string: "wss://sync.automerge.org/")!)
// a document with this id exists
try await syncAndDisplayHistory(id: "abFmaStDm3D7Yqh57ULbG5fwSoJ", repo: repo)
try? await Task.sleep(for: .seconds(60))
}
func syncAndDisplayHistory(id: String, repo: Repo) async throws {
let handle = try await repo.find(id: DocumentId(id)!)
handle.doc.objectWillChange.prepend(()).receive(on: DispatchQueue.main).sink {
print("\(id) history count: \(handle.doc.getHistory().count)")
}
.store(in: &subs)
}
} This creates an empty local doc but never fetches from the server (i.e. Is there something other than Part 2; possibly tangentialIn the original case I mentioned, the first fetch worked because I happened to be calling |
@luxmentis I took a bit of time and morphed your example test into one that would fit into the integration tests. Because they use a local docker instance of automerge-repo for hosting the WebSocket for their tests, it got updated and broken a up. I think the reason the prepend() is needed in your system is a slight missed expectation. As soon as you get the The (FWIW, the "expectation/fulfills setup in the tests can let them operate a TON faster than dropping the Take a look at the examples in the integration tests, feel free to run them yourself (you'll need to update them to use the public sync server, or run a local instance with Docker - there's a script in the repo for that though - As far as I'm concerned, there's no issue with you keeping the prepend() in your publisher if you're counting on that trigger to establish and trigger other business logic in your app, but it might be easier if you knew the history was up to date and ready to be used immediately on return from the |
Oh, for sure. Abusing the test format was just a quick and dirty way for me to get you some runnable code. Not intended as an actual test. Thanks for the integration test, which I've run and seen pass. I think we've ended up talking at cross-purposes a bit because my example code contained a publisher, in addition to the publisher in the original func testRepoWithStorage() async throws {
let repo = Repo(sharePolicy: .agreeable, storage: InMemoryStorage())
let websocket = WebSocketProvider()
await repo.addNetworkAdapter(adapter: websocket)
try await websocket.connect(to: URL(string: "wss://sync.automerge.org/")!)
let handle = try await repo.find(id: DocumentId("abFmaStDm3D7Yqh57ULbG5fwSoJ")!)
// This fails: history is empty at this point. But it would succeed if we weren't using the storage.
XCTAssertFalse(handle.doc.getHistory().isEmpty)
} So it turns out behaviour is different when the repo has storage. In this example, history is empty after the The empty history problem isn't specific to the use of InMemoryStorage – my app uses a custom FileSystemStorage implementation and behaves the same, which is what led me here. Hope that's clearer! |
Oh perfect, that's super useful! I'd not fully tested and iterated on the whole end to end interactions with storage providers other than the simple bits with the InMemory stuff in unit tests. This'll be perfect to dig into! Thanks for the code above, and for the detail that it's specific to when there IS a storage provider. That should help narrow it down and we'll get this sorted in some fashion. |
Sounds good. My emergency fix works for me for now. By the way, pretty neat sitting with my daughter doing some collaborative editing and watching each others' changes come up! |
I've got the repro set up - #113 Not only is the history not set and available (and hence, I'm sure the document contents aren't quite proper either), but the change notifications are failing when there's a storage provider attached to the repo that invoked the |
> @jessegrosjean For the cloudCount scenario, could you point out what's happening when "you click the "Automerge Repo" checkbox?" That ends up calling repo.import like this, from within SharingService.share
But I agree, lots of things happening in that code, and at the moment I'm working on another approach. I've tried to reproduce the problem in a simpler example. In this code I would expect documentA and documentB to converge, but that doesn't seem to be happening.
|
Yeah, slightly different pattern - there's definitely an issue with sync and |
Question for you @jessegrosjean - do have a local extension or something for the example above? The compiler had a little fit for |
Oops, yes. Just remove headsKey part and compare heads normally I think. |
Well, one of the issues is found anyway. On import (or create) there wasn't anything that was pro-actively kicking off an initial sync. It was only when the document was changed that it would be "written" out to any connected peers. In the PR, I've extracted the logic to request a sync into it's own private method and explicitly called it from I also went ahead and marked That solves Jesse's initial issue, but we still have the issue with sync failing to operate as expected when there's a storage adapter in the mix, which I'm still working on tracing & resolving. |
I think I have the fix in place. Need to sort out unit tests to matches updated behavior, but it's pretty close. Should have a fix out later today. |
…nvoked and the repo has a storage provider (#113) * reproduction of issue #106 * adding test from Jesse showing failure to initiate and automatically sync on two independent imports * explicitly initiate a sync on create, import, or clone with all connected peers * replacing integration test helper with upstream utility method * renaming test classes to distinguish more easily between single and dual client tests * fixing mismatched assumptions about documents being created automatically vs reporting they don't exist * updating tests to align with changed behaviors for sync on create/import/clone * in the scenario where a document is sync'd on creation, and a peer is connected, the other peer will have a record of the document to return an immediate result on find(), but the contents won't be completely up to date until the sync has processed. * The updated tests (unit and integration) verify that a sync happens, but there's no clear indicator of "I'm up to date with all my peers" right now.
@luxmentis The underlying issue - now fixed in the main branch - was that I had mismatched logic in the storage adapter interface. One side always returned a new Document, even if it wasn't available - which caused the resolver logic to never attempt to sync with other peers. That's been fixed, as well as documents are no proactively synced with all peers on creation, a change in behaviour from earlier setup. |
@heckj Looking good here. I'm currently chasing some other weirdness, but I don't think it's related. |
To reproduce the error I'm using CloudCount and:
At this point I would expect that second document's state to get synced with the web socket peer, but this isn't happening. To see the latest state from the peer I need to first modify the document... only then does syncing start to work.
The text was updated successfully, but these errors were encountered: