Skip to content

Commit 12e0b86

Browse files
committed
Queue got jobCount & better scheduling though storage requirement (fetchNextJobTimeInterval). Also, fixed critical bug - a race condition in queue. So, job might be executed two times :/ Hola TDD!
1 parent 82a64aa commit 12e0b86

10 files changed

+236
-406
lines changed

EDQueue.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ Pod::Spec.new do |s|
33
s.version = '1.0'
44
s.license = 'MIT'
55
s.summary = 'A persistent background job queue for iOS.'
6-
s.homepage = 'https://github.com/thisandagain/queue'
6+
s.homepage = 'https://github.com/gelosi/queue'
77
s.authors = {'Andrew Sliwinski' => '[email protected]', 'Francois Lambert' => '[email protected]', 'Oleg Shanyuk' => '[email protected]'}
8-
s.source = { :git => 'https://github.com/thisandagain/queue.git', :tag => 'v1.0' }
8+
s.source = { :git => 'https://github.com/gelosi/queue.git', :tag => 'v1.0' }
99
s.platform = :ios, '7.0'
1010
s.source_files = 'EDQueue'
1111
s.library = 'sqlite3.0'

EDQueue/EDQueue.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ extern NSString *const EDQueueDidDrain;
5656
- (void)stop;
5757
- (void)empty;
5858

59+
- (NSInteger)jobCount;
60+
5961
- (BOOL)jobExistsForTag:(NSString *)tag;
6062
- (BOOL)jobIsActiveForTag:(NSString *)tag;
6163
- (nullable EDQueueJob *)nextJobForTag:(NSString *)tag;

EDQueue/EDQueue.m

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ + (instancetype)defaultQueue
3636
static dispatch_once_t onceToken;
3737

3838
dispatch_once(&onceToken, ^{
39-
EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"edqueue.default.v7.3.sqlite"];
39+
EDQueueStorageEngine *fmdbBasedStorage = [[EDQueueStorageEngine alloc] initWithName:@"edqueue.default.v1.0.sqlite"];
4040

4141
defaultQueue = [[EDQueue alloc] initWithPersistentStore:fmdbBasedStorage];
4242
});
@@ -56,6 +56,16 @@ - (instancetype)initWithPersistentStore:(id<EDQueuePersistentStorage>)persistent
5656

5757
#pragma mark - Public methods
5858

