]> git.saurik.com Git - apple/javascriptcore.git/blob - API/tests/Regress141275.mm
JavaScriptCore-7601.1.46.3.tar.gz
[apple/javascriptcore.git] / API / tests / Regress141275.mm
1 /*
2 * Copyright (C) 2015 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #import "config.h"
27 #import "Regress141275.h"
28
29 #import <Foundation/Foundation.h>
30 #import <objc/objc.h>
31 #import <objc/runtime.h>
32
33 #if JSC_OBJC_API_ENABLED
34
35 extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
36
37 extern int failed;
38
39 static const NSUInteger scriptToEvaluate = 50;
40
41 @interface JSTEvaluator : NSObject
42 - (instancetype)initWithScript:(NSString*)script;
43
44 - (void)insertSignPostWithCompletion:(void(^)(NSError* error))completionHandler;
45
46 - (void)evaluateScript:(NSString*)script completion:(void(^)(NSError* error))completionHandler;
47 - (void)evaluateBlock:(void(^)(JSContext* context))evaluationBlock completion:(void(^)(NSError* error))completionHandler;
48
49 - (void)waitForTasksDoneAndReportResults;
50 @end
51
52
53 static const NSString* JSTEvaluatorThreadContextKey = @"JSTEvaluatorThreadContextKey";
54
55 /*
56 * A JSTEvaluatorThreadContext is kept in the thread dictionary of threads used by JSEvaluator.
57 *
58 * This includes the run loop thread, and any threads used by _jsSourcePerformQueue to execute a task.
59 */
60 @interface JSTEvaluatorThreadContext : NSObject
61 @property (weak) JSTEvaluator* evaluator;
62 @property (strong) JSContext* jsContext;
63 @end
64
65 @implementation JSTEvaluatorThreadContext
66 @end
67
68
69 /*!
70 * A JSTEvaluatorTask is a single task to be executed.
71 *
72 * JSTEvaluator keeps a list of pending tasks. The run loop thread is repsonsible for feeding pending tasks to the _jsSourcePerformQueue, while respecting sign posts.
73 */
74 @interface JSTEvaluatorTask : NSObject
75
76 @property (nonatomic, copy) void (^evaluateBlock)(JSContext* jsContext);
77 @property (nonatomic, copy) void (^completionHandler)(NSError* error);
78 @property (nonatomic, copy) NSError* error;
79
80 + (instancetype)evaluatorTaskWithEvaluateBlock:(void (^)(JSContext*))block completionHandler:(void (^)(NSError* error))completionBlock;
81
82 @end
83
84 @implementation JSTEvaluatorTask
85
86 + (instancetype)evaluatorTaskWithEvaluateBlock:(void (^)(JSContext*))evaluationBlock completionHandler:(void (^)(NSError* error))completionHandler
87 {
88 JSTEvaluatorTask* task = [self new];
89 task.evaluateBlock = evaluationBlock;
90 task.completionHandler = completionHandler;
91 return task;
92 }
93
94 @end
95
96 @implementation JSTEvaluator {
97 dispatch_queue_t _jsSourcePerformQueue;
98 dispatch_semaphore_t _allScriptsDone;
99 CFRunLoopRef _jsThreadRunLoop;
100 CFRunLoopSourceRef _jsThreadRunLoopSource;
101 JSContext* _jsContext;
102 NSMutableArray* __pendingTasks;
103 }
104
105 - (instancetype)init
106 {
107 self = [super init];
108 if (self) {
109 _jsSourcePerformQueue = dispatch_queue_create("JSTEval", DISPATCH_QUEUE_CONCURRENT);
110
111 _allScriptsDone = dispatch_semaphore_create(0);
112
113 _jsContext = [JSContext new];
114 _jsContext.name = @"JSTEval";
115 __pendingTasks = [NSMutableArray new];
116
117 NSThread* jsThread = [[NSThread alloc] initWithTarget:self selector:@selector(_jsThreadMain) object:nil];
118 [jsThread setName:@"JSTEval"];
119 [jsThread start];
120
121 }
122 return self;
123 }
124
125 - (instancetype)initWithScript:(NSString*)script
126 {
127 self = [self init];
128 if (self) {
129 __block NSError* scriptError = nil;
130 dispatch_semaphore_t dsema = dispatch_semaphore_create(0);
131 [self evaluateScript:script
132 completion:^(NSError* error) {
133 scriptError = error;
134 dispatch_semaphore_signal(dsema);
135 }];
136 dispatch_semaphore_wait(dsema, DISPATCH_TIME_FOREVER);
137 }
138 return self;
139 }
140
141 - (void)_accessPendingTasksWithBlock:(void(^)(NSMutableArray* pendingTasks))block
142 {
143 @synchronized(self) {
144 block(__pendingTasks);
145 if (__pendingTasks.count > 0) {
146 if (_jsThreadRunLoop && _jsThreadRunLoopSource) {
147 CFRunLoopSourceSignal(_jsThreadRunLoopSource);
148 CFRunLoopWakeUp(_jsThreadRunLoop);
149 }
150 }
151 }
152 }
153
154 - (void)insertSignPostWithCompletion:(void(^)(NSError* error))completionHandler
155 {
156 [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
157 JSTEvaluatorTask* task = [JSTEvaluatorTask evaluatorTaskWithEvaluateBlock:nil
158 completionHandler:completionHandler];
159
160 [pendingTasks addObject:task];
161 }];
162 }
163
164 - (void)evaluateScript:(NSString*)script completion:(void(^)(NSError* error))completionHandler
165 {
166 [self evaluateBlock:^(JSContext* context) {
167 [context evaluateScript:script];
168 } completion:completionHandler];
169 }
170
171 - (void)evaluateBlock:(void(^)(JSContext* context))evaluationBlock completion:(void(^)(NSError* error))completionHandler
172 {
173 NSParameterAssert(evaluationBlock != nil);
174 [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
175 JSTEvaluatorTask* task = [JSTEvaluatorTask evaluatorTaskWithEvaluateBlock:evaluationBlock
176 completionHandler:completionHandler];
177
178 [pendingTasks addObject:task];
179 }];
180 }
181
182 - (void)waitForTasksDoneAndReportResults
183 {
184 NSString* passFailString = @"PASSED";
185
186 if (!dispatch_semaphore_wait(_allScriptsDone, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC))) {
187 int totalScriptsRun = [_jsContext[@"counter"] toInt32];
188
189 if (totalScriptsRun != scriptToEvaluate) {
190 passFailString = @"FAILED";
191 failed = 1;
192 }
193
194 NSLog(@" Ran a total of %d scripts: %@", totalScriptsRun, passFailString);
195 } else {
196 passFailString = @"FAILED";
197 failed = 1;
198 NSLog(@" Error, timeout waiting for all tasks to complete: %@", passFailString);
199 }
200 }
201
202 static void __JSTRunLoopSourceScheduleCallBack(void* info, CFRunLoopRef rl, CFStringRef)
203 {
204 @autoreleasepool {
205 [(__bridge JSTEvaluator*)info _sourceScheduledOnRunLoop:rl];
206 }
207 }
208
209 static void __JSTRunLoopSourcePerformCallBack(void* info )
210 {
211 @autoreleasepool {
212 [(__bridge JSTEvaluator*)info _sourcePerform];
213 }
214 }
215
216 static void __JSTRunLoopSourceCancelCallBack(void* info, CFRunLoopRef rl, CFStringRef)
217 {
218 @autoreleasepool {
219 [(__bridge JSTEvaluator*)info _sourceCanceledOnRunLoop:rl];
220 }
221 }
222
223 - (void)_jsThreadMain
224 {
225 @autoreleasepool {
226 const CFIndex kRunLoopSourceContextVersion = 0;
227 CFRunLoopSourceContext sourceContext = {
228 kRunLoopSourceContextVersion, (__bridge void*)(self),
229 NULL, NULL, NULL, NULL, NULL,
230 __JSTRunLoopSourceScheduleCallBack,
231 __JSTRunLoopSourceCancelCallBack,
232 __JSTRunLoopSourcePerformCallBack
233 };
234
235 @synchronized(self) {
236 _jsThreadRunLoop = CFRunLoopGetCurrent();
237 CFRetain(_jsThreadRunLoop);
238
239 _jsThreadRunLoopSource = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &sourceContext);
240 CFRunLoopAddSource(_jsThreadRunLoop, _jsThreadRunLoopSource, kCFRunLoopDefaultMode);
241 }
242
243 CFRunLoopRun();
244
245 @synchronized(self) {
246 NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
247 [threadDict removeObjectForKey:threadDict[JSTEvaluatorThreadContextKey]];
248
249 CFRelease(_jsThreadRunLoopSource);
250 _jsThreadRunLoopSource = NULL;
251
252 CFRelease(_jsThreadRunLoop);
253 _jsThreadRunLoop = NULL;
254
255 __pendingTasks = nil;
256 }
257 }
258 }
259
260 - (void)_sourceScheduledOnRunLoop:(CFRunLoopRef)runLoop
261 {
262 UNUSED_PARAM(runLoop);
263 assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
264
265 // Wake up the run loop in case requests were submitted prior to the
266 // run loop & run loop source getting created.
267 CFRunLoopSourceSignal(_jsThreadRunLoopSource);
268 CFRunLoopWakeUp(_jsThreadRunLoop);
269 }
270
271 - (void)_setupEvaluatorThreadContextIfNeeded
272 {
273 NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
274 JSTEvaluatorThreadContext* context = threadDict[JSTEvaluatorThreadContextKey];
275 // The evaluator may be other evualuator, or nil if this thread has not been used before. Eaither way take ownership.
276 if (context.evaluator != self) {
277 context = [JSTEvaluatorThreadContext new];
278 context.evaluator = self;
279 threadDict[JSTEvaluatorThreadContextKey] = context;
280 }
281 }
282
283 - (void)_callCompletionHandler:(void(^)(NSError* error))completionHandler ifNeededWithError:(NSError*)error
284 {
285 if (completionHandler) {
286 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
287 completionHandler(error);
288 });
289 }
290 }
291
292 - (void)_sourcePerform
293 {
294 assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
295
296 __block NSArray* tasks = nil;
297 [self _accessPendingTasksWithBlock:^(NSMutableArray* pendingTasks) {
298 // No signpost, take all tasks.
299 tasks = [pendingTasks copy];
300 [pendingTasks removeAllObjects];
301 }];
302
303 if (tasks.count > 0) {
304 for (JSTEvaluatorTask* task in tasks) {
305 dispatch_block_t block = ^{
306 NSError* error = nil;
307 if (task.evaluateBlock) {
308 [self _setupEvaluatorThreadContextIfNeeded];
309 task.evaluateBlock(_jsContext);
310 if (_jsContext.exception) {
311 NSLog(@"Did fail on JSContext: %@", _jsContext.name);
312 NSDictionary* userInfo = @{ NSLocalizedDescriptionKey : [_jsContext.exception[@"message"] toString] };
313 error = [NSError errorWithDomain:@"JSTEvaluator" code:1 userInfo:userInfo];
314 _jsContext.exception = nil;
315 }
316 }
317 [self _callCompletionHandler:task.completionHandler ifNeededWithError:error];
318 };
319
320 if (task.evaluateBlock)
321 dispatch_async(_jsSourcePerformQueue, block);
322 else
323 dispatch_barrier_async(_jsSourcePerformQueue, block);
324 }
325
326 dispatch_barrier_sync(_jsSourcePerformQueue, ^{
327 if ([_jsContext[@"counter"] toInt32] == scriptToEvaluate)
328 dispatch_semaphore_signal(_allScriptsDone);
329 });
330 }
331 }
332
333 - (void)_sourceCanceledOnRunLoop:(CFRunLoopRef)runLoop
334 {
335 UNUSED_PARAM(runLoop);
336 assert([[[NSThread currentThread] name] isEqualToString:@"JSTEval"]);
337
338 @synchronized(self) {
339 assert(_jsThreadRunLoop);
340 assert(_jsThreadRunLoopSource);
341
342 CFRunLoopRemoveSource(_jsThreadRunLoop, _jsThreadRunLoopSource, kCFRunLoopDefaultMode);
343 CFRunLoopStop(_jsThreadRunLoop);
344 }
345 }
346
347 @end
348
349 void runRegress141275()
350 {
351 // Test that we can execute the same script from multiple threads with a shared context.
352 // See <https://webkit.org/b/141275>
353 NSLog(@"TEST: Testing multiple threads executing the same script with a shared context");
354
355 @autoreleasepool {
356 JSTEvaluator* evaluator = [[JSTEvaluator alloc] initWithScript:@"this['counter'] = 0;"];
357
358 void (^showErrorIfNeeded)(NSError* error) = ^(NSError* error) {
359 if (error) {
360 dispatch_async(dispatch_get_main_queue(), ^{
361 NSLog(@"Error: %@", error);
362 });
363 }
364 };
365
366 [evaluator evaluateBlock:^(JSContext* context) {
367 JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
368 } completion:showErrorIfNeeded];
369
370 [evaluator evaluateBlock:^(JSContext* context) {
371 context[@"wait"] = ^{
372 [NSThread sleepForTimeInterval:0.01];
373 };
374 } completion:^(NSError* error) {
375 if (error) {
376 dispatch_async(dispatch_get_main_queue(), ^{
377 NSLog(@"Error: %@", error);
378 });
379 }
380 for (unsigned i = 0; i < scriptToEvaluate; i++)
381 [evaluator evaluateScript:@"this['counter']++; this['wait']();" completion:showErrorIfNeeded];
382 }];
383
384 [evaluator waitForTasksDoneAndReportResults];
385 }
386 }
387
388 #endif // JSC_OBJC_API_ENABLED