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

Retry PUT 409 errors - delete and re-create event #964

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

telotortium
Copy link
Contributor

Attempt to address #963

I would appreciate a code review here - there might be a more elegant way to address this problem, but it works for my (very limited) use case.

Comment on lines 590 to 604
try:
href, etag = await self._put(self._normalize_href(href), item, etag)
except aiohttp.ClientResponseError as e:
if e.status == 409:
dav_logger.debug("Conflict, will delete old event and recreate it.")
await self.delete(self._normalize_href(href), None)
dav_logger.debug("Now trying again")
href, etag = await self._put(self._normalize_href(href), item, None)
else:
raise e
Copy link
Member

Choose a reason for hiding this comment

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

This is not correct, 409 indicates that the server's copy of the item has changed since we last read it. Deleting-and-then-updating is essentially overwriting the conflicting copy, effectively resulting in data loss.

Maybe this is happening due to race conditions with concurrent edits? In such cases, the correct thing to do is run the sync again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In my case, I have the following ~/.config/vdirsyncer/config:

[general]
status_path = "~/.cache/vdirsyncer/status/"

[storage caldav]
type = "caldav"
url = # redacted
username = # redacted
password = # redacted
read_only = true
item_types = ["VEVENT"]

[storage gcal]
type = "google_calendar"
token_file = "~/.cache/vdirsyncer/gcal_robert_work.token"
client_id = # redacted
client_secret = # redacted
item_types = ["VEVENT"]

[pair caldav_to_gcal]
a = "caldav"
b = "gcal"

# Robert work (new)
collections = [["caldav_to_gcal", "a_calendar_id", "b_calendar_id@group.calendar.google.com"]]

conflict_resolution = "a wins"
partial_sync = "ignore"

So in my case, since my caldav server is set to read-only and I have conflict resolution set to a wins, I actually don't care about the old event, and I'm completely fine with deleting it.

Comment on lines 604 to 618
try:
rv = await self._put(href, item, None)
except aiohttp.ClientResponseError as e:
if e.status == 409:
dav_logger.debug("Conflict, will delete old event and recreate it.")
await self.delete(href, None)
dav_logger.debug("Now trying again")
rv = await self._put(href, item, None)
else:
raise e
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as above.

@@ -547,7 +547,7 @@ async def get_multi(self, hrefs):
else:
rv.append((href, Item(raw), etag))
for href in hrefs_left:
raise exceptions.NotFoundError(href)
Copy link
Member

Choose a reason for hiding this comment

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

get_multi receives a set of ids and currently returns items with those ids. In this case, one of them is missing -- but callers of this function will expect it to be returned.

I understand the rationale here, but I've an impression that this change will break things for other places using get_multi.

@telotortium
Copy link
Contributor Author

@WhyNotHugo Thanks for your comments. I agree that I need to figure out how to make these changes more correct before merging, but I don't understand CalDAV or the code well enough to know what I need to do. These hacks are just what I need to get the sync in my configuration working.

@WhyNotHugo
Copy link
Member

A few tests are stil failing: https://builds.sr.ht/~whynothugo/job/776820#task-test-134

@g0rdonL

This comment was marked as off-topic.

telotortium and others added 3 commits March 14, 2023 17:27
Attempt to address pimutils#963

I would appreciate a code review here - there might be a more elegant way to address this problem, but it works for my (very limited) use case.
Rather, just log them. This allows my calendar sync to continue to work
even if some of the hrefs can't be retrieved. In my case, one of the
hrefs on my calendar was always returning a 404, so I want to skip it.
@telotortium telotortium force-pushed the patch-1 branch 3 times, most recently from 2b0af6b to 3017744 Compare March 15, 2023 00:41
@WhyNotHugo
Copy link
Member

Basically each time that vdirsyncer reads an event, we also get an Etag, which can be though of as a version identifier. The Etag for an event changes each time that its content changes. When vdirsyncer tries to delete or alter an event, it specifies "only if the Etag is equal to XXX". This avoids deleting an event if someone else has modified it after vdirsyncer last read it.

A 409 error means that we tried a write operation on an event assuming it had not changed, but it has changed. If this keeps happening over and over, it likely means that there is an issue elsewhere in the logic. But in the general case, ignoring a 409 error means that you're overwriting or deleting files which have been modified by someone else, and will result in a data loss.

I honestly haven't looked deeply into the cause of this bug because it sounds very non-trivial, and I'd rather focus energy on the rewrite which should either not have this bug, or be a lot easier to debug.

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.

None yet

3 participants