]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSNearFutureScheduler.m
Security-58286.1.32.tar.gz
[apple/security.git] / keychain / ckks / CKKSNearFutureScheduler.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #import "CKKSNearFutureScheduler.h"
25 #import "CKKSCondition.h"
26 #include <os/transaction_private.h>
27
28 @interface CKKSNearFutureScheduler ()
29 @property NSString* name;
30 @property dispatch_time_t initialDelay;
31 @property dispatch_time_t continuingDelay;
32
33 @property NSDate* predictedNextFireTime;
34 @property bool liveRequest;
35 @property CKKSCondition* liveRequestReceived; // Triggered when liveRequest goes to true.
36
37 @property dispatch_source_t timer;
38 @property dispatch_queue_t queue;
39
40 @property bool keepProcessAlive;
41 @property os_transaction_t transaction;
42 @end
43
44 @implementation CKKSNearFutureScheduler
45
46 -(instancetype)initWithName:(NSString*)name delay:(dispatch_time_t)ns keepProcessAlive:(bool)keepProcessAlive block:(void (^)(void))futureOperation
47 {
48 return [self initWithName:name initialDelay:ns continuingDelay:ns keepProcessAlive:keepProcessAlive block:futureOperation];
49 }
50
51 -(instancetype)initWithName:(NSString*)name
52 initialDelay:(dispatch_time_t)initialDelay
53 continuingDelay:(dispatch_time_t)continuingDelay
54 keepProcessAlive:(bool)keepProcessAlive
55 block:(void (^)(void))futureOperation
56 {
57 if((self = [super init])) {
58 _name = name;
59
60 _queue = dispatch_queue_create([[NSString stringWithFormat:@"near-future-scheduler-%@",name] UTF8String], DISPATCH_QUEUE_SERIAL);
61 _initialDelay = initialDelay;
62 _continuingDelay = continuingDelay;
63 _futureOperation = futureOperation;
64
65 _liveRequest = false;
66 _liveRequestReceived = [[CKKSCondition alloc] init];
67 _predictedNextFireTime = nil;
68
69 _keepProcessAlive = keepProcessAlive;
70 }
71 return self;
72 }
73
74 -(NSString*)description {
75 NSDate* nextAt = self.nextFireTime;
76 if(nextAt) {
77 NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
78 [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
79 return [NSString stringWithFormat: @"<CKKSNearFutureScheduler(%@): next at %@", self.name, [dateFormatter stringFromDate: nextAt]];
80 } else {
81 return [NSString stringWithFormat: @"<CKKSNearFutureScheduler(%@): no pending attempts", self.name];
82 }
83 }
84
85 - (NSDate*)nextFireTime {
86 // If we have a live request, send the next fire time back. Otherwise, wait a tiny tiny bit to see if we receive a request.
87 if(self.liveRequest) {
88 return self.predictedNextFireTime;
89 } else if([self.liveRequestReceived wait:100*NSEC_PER_USEC] == 0) {
90 return self.predictedNextFireTime;
91 }
92
93 return nil;
94 }
95
96 -(void)waitUntil:(uint64_t)delay {
97 dispatch_sync(self.queue, ^{
98 [self _onqueueTrigger:delay];
99 });
100 }
101
102 -(void)_onqueueTimerTick {
103 dispatch_assert_queue(self.queue);
104
105 if(self.liveRequest) {
106 self.futureOperation();
107 self.liveRequest = false;
108 self.liveRequestReceived = [[CKKSCondition alloc] init];
109 self.transaction = nil;
110
111 self.predictedNextFireTime = [NSDate dateWithTimeIntervalSinceNow: (NSTimeInterval) ((double) self.continuingDelay) / (double) NSEC_PER_SEC];
112 } else {
113 // The timer has fired with no requests to call the block. Cancel it.
114 dispatch_source_cancel(self.timer);
115 self.predictedNextFireTime = nil;
116 }
117 }
118
119 -(void)trigger {
120 __weak __typeof(self) weakSelf = self;
121 dispatch_async(self.queue, ^{
122 // The timer tick should call the block!
123 self.liveRequest = true;
124 [self.liveRequestReceived fulfill];
125
126 [weakSelf _onqueueTrigger:DISPATCH_TIME_NOW];
127 });
128 }
129
130 -(void)_onqueueTrigger:(dispatch_time_t)requestedDelay {
131 dispatch_assert_queue(self.queue);
132 __weak __typeof(self) weakSelf = self;
133
134 // If we don't have one already, set up an os_transaction
135 if(self.keepProcessAlive && self.transaction == nil) {
136 self.transaction = os_transaction_create([[NSString stringWithFormat:@"com.apple.securityd.%@",self.name] UTF8String]);
137 }
138
139 if(requestedDelay != DISPATCH_TIME_NOW) {
140 NSDate* delayTime = [NSDate dateWithTimeIntervalSinceNow: (NSTimeInterval) ((double) requestedDelay) / (double) NSEC_PER_SEC];
141 if([delayTime compare:self.predictedNextFireTime] != NSOrderedDescending) {
142 // The next fire time is after this delay. Do nothing with the request.
143 } else {
144 // Need to cancel the timer and reset it below.
145 dispatch_source_cancel(self.timer);
146 self.predictedNextFireTime = nil;
147 }
148 }
149
150 // Check if the timer is alive
151 if(self.timer != nil && 0 == dispatch_source_testcancel(self.timer)) {
152 // timer is alive, do nothing
153 } else {
154 // start the timer
155 self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
156 0,
157 (dispatch_source_timer_flags_t)0,
158 self.queue);
159 dispatch_source_set_event_handler(self.timer, ^{
160 [weakSelf _onqueueTimerTick];
161 });
162
163 dispatch_time_t actualDelay = self.initialDelay > requestedDelay ? self.initialDelay : requestedDelay;
164
165 dispatch_source_set_timer(self.timer,
166 dispatch_walltime(NULL, actualDelay),
167 self.continuingDelay,
168 50 * NSEC_PER_MSEC);
169 dispatch_resume(self.timer);
170
171 self.predictedNextFireTime = [NSDate dateWithTimeIntervalSinceNow: (NSTimeInterval) ((double) actualDelay) / (double) NSEC_PER_SEC];
172 };
173 }
174
175 -(void)cancel {
176 dispatch_sync(self.queue, ^{
177 if(self.timer != nil && 0 == dispatch_source_testcancel(self.timer)) {
178 dispatch_source_cancel(self.timer);
179 }
180 });
181 }
182
183 @end