59+
/**
60+
* Total number of enqueued & valid jobs.
61+
*
62+
* @return {NSInteger}
63+
*/
64+
- (NSInteger)jobCount
65+
{
66+
return [self.storage jobCount];
67+
}
68+
5969
/**
6070
* Adds a new job to the queue.
6171
*
@@ -152,6 +162,8 @@ - (void)stop
152162
- (void)empty
153163
{
154164
[self.storage removeAllJobs];
165+
166+
[self postNotificationOnMainThread:@{ EDQueueNameKey : EDQueueDidDrain }];
155167
}
156168

157169

@@ -164,15 +176,31 @@ - (void)empty
164176
*/
165177
- (void)tick
166178
{
167-
dispatch_queue_t gcd = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
168-
dispatch_async(gcd, ^{
169-
if (self.isRunning && !self.isActive && [self.storage jobCount] > 0) {
179+
static dispatch_once_t onceToken;
180+
static dispatch_queue_t gcd;
181+
182+
dispatch_once(&onceToken, ^{
183+
gcd = dispatch_queue_create("edqueue.serial", DISPATCH_QUEUE_SERIAL);
184+
});
185+
186+
dispatch_barrier_async(gcd, ^{
187+
188+
if (!self.isRunning) {
189+
return;
190+
}
191+
192+
if (self.isActive) {
193+
return;
194+
}
195+
196+
if ([self.storage jobCount] > 0) {
170197

171198
id<EDQueueStorageItem> storedJob = [self.storage fetchNextJobValidForDate:[NSDate date]];
172199

173200
if (!storedJob) {
174201
__weak typeof(self) weakSelf = self;
175-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
202+
NSTimeInterval nextTime = [self.storage fetchNextJobTimeInterval];
203+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(nextTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
176204
[weakSelf performSelectorOnMainThread:@selector(tick) withObject:nil waitUntilDone:false];
177205
});
178206

@@ -182,6 +210,7 @@ - (void)tick
182210
// Start job & Pass job to delegate
183211
self.activeJobTag = storedJob.job.tag;
184212
_isActive = YES;
213+
185214
[self.delegate queue:self processJob:storedJob.job completion:^(EDQueueResult result) {
186215
[self processJob:storedJob withResult:result];
187216
self.activeJobTag = nil;
@@ -202,6 +231,7 @@ - (void)processJob:(id<EDQueueStorageItem>)storedJob withResult:(EDQueueResult)r
202231
}];
203232

204233
[self.storage removeJob:storedJob];
234+
205235
break;
206236

207237
case EDQueueResultFail:
@@ -224,6 +254,7 @@ - (void)processJob:(id<EDQueueStorageItem>)storedJob withResult:(EDQueueResult)r
224254
} else {
225255
[self.storage removeJob:storedJob];
226256
}
257+
227258
break;
228259
case EDQueueResultCritical:
229260

@@ -234,12 +265,13 @@ - (void)processJob:(id<EDQueueStorageItem>)storedJob withResult:(EDQueueResult)r
234265

235266
[self errorWithMessage:@"Critical error. Job canceled."];
236267
[self.storage removeJob:storedJob];
268+
237269
break;
238270
}
239271

240272
// Clean-up
241273
_isActive = NO;
242-
274+
243275
// Drain
244276
if ([self.storage jobCount] == 0) {
245277

EDQueue/EDQueuePersistentStorageProtocol.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN
3535
- (NSUInteger)jobCount;
3636
- (nullable id<EDQueueStorageItem>)fetchNextJobValidForDate:(NSDate *)date;
3737
- (nullable id<EDQueueStorageItem>)fetchNextJobForTag:(NSString *)tag validForDate:(NSDate *)date;
38+
- (NSTimeInterval)fetchNextJobTimeInterval;
3839

3940
@end
4041

EDQueue/EDQueueStorageEngine.m

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515

1616
#import "EDQueueJob.h"
1717

18+
static NSTimeInterval const DefaultJobSleepInteval = 5.0;
19+
static NSTimeInterval const MaxJobSleepInterval = 60.0;
20+
21+
1822
NS_ASSUME_NONNULL_BEGIN
1923

2024
static NSString *pathForStorageName(NSString *storage)
@@ -58,6 +62,11 @@ - (instancetype)initWithTag:(NSString *)tag
5862
return self;
5963
}
6064

65+
- (NSString *)description
66+
{
67+
return [NSString stringWithFormat:@"%@ : id:%@, tag: %@",NSStringFromClass([self class]), _jobID, _job.tag];
68+
}
69+
6170
@end
6271

6372
@interface EDQueueStorageEngine()
@@ -239,7 +248,7 @@ - (NSUInteger)jobCount
239248
}
240249

241250
/**
242-
* Returns the oldest job from the datastore.
251+
* Returns the oldest valid job from the datastore.
243252
*
244253
* @return {id<EDQueueStorageItem>}
245254
*/
@@ -263,28 +272,60 @@ - (NSUInteger)jobCount
263272
}
264273

265274
/**
266-
* Returns the oldest job for the with tag from the datastore.
267-
*
268-
* @param {id} tag
275+
* Returns the oldest valid job from the datastore with specific tag
269276
*
270277
* @return {id<EDQueueStorageItem>}
271278
*/
272279
- (nullable id<EDQueueStorageItem>)fetchNextJobForTag:(NSString *)tag validForDate:(NSDate *)date
273280
{
274281
__block id<EDQueueStorageItem> job;
275-
282+
276283
[self.queue inDatabase:^(FMDatabase *db) {
277-
FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE tag = ? AND lastAttempt <= ? AND expiration >= ? ORDER BY id ASC LIMIT 1", tag, @(date.timeIntervalSince1970), @(date.timeIntervalSince1970)];
284+
NSTimeInterval timestamp = date.timeIntervalSince1970;
285+
FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE tag = ? AND lastAttempt <= ? AND expiration >= ? ORDER BY id ASC LIMIT 1", tag, @(timestamp), @(timestamp)];
278286
[self _databaseHadError:[db hadError] fromDatabase:db];
279-
287+
280288
while ([rs next]) {
281289
job = [self _jobFromResultSet:rs];
282290
}
291+
292+
[rs close];
293+
}];
294+
295+
return job;
296+
}
297+
298+
/**
299+
* Returns the minumum timeout for starting the next job.
300+
*
301+
* @param {id} tag
302+
*
303+
* @return {id<EDQueueStorageItem>}
304+
*/
305+
- (NSTimeInterval)fetchNextJobTimeInterval
306+
{
307+
308+
__block NSTimeInterval timeInterval = DefaultJobSleepInteval;
309+
310+
[self.queue inDatabase:^(FMDatabase *db) {
311+
312+
FMResultSet *rs = [db executeQuery:@"SELECT * FROM queue WHERE lastAttempt < expiration ORDER BY lastAttempt ASC LIMIT 1"];
313+
[self _databaseHadError:[db hadError] fromDatabase:db];
314+
315+
while ([rs next]) {
316+
timeInterval = [rs doubleForColumn:@"lastAttempt"];
317+
318+
timeInterval = fabs(timeInterval - [NSDate date].timeIntervalSince1970);
319+
320+
if (timeInterval > MaxJobSleepInterval) {
321+
timeInterval = MaxJobSleepInterval;
322+
}
323+
}
283324

284325
[rs close];
285326
}];
286327

287-
return job;
328+
return timeInterval;
288329
}
289330

290331
#pragma mark - Private methods
@@ -298,7 +339,7 @@ - (NSUInteger)jobCount
298339
jobID:@([rs intForColumn:@"id"])
299340
atempts:@([rs intForColumn:@"attempts"])];
300341

301-
storedItem.job.maxRetryCount = [rs unsignedLongLongIntForColumn:@"maxAttempts"];
342+
storedItem.job.maxRetryCount = [rs intForColumn:@"maxAttempts"];
302343
storedItem.job.retryTimeInterval = [rs doubleForColumn:@"retryTimeInterval"];
303344

304345
NSTimeInterval expiration = [rs doubleForColumn:@"expiration"];

Project/queue/EDViewController.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
@interface EDViewController : UIViewController
1313

14+
@property (weak, nonatomic) IBOutlet UILabel *activityTitle;
1415
@property (nonatomic) IBOutlet UITextView *activity;
1516

1617
- (IBAction)addSuccess:(id)sender;
1718
- (IBAction)addFail:(id)sender;
1819
- (IBAction)addCritical:(id)sender;
20+
- (IBAction)clearQueue:(id)sender;
1921

2022
@end

Project/queue/EDViewController.m

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ - (IBAction)addSuccess:(id)sender
4343
- (IBAction)addFail:(id)sender
4444
{
4545
EDQueueJob *fail = [[EDQueueJob alloc] initWithTag:@"fail" userInfo:nil];
46+
fail.maxRetryCount = 10;
4647
[[EDQueue defaultQueue] enqueueJob:fail];
4748
}
4849

@@ -51,13 +52,20 @@ - (IBAction)addCritical:(id)sender
5152
EDQueueJob *critical = [[EDQueueJob alloc] initWithTag:@"critical" userInfo:nil];
5253
[[EDQueue defaultQueue] enqueueJob:critical];
5354
}
55+
56+
- (IBAction)clearQueue:(id)sender
57+
{
58+
[[EDQueue defaultQueue] empty];
59+
}
5460

5561
#pragma mark - Notifications
5662

5763
- (void)receivedNotification:(NSNotification *)notification
5864
{
5965
self.activity.text = [NSString stringWithFormat:@"%@%@\n", self.activity.text, notification];
6066
[self.activity scrollRangeToVisible:NSMakeRange([self.activity.text length], 0)];
67+
68+
self.activityTitle.text = [NSString stringWithFormat:@"Activity: %ld",(long)[[EDQueue defaultQueue] jobCount]];
6169
}
6270

6371
#pragma mark - Dealloc

0 commit comments

Comments
 (0)