Skip to content
This repository has been archived by the owner on Feb 29, 2020. It is now read-only.

[iOS] Returning operations from sync actions #709

Merged
merged 9 commits into from May 26, 2015
2 changes: 1 addition & 1 deletion sdk/iOS/src/MSSyncContext.h
Expand Up @@ -109,7 +109,7 @@ typedef void (^MSSyncPushCompletionBlock)(void);
@property (nonatomic, readonly) NSUInteger pendingOperationsCount;

/// Executes all current pending operations on the queue
- (void) pushWithCompletion:(MSSyncBlock)completion;
- (NSOperation *) pushWithCompletion:(MSSyncBlock)completion;

/// Specifies the delegate that will be used in the resolution of syncing issues
@property (nonatomic, strong) id<MSSyncContextDelegate> delegate;
Expand Down
50 changes: 29 additions & 21 deletions sdk/iOS/src/MSSyncContext.m
Expand Up @@ -82,7 +82,7 @@ -(NSUInteger) pendingOperationsCount
/// Begin sending pending operations to the remote tables. Abort the push attempt whenever any single operation
/// recieves an error due to network or authorization. Otherwise operations will all run and all errors returned
/// to the caller at once.
-(void) pushWithCompletion:(MSSyncBlock)completion
-(NSOperation *) pushWithCompletion:(MSSyncBlock)completion
{
// TODO: Allow users to cancel operations
MSQueuePushOperation *push = [[MSQueuePushOperation alloc] initWithSyncContext:self
Expand All @@ -91,6 +91,8 @@ -(void) pushWithCompletion:(MSSyncBlock)completion
completion:completion];

[pushQueue_ addOperation:push];

return push;
}


Expand Down Expand Up @@ -321,7 +323,7 @@ - (void) cancelOperation:(MSTableOperation *)operation discardItemWithCompletion
}

/// Verify our input is valid and try to pull our data down from the server
- (void) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion;
- (NSOperation *) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion;
{
// make a copy since we'll be modifying it internally
MSQuery *queryCopy = [query copy];
Expand Down Expand Up @@ -371,7 +373,7 @@ - (void) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(
completion(error);
}];
}
return;
return nil;
}

// Get the required system properties from the Store
Expand Down Expand Up @@ -404,54 +406,56 @@ - (void) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(
queryCopy.fetchLimit = MIN(maxRecords, defaultFetchLimit);

// Begin the actual pull request
[self pullWithQueryInternal:queryCopy queryId:queryId maxRecords:maxRecords completion:completion];
return [self pullWithQueryInternal:queryCopy queryId:queryId maxRecords:maxRecords completion:completion];
}

