]> git.saurik.com Git - apple/javascriptcore.git/blame - API/tests/Regress141275.mm
JavaScriptCore-7601.1.46.3.tar.gz
[apple/javascriptcore.git] / API / tests / Regress141275.mm
CommitLineData
ed1e77d3
A
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
35extern "C" void JSSynchronousGarbageCollectForDebugging(JSContextRef);
36
37extern int failed;
38
39static 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
53static 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
202static void __JSTRunLoopSourceScheduleCallBack(void* info, CFRunLoopRef rl, CFStringRef)
203{
204 @autoreleasepool {
205 [(__bridge JSTEvaluator*)info _sourceScheduledOnRunLoop:rl];
206 }
207}
208
209static void __JSTRunLoopSourcePerformCallBack(void* info )
210{
211 @autoreleasepool {
212 [(__bridge JSTEvaluator*)info _sourcePerform];
213 }
214}
215
216static 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
349void 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