I have an issue with pull syncing, that I think is in my swift code or library.
This used to work, and somehow I’ve broken it and I don’t know how because (I think) the basic sync code has been untouched. I’m hoping that someone can provide a little guidance as to how to debug syncing and to interpret the logs.
Background
I have 3 types of data:
- public. Your public PKE keys and password salts. The channel is “keys”, and everyone including GUEST has access.
- private. Your data and private PKE keys. The channel is the same as your uid, and only you have access.
- shared. Any info I share with you, by adding your uid (channel ID) to the channels for that doc.
I can create a user:
- user account is set up in Sync Gateway. I can see it in the web UI.
- a public record is created with channel [“keys”] - GUEST can access this. I can see that in the web UI
- a set of private records are created with channel [my uid], and only I can access these.
In the sync gateway UI I can see the user, the channels and the right data shown in the channel itself (channels tab). This all looks good.
I can also test the data by running this on the command line:
curl -X GET http://localhost:4984/<db>/_changes
This returns only the public data, because its logging in as guest.
I can also do this:
curl -X GET http://<uid>:<pwd>@localhost:4984/<db>/_changes?feed=continuous
I can then push some changes from my app, and I can see the changes reflected in the curl output as they happen.
Ok - by now I’m hoping that I have convinced you that the data is structured perfectly in couchbase/sync gateway. . Push is working perfectly.
The Issue
If I install the app on desktop or mobile “A”, create some data, then install the app on desktop or mobile “B” and enter my ID/pwd, I should have the data sync’d to the new device. This used to work!
But now pull syncing doesn’t seem to work anymore. What happens is nothing is returned to the app and the app and sync_gateway both go into a weird infinite loop.
I hate to dump a ton of code… but this is my sync code (shamelessly copied from sample Grocery App Swift code):
func startSyncProcesses() {
guard let user = UserManager.user else { abort() }
debugPrint("Starting replication")
_push = setupReplication(gDB.createPushReplication(gServerURL))
_pull = setupReplication(gDB.createPullReplication(gServerURL))
var auth: CBLAuthenticatorProtocol?
auth = CBLAuthenticator.basicAuthenticatorWithName(user.id, password: user.pass_hash)
_push.authenticator = auth
_pull.authenticator = auth
// Only pull files with my id in their channels (note: we dont want to pull keys, even though
// I have access to them). Push, on the other hand, is for everything I have access to.
_pull.channels = [user.id] // channel id == user.id
_push.start()
_pull.start()
}
func setupReplication(replication: CBLReplication!) -> CBLReplication! {
if replication != nil {
replication.continuous = true
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(MainView.replicationProgress(_:)),
name: kCBLReplicationChangeNotification,
object: replication)
}
return replication
}
func replicationProgress(n: NSNotification) {
debugPrint("ReplicationProcess")
if (_pull.status == CBLReplicationStatus.Active || _push.status == CBLReplicationStatus.Active) {
debugPrint("Sync is active")
// Sync is active -- aggregate the progress of both replications and compute a fraction:
let completed = _pull.completedChangesCount + _push.completedChangesCount
let total = _pull.changesCount + _push.changesCount
debugPrint("SYNC progress: \(completed) / \(total)")
notesListView!.syncStatusImage.hidden = false
notesListView!.syncStatusImage.image = NSImage(named:"NSRefreshTemplate")
let percent: Int = total == 0 ? 100 : Int(completed*100/total)
notesListView!.syncStatusImage.toolTip = "Sync in progress...\(percent)"
} else {
// Sync is idle -- hide the progress bar:
debugPrint("Sync is idle")
notesListView!.syncStatusImage.hidden = true
}
// Check for any sync errors
let error = _pull.lastError ?? _push.lastError
if error != nil {
debugPrint("Error syncing", error)
notesListView!.syncStatusImage.hidden = false
notesListView!.syncStatusImage.image = NSImage(named:"NSCaution")
notesListView!.syncStatusImage.toolTip = "Error connecting to sync server"
}
}
Push works perfectly, but pull does not. If I have 100 records to push, I can see the notifications happen as push syncing goes ahead.
When pull syncing starts, here is the debug log created by the code above (i.e. sync reports nothing to do).
"Starting replication"
"ReplicationProcess"
"Sync is idle"
"ReplicationProcess"
"Sync is idle"
"ReplicationProcess"
"Sync is idle"
"ReplicationProcess"
"Sync is active"
"SYNC progress: 0 / 0"
"ReplicationProcess"
"Sync is idle"
On the sync server… the following log entries are created:
First I pull the public key information (ID is -key). This must be done as guest as I haven’t established auth yet. I do this with a direct http GET to the sync gateway
2016-06-14T12:25:11.808+12:00 HTTP: #961: GET /private/AXYg7VG9edz6VlUGGNY1qn-key
2016-06-14T12:25:11.809+12:00 HTTP+: #961: --> 200 (1.7 ms)
Then I use the ID/pwd to pull the first block of private information, again via a GET. This works and proves that I have the right ID and password.
2016-06-14T12:25:11.833+12:00 HTTP: #962: GET /private/AXYg7VG9edz6VlUGGNY1qn (as AXYg7VG9edz6VlUGGNY1qn)
2016-06-14T12:25:11.835+12:00 HTTP+: #962: --> 200 (3.1 ms)
I’m guessing the “(as AXYg7VG9edz6VlUGGNY1qn)” implies that auth worked.
Next I start the sync process using the code above. Here is what happens at the server:
2016-06-14T12:25:12.131+12:00 HTTP: #963: POST /private/_changes
2016-06-14T12:25:12.131+12:00 Changes+: Int sequence multi changes feed...
2016-06-14T12:25:12.131+12:00 Changes: MultiChangesFeed({AXYg7VG9edz6VlUGGNY1qn}, {Since:0 Limit:0 Conflicts:true IncludeDocs:false Wait:false Continuous:false Terminator:0xc820bae3c0 HeartbeatMs:300000 TimeoutMs:300000 ActiveOnly:true}) ...
2016-06-14T12:25:12.131+12:00 Changes+: MultiChangesFeed: channels expand to channels.TimedSet{} ...
2016-06-14T12:25:12.131+12:00 Changes+: MultiChangesFeed sending &{Seq:1 ID:_user/GUEST Deleted:false Removed:{} Doc:map[] Changes:[] Err:<nil> allRemoved:false branched:false}
2016-06-14T12:25:12.132+12:00 Changes: MultiChangesFeed done
2016-06-14T12:25:12.132+12:00 HTTP+: #963: --> 200 OK (0.0 ms)
2016-06-14T12:25:12.136+12:00 HTTP: #964: POST /private/_changes
2016-06-14T12:25:12.136+12:00 Changes+: Int sequence multi changes feed...
2016-06-14T12:25:12.136+12:00 Changes: MultiChangesFeed({AXYg7VG9edz6VlUGGNY1qn}, {Since:1 Limit:0 Conflicts:true IncludeDocs:false Wait:true Continuous:false Terminator:0xc820bae8a0 HeartbeatMs:300000 TimeoutMs:300000 ActiveOnly:false}) ...
2016-06-14T12:25:12.137+12:00 Changes+: MultiChangesFeed: channels expand to channels.TimedSet{} ...
2016-06-14T12:25:12.137+12:00 Changes+: MultiChangesFeed waiting...
2016-06-14T12:25:12.137+12:00 Changes+: Waiting for "private"'s count to pass 12
2016-06-14T12:25:12.137+12:00 Changes: MultiChangesFeed done
2016-06-14T12:25:12.137+12:00 HTTP+: #964: --> 200 OK (0.0 ms)
This repeats for ever and very very fast (you can see its happening every few ms).
My Questions:
- Does “MultiChangesFeed({AXYg7VG9edz6VlUGGNY1qn}” refer to the channel? If so, that’s correct.
- Does “ID:_user/GUEST” imply auth has failed and sync_gateway is falling back to using guest? As you can see from my code above - I am using a basic auth object for push and pull, and it does work for the push.
- It says "Continuous:false ", but I set that to true in the code
- Is the sync upset by a previous call to the REST API (I use NSURLSessionConfiguration.ephemeralSessionConfiguration(), so it “shouldn’t”.
- Why does a failed pull sync cause such a crazy loop?
Is there anything else I can look for that might help sort this out?
Many thanks for any advice you can provide.
Cheers.
Paul
p.s. tried this with sync_gateway on mac and Ubuntu… same results. Also tried deleting and recreating the bucket. I’m positive this is something in my code… but what?