/// Basic pull logic is:
/// Check if our table has pending operations, if so, push
/// If push fails, return error, else repeat while we have pending operations
/// Read from server using an MSQueuePullOperation
- (void) pullWithQueryInternal:(MSQuery *)query queryId:(NSString *)queryId maxRecords:(NSInteger)maxRecords completion:(MSSyncBlock)completion
- (NSOperation *) pullWithQueryInternal:(MSQuery *)query queryId:(NSString *)queryId maxRecords:(NSInteger)maxRecords completion:(MSSyncBlock)completion
{
MSQueuePullOperation *pull = [[MSQueuePullOperation alloc] initWithSyncContext:self
query:query
queryId:queryId
maxRecords:maxRecords
dispatchQueue:writeOperationQueue
callbackQueue:self.callbackQueue
completion:completion];

dispatch_async(writeOperationQueue, ^{
// Before we can pull from the remote, we need to make sure out table doesn't having pending operations
NSArray *tableOps = [self.operationQueue getOperationsForTable:query.table.name item:nil];
if (tableOps.count > 0) {
[self pushWithCompletion:^(NSError *error) {
NSOperation *push = [self pushWithCompletion:^(NSError *error) {
// For now we just abort the pull if the push failed to complete successfully
// Long term we can be smarter and check if our table succeeded
if (error) {
[pull cancel];
Copy link
Contributor

Choose a reason for hiding this comment

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

Are you sure calling cancel is enough? Doesn't that just set the isCancelled flag? The op itself wouldn't set isFinished until it goes to run. What flag is the dependent Op observing?

Copy link
Contributor

Choose a reason for hiding this comment

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

Cancelling the NSOperation would mark it finished, so the observing task should be fine.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The operation wouldn't have even started at this point, so I don't think we need to do anything else. The completion block will be called if the user has provided one to notify them of the failure.

I could do some testing though with the returned pull operation as a dependency of another operation. If the execution flows through to the waiting operation when this is cancelled, should all be fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you link to docs? Everything I see online, says that the behavior of cancel isn't different if the operation is not running. So if you want to trigger a dependent task, you are responsible for setting the isFinished flag as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Running through a unit test, you were right @phvannor that without explicitly setting the finished status, the dependent tasks wouldn't be completed.

Latest commit with changes to access internal method for completion plus unit test for this case added.

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding UT to test this path.
Btw, should we not simply add the pull NSOperation to the queue immediately after making it dependent on the push NSOperation (instead of waiting for the push to complete successfully)? That way you won't need to call 'completeOperation' explicitly. Just calling cancel ( [push cancel] ) will be enough.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sadly, then the pull would possibly already be running, so we'd actually need the push op to expose its error state, so the pull op could look at its dependencies & verify they are were ok before running. Probably worth looking into, but not sure on effort to do so vs what we have now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right!


if (completion) {
[self.callbackQueue addOperationWithBlock:^{
completion(error);
}];
}
} else {
// Check again if we have new pending ops while we synced, and repeat as needed
[self pullWithQueryInternal:query queryId:queryId maxRecords:maxRecords completion:completion];
[pushQueue_ addOperation:pull];
}
}];
return;
} else {
// TODO: Allow users to cancel operations
MSQueuePullOperation *pull = [[MSQueuePullOperation alloc] initWithSyncContext:self
query:query
queryId:queryId
maxRecords:maxRecords
dispatchQueue:writeOperationQueue
callbackQueue:self.callbackQueue
completion:completion];


[pull addDependency:push];
} else {
[pushQueue_ addOperation:pull];
}
});

return pull;
}

/// In order to purge data from the local store, purge first checks if there are any pending operations for
/// the specific table on the query. If there are, no purge is performed and an error returned to the user.
/// Otherwise clear the local table of all macthing records
- (void) purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion
- (NSOperation *) purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion
{
MSQueuePurgeOperation *purge = [[MSQueuePurgeOperation alloc] initPurgeWithSyncContext:self
query:query
Expand All @@ -460,11 +464,13 @@ - (void) purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion
callbackQueue:self.callbackQueue
completion:completion];
[pushQueue_ addOperation:purge];

return purge;
}

/// Purges all data, pending operations, operation errors, and metadata for the
/// MSSyncTable from the local store.
-(void) forcePurgeWithTable:(MSSyncTable *)syncTable completion:(MSSyncBlock)completion
-(NSOperation *) forcePurgeWithTable:(MSSyncTable *)syncTable completion:(MSSyncBlock)completion
{
MSQuery *query = [[MSQuery alloc] initWithSyncTable:syncTable];
MSQueuePurgeOperation *purge = [[MSQueuePurgeOperation alloc] initPurgeWithSyncContext:self
Expand All @@ -474,6 +480,8 @@ -(void) forcePurgeWithTable:(MSSyncTable *)syncTable completion:(MSSyncBlock)com
callbackQueue:self.callbackQueue
completion:completion];
[pushQueue_ addOperation:purge];

return purge;
}

+ (BOOL) dictionary:(NSDictionary *)dictionary containsCaseInsensitiveKey:(NSString *)key
Expand Down
6 changes: 3 additions & 3 deletions sdk/iOS/src/MSSyncContextInternal.h
Expand Up @@ -25,11 +25,11 @@

-(void) readWithQuery:(MSQuery *)query completion:(MSReadQueryBlock)completion;

-(void) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion;
-(NSOperation *) pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion;

-(void) purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion;
-(NSOperation *) purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion;

-(void) forcePurgeWithTable:(MSSyncTable *)syncTable completion:(MSSyncBlock)completion;
-(NSOperation *) forcePurgeWithTable:(MSSyncTable *)syncTable completion:(MSSyncBlock)completion;


#pragma mark * Operation Helpers
Expand Down
14 changes: 9 additions & 5 deletions sdk/iOS/src/MSSyncTable.h
Expand Up @@ -6,6 +6,9 @@
#import "MSClient.h"
#import "MSTable.h"

@class MSQueuePullOperation;
@class MSQueuePurgeOperation;

/// The *MSSyncTable* class represents a table of a Windows Azure Mobile Service.
/// Items can be inserted, updated, deleted and read from the table. The table
/// can also be queried to retrieve an array of items that meet the given query
Expand Down Expand Up @@ -94,19 +97,20 @@

/// Initiates a request to go to the server and get a set of records matching the specified
/// MSQeury object.
/// Before a pull is allowed to run, all pending requests on the specified table will be sent to
/// the server. If a pending request for this table fails, the pull will be cancelled
-(void)pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion;
/// Before a pull is allowed to run, one operation to send all pending requests on the
/// specified table will be sent to the server. If a pending request for this table fails,
/// the pull will be cancelled
-(NSOperation *)pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion;

/// Removes all records in the local cache that match the results of the specified query.
/// If query is nil, all records in the local table will be removed.
/// Before local data is removed, a check will be made for pending operations on this table. If
/// any are found the purge will be cancelled and an error returned.
-(void)purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion;
-(NSOperation *)purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion;

/// Purges all data, pending operations, operation errors, and metadata for the
/// MSSyncTable from the local cache.
-(void)forcePurgeWithCompletion:(MSSyncBlock)completion;
-(NSOperation *)forcePurgeWithCompletion:(MSSyncBlock)completion;

/// @}

Expand Down
14 changes: 7 additions & 7 deletions sdk/iOS/src/MSSyncTable.m
Expand Up @@ -59,28 +59,28 @@ -(void)delete:(NSDictionary *)item completion:(MSSyncBlock)completion
#pragma mark * Public Local Storage Management commands


-(void)pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion
-(NSOperation *)pullWithQuery:(MSQuery *)query queryId:(NSString *)queryId completion:(MSSyncBlock)completion
{
[self.client.syncContext pullWithQuery:query queryId:queryId completion:completion];
return [self.client.syncContext pullWithQuery:query queryId:queryId completion:completion];
}

-(void)purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion
-(NSOperation *)purgeWithQuery:(MSQuery *)query completion:(MSSyncBlock)completion
{
// If no query, purge all records in the table by default
if (query == nil) {
MSQuery *allRecords = [[MSQuery alloc] initWithSyncTable:self];
[self.client.syncContext purgeWithQuery:allRecords completion:completion];
return [self.client.syncContext purgeWithQuery:allRecords completion:completion];

} else {
[self.client.syncContext purgeWithQuery:query completion:completion];
return [self.client.syncContext purgeWithQuery:query completion:completion];
}
}

/// Purges all data, pending operations, operation errors, and metadata for the
/// MSSyncTable from the local store.
-(void)forcePurgeWithCompletion:(MSSyncBlock)completion
-(NSOperation *)forcePurgeWithCompletion:(MSSyncBlock)completion
{
[self.client.syncContext forcePurgeWithTable:self completion:completion];
return [self.client.syncContext forcePurgeWithTable:self completion:completion];
}

#pragma mark * Public Read Methods
Expand Down