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

Filter Empty Updates from YDoc.observe_after_transaction #104

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions examples/drawing/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ def async_loop():

def send_updates(self, txn_event: Y.AfterTransactionEvent):
update = txn_event.get_update()
# Sometimes transactions don't write, which means updates are empty.
# We only care about updates with meaningful mutations.
if update != b'\x00\x00':
self.send_q.put_nowait(update)
self.send_q.put_nowait(update)

def apply_updates(self, doc: Y.YDoc):
while not self.recv_q.empty():
Expand Down
14 changes: 8 additions & 6 deletions src/y_doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,14 @@ impl YDoc {
pub fn observe_after_transaction(&mut self, callback: PyObject) -> SubscriptionId {
self.0
.observe_transaction_cleanup(move |txn, event| {
Python::with_gil(|py| {
let event = AfterTransactionEvent::new(event, txn);
if let Err(err) = callback.call1(py, (event,)) {
err.restore(py)
}
})
if event.before_state != event.after_state {
Python::with_gil(|py| {
let event = AfterTransactionEvent::new(event, txn);
if let Err(err) = callback.call1(py, (event,)) {
err.restore(py)
}
})
}
})
.into()
}
Expand Down
33 changes: 32 additions & 1 deletion tests/test_y_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ def test_get_update():
"""
d = Y.YDoc()
m = d.get_map("foo")
r = d.get_map("foo")
update: bytes = None

def get_update(event: AfterTransactionEvent) -> None:
Expand All @@ -126,3 +125,35 @@ def get_update(event: AfterTransactionEvent) -> None:
m.set(txn, "hi", "there")

assert type(update) == bytes


def test_empty_updates_observe_after_transaction():
"""
Transactions should be observable only when they are used to update the CRDT state.
If a transaction does not differ between its before and after state, it should be ignored.
"""
doc = Y.YDoc()
items = doc.get_array("items")

def handle_update(e: Y.AfterTransactionEvent):
update = e.get_update()
# Ensures observe_after_transaction is only called on contentful updates.
assert update != b'\x00\x00'

doc.observe_after_transaction(handle_update)
# Updating the document state should result in a non empty update.
with doc.begin_transaction() as txn:
items.append(txn, "This triggers an AfterTransactionEvent")

# The following methods create an empty transaction in order to perform their operation.
# Check that none of these yield an empty update.
Y.encode_state_vector(doc)
update = Y.encode_state_as_update(doc)
Y.apply_update(doc, update)
doc.get_array("array")
doc.get_map("map")
doc.get_text("text")
doc.get_xml_element("xml_el")
doc.get_xml_text("xml_text")