Skip to content

jaanus/DoubleSharedDatabaseNotifications

Repository files navigation

Double Shared Database Notifications

This repo exists specifically to analyze a CloudKit database-subscription bug where adding a child record can produce two visible database notifications even though only one useful zone-change payload exists.

Also filed to Apple as FB22866852.

The toy app models a small hierarchy:

  • Chat
  • Message, parented by Chat
  • Reaction, parented by Message

The repro case is: create a Message, then add a Reaction to that newly created message. The first mutation produces one push. The second mutation produces two pushes. The first reaction push fetches the new Reaction plus the parent Message again. The second reaction push reports a changed zone at the database-changes layer, but fetching zone changes for that zone returns no changed or deleted records.

This app intentionally logs the whole change-flow path: incoming notification, subscription ID, database scope, serialized change-fetch number, database changes, zone changes, changed record types, changed record IDs, and local upserts.

Observed Bug

Fresh private-database repro log:

Received remote notification
Push #1 CloudKit notification type: 4, subscription: private-database-visible-subscription
Database notification scope: private
Change fetch #2 scheduled because: push #1; scopes: private
Change fetch #2 starting
Loaded database token for private, has token: true
Stored database token for private, has token: true
Database changes for private: changed 1, deleted 0, purged 0
Fetching zone changes for 1 private zones
Loaded zone token for Chats/__defaultOwner__, has token: true
Stored zone token for Chats/__defaultOwner__, has token: true
Zone changes for private: changed 1, deleted 0
Changed records for private by type: Message: 1
Changed record IDs for private: Message:4F4E8BE7-3247-4721-B673-619D8930D9E3
Upserted message 4F4E8BE7-3247-4721-B673-619D8930D9E3
Change fetch #2 finished

That is the initial message insert. It produces one push and one changed Message, which is expected.

Received remote notification
Push #2 CloudKit notification type: 4, subscription: private-database-visible-subscription
Database notification scope: private
Change fetch #3 scheduled because: push #2; scopes: private
Change fetch #3 starting
Loaded database token for private, has token: true
Stored database token for private, has token: true
Database changes for private: changed 1, deleted 0, purged 0
Fetching zone changes for 1 private zones
Loaded zone token for Chats/__defaultOwner__, has token: true
Stored zone token for Chats/__defaultOwner__, has token: true
Zone changes for private: changed 2, deleted 0
Changed records for private by type: Message: 1, Reaction: 1
Changed record IDs for private: Reaction:41AE3D75-CECF-4DE3-8623-BE0AED17A1F7, Message:4F4E8BE7-3247-4721-B673-619D8930D9E3
Upserted reaction 41AE3D75-CECF-4DE3-8623-BE0AED17A1F7
Upserted message 4F4E8BE7-3247-4721-B673-619D8930D9E3
Change fetch #3 finished

That is the first push after adding a reaction to the newly inserted message. It reports the expected Reaction, but also reports the parent Message again.

Received remote notification
Push #3 CloudKit notification type: 4, subscription: private-database-visible-subscription
Database notification scope: private
Change fetch #4 scheduled because: push #3; scopes: private
Change fetch #4 starting
Loaded database token for private, has token: true
Stored database token for private, has token: true
Database changes for private: changed 1, deleted 0, purged 0
Fetching zone changes for 1 private zones
Loaded zone token for Chats/__defaultOwner__, has token: true
Stored zone token for Chats/__defaultOwner__, has token: true
Zone changes for private: changed 0, deleted 0
Changed records for private: none
Change fetch #4 finished

That is the second push after the same reaction insert. fetchDatabaseChanges reports one changed zone, but fetchZoneChanges returns no changed or deleted records. The app serializes change fetches, so this is not caused by overlapping token reads/writes.

Expected behavior: adding one Reaction should result in one database notification with one useful zone-change payload.

Observed behavior: adding one Reaction to a newly created Message can result in two database notifications. The second notification is empty after fetching zone changes.

Toy CloudKit chat app

The Xcode project contains two SwiftUI targets that share the same app code:

  • DSDN iOS
  • DSDN macOS

The app uses SwiftData as a local-only cache/token store and Canopy for all CloudKit operations. On launch it creates a private Chats zone, installs visible CKDatabaseSubscriptions on the private and shared databases, fetches database changes, fetches zone changes, and stores all change tokens in SwiftData.

Before running on a signed device or Mac, update the iCloud container in:

  • DoubleSharedDatabaseNotifications/Resources/DoubleSharedDatabaseNotifications.entitlements
  • DoubleSharedDatabaseNotifications/Services/CloudKitConstants.swift
  • the app target signing settings if your bundle ID or team differs

The sample container is currently iCloud.com.justtact.DoubleSharedDatabaseNotifications.

CloudKit record types used by the toy app:

  • Chat: name, owningChat
  • Message: encrypted text, owningChat, parent Chat
  • Reaction: emoji, owningChat, parent Message

Chat deletion intentionally queries and deletes Reaction records first, then Message records, and only then the Chat root record.

About

Toy CloudKit app to analyze a notifications bug

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages