I don’t think I’m capable of fixing this myself, so I’m sharing my findings here. If you can’t look into it yourself at the moment, maybe you’ll want to post it on GitHub.
First of all, even when the sync button isn't blue, syncing may still not work properly. That is, regardless of how often you press Y, Anki will never reach a synchronised state and keep modifying the db on every call.
I think I've found the reason why this is happening:
Premise: A sync is performed. Now, the local and the remote collection have identical modified timestamps. We prompt another sync.
What should happen: Anki compares the two timestamps and finds that no sync is required.
What happens instead: The frontend runs self.db.execute("update col set mod=?", val) (triggered by collection.save) where val is a fresh timestamp. When the backend compares the local and remote modified timestamps now, the local one is more recent, so a sync is performed.
This happens most of the time, but not always and I can’t provide a reliable way to reproduce it. I’m also not sure why it only sometimes causes the sync button to be blue.
Thanks for digging into it. If you run Anki with ‘TRACESQL=1 ./run’, every sql statement will be printed out, and that should help identify what sql statement is being run that is marking the database as modified (and thus causing the modtime to update on save)
After further digging, I’m now certain that this bug is out of my reach (see below). So the following is my last report on it. But if there is anything else I can help with, let me know.
During syncing, we get a timestamp from the server:
However, when I print a locally generated timestamp immediately afterwards, I get
1607881544900
So the timestamp provided by the server is from the future.
This is a problem as can be demonstrated with some more printing during the next sync. There, we compare two timestamps:
Here, Anki gets the idea that the last local modification is more recent than the last db begin (start of database transaction):
db.mod: False; modified_after_begin: True
This triggers a db saving which in turn requires a sync.
What would have happened if the timestamp from the server had been smaller than the local timestamp as had been expected?
The mod timestamp (which is the server timestamp) would have been smaller than the begin timestamp, rendering modified_after_begin to False. Thus, no save, no sync.
Therefore, I’ve come to the conclusion that the cause of the never ending sync loop is on the server side.
As for the sync loop bug, I couldn’t reproduce it yet, but that doesn’t mean much. It appeared on some days, but not on others, seemingly independently of my actions. I’ll keep an eye on it and report back once it reoccurs or I am confident that it won’t.
Unfortunately, I had no trouble reproducing the other (?) bug, where the sync button is blue after the first sync upon start. It might be comeplety unrelated to the first one. I didn’t actually ascertain that it has the same cause.
I’ve just tried again while switching between versions and couldn’t reproduce the bug with the latest master whereas it appeared every time with the latest beta.
So either it’s better but not entirely fixed or you’ve fixed it completely and I have messed up earlier in which case I am sorry for the confusion.
I’ll keep an eye out, but in any case thanks for the patches!
Edit: Unfortunately, it's definitely the former case. The bug popped up again. I'll look into it.
That’s a shame. Configuration is not properly synced - #2 by Zuii is a report about add-ons marking the collection as changed, which could cause this - if you use any, it might be worth ruling them out?
I don’t have any add-ons enabled.
After db begin is set during the initial sync, there seem to be two database calls which are responsible for updating the mod stamp again:
sql: insert or replace into config (key, usn, mtime_secs, val) values ('activeDecks', -1, 1608712221, x'5b313535393539303330363434385d')
sql: insert or replace into config (key, usn, mtime_secs, val) values ('curDeck', -1, 1608712221, x'31353539353930333036343438')
I don’t know if that tells you anything. Of course, you can’t do much if you can’t reproduce the issue on your machine.
I’ll see what I can find out after the holidays.
col.decks.select() is called by .reset() after the sync finishes. If the deck to select and its children haven’t changed then it should be a no-op. Might be worth checking the if statement in that function and seeing what’s not matching - maybe activeDecks needs to be sorted before comparing?