1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
|
#import <Foundation/NSDictionary.h>
#import <Foundation/NSThread.h>
#import <Foundation/NSLock.h>
#include <signal.h>
#import "Testing.h"
#if defined(SIGALRM)
#include <unistd.h>
#endif
@interface Init0 : NSObject
@end
@interface Init1 : Init0
@end
@interface SlowInit0
@end
@interface SlowInit1 : SlowInit0
+ (void) doNothing;
@end
@interface SlowInit2
+ (void) doNothing;
@end
@interface Mutual1 : NSObject
@end
@interface Mutual2 : NSObject
@end
static NSCondition *l;
static volatile int init0, init1, init2, init3;
static int initCount = 0;
@implementation Init0
+ (void) initialize
{
initCount++;
}
@end
@implementation Init1
@end
@implementation SlowInit0
+ (void) initialize
{
init0 = 1;
}
@end
@implementation SlowInit1
/**
* Called from main thread.
*/
+ (void) initialize
{
PASS(init0 == 1, "Superclass +initialize called before subclass");
// Spin until we've entered the second +initialize method
while (init2 == 0) {}
// Wake up the other thread
[l signal];
[l unlock];
// Trigger the
[SlowInit2 doNothing];
init1 = 1;
}
+ (void) doNothing {}
@end
@implementation SlowInit2
/**
* Called from the second thread.
*/
+ (void) initialize
{
init2++;
// Sleep until the main thread is ready for us
[l lock];
/* If the runtime is doing the wrong thing and this is called twice, then
* there will be no signal. We don't want to deadlock, so make sure that
* this times out after a short while.
*/
[l waitUntilDate: [NSDate dateWithTimeIntervalSinceNow: 1.5]];
[l unlock];
[NSThread sleepForTimeInterval: 1];
PASS(init1 == 0, "First initialize method did not finish too early");
init3++;
}
static volatile int finished = 2;
+ (void) doNothing
{
PASS(init2 == 1, "+initialize called exactly once");
PASS(init3 == 1, "+initialize completed before another method started");
finished--;
}
@end
@interface Trampoline : NSObject
+ (void) launch: (id)ignored;
@end
@implementation Trampoline
/**
* Launch the second thread. NSThread retains its arguments in the main
* thread, we need to ensure that nothing triggers the second +initialize
* method until we're in the second thread.
*/
+ (void) launch: (id)ignored
{
[NSAutoreleasePool new];
[SlowInit2 doNothing];
}
@end
static int mutual = 0;
@implementation Mutual1
+ (void) initialize
{
[NSThread sleepForTimeInterval: 0.5];
[Mutual2 class];
mutual++;
}
@end
@implementation Mutual2
+ (void) initialize
{
[NSThread sleepForTimeInterval: 0.5];
[Mutual1 class];
mutual++;
}
@end
@interface NSObject (Mutual)
+ (void) mutual1: (id)ignored;
+ (void) mutual2: (id)ignored;
@end
@implementation NSObject (Mutual)
+ (void) mutual1: (id)ignored
{
[Mutual1 class];
}
+ (void) mutual2: (id)ignored
{
[Mutual2 class];
}
@end
static void
mutualinit(int sig)
{
/* Generate a dashed hope for mutuality testing.
*/
testHopeful = YES;
PASS(0, "+initialize mutually dependent methods work");
_exit(0); // Use _exit() to avoid deadlocking in atexit()
}
static void
concurrency(int sig)
{
/* Generate a dashed hope for concurrency testing.
*/
testHopeful = YES;
PASS(0, "+initialize runs concurrently");
_exit(0); // Use _exit() to avoid deadlocking in atexit()
}
/**
* Test the behaviour of +initialize.
* It's an undocumented (but nice) feature that the Apple runtime lets
* both of the +initialize methods run concurrently, however the first
* one will block implicitly until the second one has completed.
*/
int main(void)
{
NSDate *when;
[NSAutoreleasePool new];
START_SET("+initialize")
/* Make sure we have initalised all the classes necessary for the test
* framework to record a test ... by passing one.
*/
PASS(1, "initialize test starts");
#if defined(SIGALRM)
/* End in a signal if the concurrency test deadlocks.
*/
signal(SIGALRM, mutualinit);
alarm(5);
#else
SKIP("+initialize runs concurrently. No SIGALRM present, this means we cannot stop the test on deadlock.");
#endif
/* Make sure that classes whose +initialise methods call each other
* can operate safely.
*/
[NSThread detachNewThreadSelector: @selector(mutual1:)
toTarget: [NSObject class]
withObject: nil];
[NSThread detachNewThreadSelector: @selector(mutual2:)
toTarget: [NSObject class]
withObject: nil];
when = [NSDate dateWithTimeIntervalSinceNow: 10.0];
while (mutual < 2 && [when timeIntervalSinceNow] > 0.0)
{
[NSThread sleepForTimeInterval: 0.1];
}
#if defined(SIGALRM)
signal(SIGALRM, SIG_IGN);
#endif
PASS(2 == mutual, "mutually dependent +initialize is thread-safe");
/* Make sure that when a class without its own +initialise is first used,
* the inherited +initialize is called instead.
* This is a traditional feature of the Apple runtime, which was 'improved'
* by the GNU runtime (which actually checks to see if +initialize is
* implemented before trying to call it). Good software should not depend
* on either behavior ... which means all good software should protext its
* +initialize code so that being called multiple times is OK.
* New GNU/GNUstep runtimes adopt the old Apple behavior purely for
* consistency.
*/
[Init1 class];
testHopeful = YES;
PASS(2 == initCount, "inherited +initialize is called automatically");
testHopeful = NO;
#if defined(SIGALRM)
/* End in a signal if the concurrency test deadlocks.
*/
signal(SIGALRM, concurrency);
alarm(5);
#else
SKIP("+initialize runs concurrently. No SIGALRM present, this means we cannot stop the test on deadlock.");
#endif
l = [NSCondition new];
[l lock];
[NSThread detachNewThreadSelector: @selector(launch:)
toTarget: [Trampoline class]
withObject: nil];
[NSThread sleepForTimeInterval: 0.5];
[SlowInit1 doNothing];
[l lock];
[l unlock];
while (finished)
{
[NSThread sleepForTimeInterval: 0.01];
}
END_SET("+initialize")
return 0;
}
|