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

Protect document state with recursive lock and add objectDidChange publisher #192

Merged
merged 2 commits into from
Jul 13, 2024

Conversation

jessegrosjean
Copy link
Contributor

@jessegrosjean jessegrosjean commented Jul 13, 2024

This gives Document an .objectDidChange publisher, that is fired in sync with and immediately after changes are made to a document. This required two changes:

  1. Protect document state with a NSRecursiveLock instead of a dispatch queue. This is needed because I want to be able to call back to the document from my .objectDidChange listener without creating deadlock in the queue.
  2. Add an optional (when combine is available) .objectDidChange publisher that always balances the current .objectWillChange publisher.

resolves #178

@heckj heckj requested review from heckj and alexjg July 13, 2024 16:40
try work()
}
#endif

#if canImport(Combine)
public let objectDidChange: PassthroughSubject<(), Never> = .init()
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you add a bit of doc comment here highlighting that this is triggered explicitly after the document has been updated, and use your use-case as a "for example"? i.e. "Sends a signal that the document has been updated. For example, you can use this signal to retrieve heads for the document to capture the state after you applied changes." - or something like that?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but I'm just in the middle of a mad summer dash, so won't get to it today, but will do.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Jesse - I'll go ahead and merge this and add the docs after.

@alexjg
Copy link
Collaborator

alexjg commented Jul 13, 2024

I think this is all good. I have a slightly uncomfortable feeling about the re-entrancy because we use an RwLock on the rust side to ensure exclusive access to the document and we panic if we can't obtain the lock because we know that on the Swift side we're ensuring exclusive access. However, I think the re-entrancy here would never happen when we're holding the lock on the Rust side so we're probably okay.

To make that concrete, here's put_in_map

    pub fn put_in_map(&self, obj: ObjId, key: String, value: ScalarValue) -> Result<(), DocError> {
        let obj = am::ObjId::from(obj);
        let mut doc = self.0.write().unwrap();
        assert_map(&*doc, &obj)?;
        doc.put(obj, key, value).map_err(|e| e.into())
    }

(self.0 is an RwLock<automerge::AutoCommit>)

You can see here that we panic if we can't obtain the lock (the self.0.write().unwrap() call). Now, none of the methods on Document return anything which refers to the document - and I don't think there is any way we could return such a reference as Uniffi won't let us - so we're okay. So, despite my uneasiness, I think this works.

@heckj
Copy link
Collaborator

heckj commented Jul 13, 2024

On the Apple platforms side, NSRecursiveLock (the lock in question here) only allows the same thread to pass through the lock, so I think it prevents the bits where we'd get re-entrancy across multiple threads, and with a single thread access we'd be fine - it would come through to the Rust side as serialized calls to the llibrary since they're all coming from a single thread. Other lock implementations here could be very poor, so I'd want to not replace this with a "cheaper lock" down the road, as the different semantics could easily bite us, but this I think is going to be fine - a good choice for the pain point/use-case that Jesse's hitting.

@heckj heckj merged commit e2bb692 into automerge:main Jul 13, 2024
4 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.

Add doc.objectDidChange?
3 participants