Google-Script-Contacts-Sync icon indicating copy to clipboard operation
Google-Script-Contacts-Sync copied to clipboard

Script blows up when Google says thousands of contacts have been updated.

Open knwpsk opened this issue 5 months ago • 1 comments

Sometimes google decides to (falsely) mark "all" of my contacts as being modified, and the sync process blows up. I think I know why, and I think I know what to do about it. Need confirmation and an answer to one question.

Background: Been using the script for a long time, usually fine, but every once in a while something goes nuts. Last week I had this happen again. The sequence goes like this:

  • syncContacts kicks off from the trigger.
  • GetUpdatedContacts runs, and passes the call to People API for updated contacts, and google returns 4298 records -- essentially all of my Contacts. GUC manages to write all of them to the sync account tab on my sheets file. PageToken should be null (I'm going to add logging to check that). Elapsed time: 154 seconds. Return to main function SyncContacts.
  • SyncContacts now calls UpdatesMerge, and it begins. It processes around 300 rows, and stops because we're running out of script time (300 second limit in the function). It deletes those 300 rows from the syncaccount tab, leaving around 4000 rows to do next time.

Important note here: UpdatesMerge does not return any value to SyncContacts, and SyncContacts does not look for any return value. So at the top level, we're not aware that UpdatesMerge didn't finish.

  • SyncContacts now calls SpreadsheetToContacts. Again, it does not expect any return value from STC, even though STC does return one.
  • SpreadsheetToContacts only looks at the first row of data, because on the first iteration it finds that the script is already running out of time. No updates get processed to my gmail Contacts (which is OK, for now, not directly relevant to this problem).
  • Back to SyncContacts again. It calls RefreshSyncToken.
  • SyncTokenExpired is false, pageToken is presumably null, and syncToken is the same value we started with. Now we get Connections with those values and loop through them all. (I don't quite understand why. The results here should be nil, since we already cycled through all of them in GetUpdatedContacts, right?) In my logging, it does this do-while loop twice (two pages of results).
  • Assigns new syncToken.
  • Return to syncContacts. Finished this run.

Now, on the next run, GetUpdatedContacts finds 4298 contacts to update in my gmail contacts, again. It adds them to the sync account tab, which already had 4000 contacts from the previous run, so now we have over 8000 of them. MergeContacts processes another 300 rows. Etc. This keeps happening with each trigger, so my sync account tab keeps growing out of control.

So, I sortof think that SyncContacts needs to first examine what's in the sync account's tab, and if it's nonzero, then skip calling GetUpdatedContacts this time. Just go right to UpdatesMerge, and keep doing that iteratively until all changes are merged into the main tab. Then on the run after that, it till find no more updates in the tab, and it can call GetUpdatedContacts again.

But, there's one part of this I still don't get. Why is the second run finding 4298 updated contacts again? Is this a bug? (Edit next day) I deleted my synctoken and all the sync account tab records, and ran SyncContacts again, and watched. I realized what I missed yesterday: when there are this many (superfluous) updates, the script times out at the end of RefreshSyncToken, and it misses the last steps. Namely these:

var newSyncToken = connections.nextSyncToken; syncTokenFile.setContent(newSyncToken); return (newSyncToken);

So, I suspect this is the explanation for why, on subsequent runs, the script finds the same 4298 "updates" all over again. A bit more script-time-management protection is needed here. I still don't yet understand why refreshSyncToken is paging through all of the Connections, when getUpdatedContacts already did that...?

knwpsk avatar Aug 08 '25 00:08 knwpsk

I couldn't find it in the People/Contacts api docs. But I found this in the Calendar api: https://developers.google.com/workspace/calendar/api/guides/sync "In the response to the list operation, you will find a field called nextSyncToken representing a sync token. You'll need to store the value of nextSyncToken. If the result set is too large and the response gets paginated, then the nextSyncToken field is present only on the very last page."

I think this explains why RefreshSyncToken has to page through the whole list (again) -- to get nextSyncToken. But I think maybe there's a more efficient way to do this if we re-examine how getUpdatedContacts works? Grab the value of nextSyncToken then, and pass it around? TBD.

Facts:

  • getUpdatedContacts takes 3.5 minutes to run when I have 4300 updated contacts.
  • refreshSyncToken takes about 2.5 minutes to run " " ".
  • These two things alone add up to around 6 minutes, the total allowed GAS runtime, leaving no time for the other major functions (merge and update), without hitting a timeout.

Also: sometimes GAS logs what looks like an unhandled script failure, saying "The JavaScript runtime exited unexpectedly." Given my observations, I think most of the time this is just a 6-minute timeout. (There was one time I got that logging after only ~3 minutes, though.)

knwpsk avatar Aug 08 '25 17:08 knwpsk