diff --git a/CodenameOne/src/com/codename1/notifications/LocalNotification.java b/CodenameOne/src/com/codename1/notifications/LocalNotification.java index 3621694637..4d5f4c6120 100644 --- a/CodenameOne/src/com/codename1/notifications/LocalNotification.java +++ b/CodenameOne/src/com/codename1/notifications/LocalNotification.java @@ -202,6 +202,10 @@ public void setAlertSound(String alertSound) { /// /// The ID can also be used to cancel the notification later using `com.codename1.ui.Display#cancelLocalNotification(java.lang.String)` /// + /// **Platform note (iOS):** Notification IDs map to `UNNotificationRequest` identifiers. + /// Scheduling another local notification with the same ID replaces the existing pending notification + /// rather than adding a second one. + /// /// #### Returns /// /// the id @@ -216,6 +220,10 @@ public String getId() { /// /// The ID can also be used to cancel the notification later using `com.codename1.ui.Display#cancelLocalNotification(java.lang.String)` /// + /// **Platform note (iOS):** Notification IDs map to `UNNotificationRequest` identifiers. + /// Scheduling another local notification with the same ID replaces the existing pending notification + /// rather than adding a second one. + /// /// #### Parameters /// /// - `id`: the id to set diff --git a/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java b/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java index 337710b512..b31dd9dc72 100644 --- a/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java +++ b/Ports/iOSPort/src/com/codename1/impl/ios/IOSImplementation.java @@ -9204,9 +9204,13 @@ public void splitString(String source, char separator, ArrayList out) { } public void scheduleLocalNotification(LocalNotification n, long firstTime, int repeat) { - + String id = n.getId(); + if (id != null) { + nativeInstance.cancelLocalNotification(id); + } + nativeInstance.sendLocalNotification( - n.getId(), + id, n.getAlertTitle(), n.getAlertBody(), n.getAlertSound(), diff --git a/scripts/hellocodenameone/android/src/main/java/com/codenameone/examples/hellocodenameone/LocalNotificationNativeImpl.java b/scripts/hellocodenameone/android/src/main/java/com/codenameone/examples/hellocodenameone/LocalNotificationNativeImpl.java new file mode 100644 index 0000000000..1e61e06d04 --- /dev/null +++ b/scripts/hellocodenameone/android/src/main/java/com/codenameone/examples/hellocodenameone/LocalNotificationNativeImpl.java @@ -0,0 +1,11 @@ +package com.codenameone.examples.hellocodenameone; + +public class LocalNotificationNativeImpl { + public int countPendingNotificationsWithId(String notificationId) { + return -1; + } + + public boolean isSupported() { + return false; + } +} diff --git a/scripts/hellocodenameone/android/src/main/java/com/codenameone/examples/hellocodenameone/TestDiagnosticsNativeImpl.java b/scripts/hellocodenameone/android/src/main/java/com/codenameone/examples/hellocodenameone/TestDiagnosticsNativeImpl.java new file mode 100644 index 0000000000..b063376a07 --- /dev/null +++ b/scripts/hellocodenameone/android/src/main/java/com/codenameone/examples/hellocodenameone/TestDiagnosticsNativeImpl.java @@ -0,0 +1,16 @@ +package com.codenameone.examples.hellocodenameone; + +public class TestDiagnosticsNativeImpl { + public void enableNativeCrashSignalLogging(String reason) { + } + + public void dumpNativeThreads(String reason) { + } + + public void failFastWithNativeThreadDump(String reason) { + } + + public boolean isSupported() { + return false; + } +} diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/LocalNotificationNative.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/LocalNotificationNative.java new file mode 100644 index 0000000000..40d9d096af --- /dev/null +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/LocalNotificationNative.java @@ -0,0 +1,7 @@ +package com.codenameone.examples.hellocodenameone; + +import com.codename1.system.NativeInterface; + +public interface LocalNotificationNative extends NativeInterface { + int countPendingNotificationsWithId(String notificationId); +} diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/TestDiagnosticsNative.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/TestDiagnosticsNative.java new file mode 100644 index 0000000000..a4f2452bdc --- /dev/null +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/TestDiagnosticsNative.java @@ -0,0 +1,9 @@ +package com.codenameone.examples.hellocodenameone; + +import com.codename1.system.NativeInterface; + +public interface TestDiagnosticsNative extends NativeInterface { + void enableNativeCrashSignalLogging(String reason); + void dumpNativeThreads(String reason); + void failFastWithNativeThreadDump(String reason); +} diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/BaseTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/BaseTest.java index 1036f4f205..970a0d8978 100644 --- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/BaseTest.java +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/BaseTest.java @@ -34,8 +34,11 @@ protected Form createForm(String title, Layout layout, final String imageName) { @Override protected void onShowCompleted() { registerReadyCallback(this, () -> { - Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshot(imageName); - done(); + if (Cn1ssDeviceRunnerHelper.emitCurrentFormScreenshot(imageName)) { + done(); + } else { + fail("Screenshot capture failed for " + imageName); + } }); } }; @@ -53,4 +56,4 @@ protected synchronized void done() { public synchronized boolean isDone() { return done; } -} \ No newline at end of file +} diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java index 6157688bb8..5c0f8d1795 100644 --- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java @@ -3,10 +3,12 @@ import com.codename1.io.Util; import com.codename1.testing.DeviceRunner; import com.codename1.testing.TestReporting; +import com.codename1.system.NativeLookup; import com.codename1.ui.CN; import com.codename1.ui.Display; import com.codename1.ui.Form; import com.codename1.util.StringUtil; +import com.codenameone.examples.hellocodenameone.TestDiagnosticsNative; import com.codenameone.examples.hellocodenameone.tests.graphics.AffineScale; import com.codenameone.examples.hellocodenameone.tests.graphics.Clip; import com.codenameone.examples.hellocodenameone.tests.graphics.DrawArc; @@ -71,6 +73,7 @@ public final class Cn1ssDeviceRunner extends DeviceRunner { new OrientationLockScreenshotTest(), new SheetScreenshotTest(), new InPlaceEditViewTest(), + new LocalNotificationIdOverrideTest(), new BytecodeTranslatorRegressionTest(), new BackgroundThreadUiAccessTest(), new VPNDetectionAPITest(), @@ -82,6 +85,8 @@ public static void addTest(BaseTest test) { } public void runSuite() { + boolean suiteFailed = false; + enableNativeCrashSignalLogging(); CN.callSerially(() -> { Display.getInstance().addEdtErrorHandler(e -> { log("CN1SS:ERR:exception caught in EDT " + e.getSource()); @@ -104,6 +109,7 @@ public void runSuite() { } catch (Throwable t) { log("CN1SS:ERR:suite test=" + testClass + " failed=" + t); t.printStackTrace(); + testClass.fail("Unhandled exception: " + t.getMessage()); } }); int timeout = 30000; @@ -114,8 +120,12 @@ public void runSuite() { testClass.cleanup(); if(timeout == 0) { log("CN1SS:ERR:suite test=" + testClass + " failed due to timeout waiting for DONE"); + dumpDiagnostics("timeout test=" + testClass); + suiteFailed = true; } else if (testClass.isFailed()) { log("CN1SS:ERR:suite test=" + testClass + " failed: " + testClass.getFailMessage()); + dumpDiagnostics("failure test=" + testClass + " message=" + testClass.getFailMessage()); + suiteFailed = true; } else { if (!testClass.shouldTakeScreenshot()) { log("CN1SS:INFO:test=" + testClass + " screenshot=none"); @@ -124,7 +134,14 @@ public void runSuite() { log("CN1SS:INFO:suite finished test=" + testClass); } log("CN1SS:SUITE:FINISHED"); + if (suiteFailed) { + log("CN1SS:ERR:suite failed due to one or more test errors"); + } TestReporting.getInstance().testExecutionFinished(getClass().getName()); + if (suiteFailed) { + failFastWithNativeCrash("CN1SS suite failed"); + throw new RuntimeException("CN1SS suite failed"); + } if (CN.isSimulator()) { Display.getInstance().exitApplication(); } @@ -134,6 +151,55 @@ private static void log(String msg) { System.out.println(msg); } + private static void dumpDiagnostics(String reason) { + try { + Thread current = Thread.currentThread(); + log("CN1SS:ERR:capturing java stack reason=" + reason + " thread=" + current.getName()); + String stack = Display.getInstance().getStackTrace(current, new RuntimeException("CN1SS diagnostics: " + reason)); + for (String s : StringUtil.tokenize(stack, '\n')) { + if (s.length() > 400) { + s = s.substring(0, 400); + } + log("CN1SS:STACK:" + s); + } + } catch (Throwable t) { + log("CN1SS:ERR:failed to capture java stack=" + t); + } + + try { + TestDiagnosticsNative nativeDiagnostics = NativeLookup.create(TestDiagnosticsNative.class); + if (nativeDiagnostics != null && nativeDiagnostics.isSupported()) { + nativeDiagnostics.dumpNativeThreads(reason); + } else { + log("CN1SS:INFO:native thread dump unsupported"); + } + } catch (Throwable t) { + log("CN1SS:ERR:failed to capture native thread dump=" + t); + } + } + + private static void failFastWithNativeCrash(String reason) { + try { + TestDiagnosticsNative nativeDiagnostics = NativeLookup.create(TestDiagnosticsNative.class); + if (nativeDiagnostics != null && nativeDiagnostics.isSupported()) { + nativeDiagnostics.failFastWithNativeThreadDump(reason); + } + } catch (Throwable t) { + log("CN1SS:ERR:failed to invoke native fail-fast dump=" + t); + } + } + + private static void enableNativeCrashSignalLogging() { + try { + TestDiagnosticsNative nativeDiagnostics = NativeLookup.create(TestDiagnosticsNative.class); + if (nativeDiagnostics != null && nativeDiagnostics.isSupported()) { + nativeDiagnostics.enableNativeCrashSignalLogging("CN1SS runSuite start"); + } + } catch (Throwable t) { + log("CN1SS:ERR:failed to install native crash signal handlers=" + t); + } + } + @Override protected void startApplicationInstance() { Cn1ssDeviceRunnerHelper.runOnEdtSync(() -> { diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunnerHelper.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunnerHelper.java index 58a2187a1e..68da1ea77b 100644 --- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunnerHelper.java +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunnerHelper.java @@ -29,37 +29,28 @@ static void runOnEdtSync(Runnable runnable) { } } - static void emitCurrentFormScreenshot(String testName) { + static boolean emitCurrentFormScreenshot(String testName) { String safeName = sanitizeTestName(testName); Form current = Display.getInstance().getCurrent(); if (current == null) { println("CN1SS:ERR:test=" + safeName + " message=Current form is null"); println("CN1SS:END:" + safeName); + return false; } int width = Math.max(1, current.getWidth()); int height = Math.max(1, current.getHeight()); - Image[] img = new Image[1]; - Display.getInstance().screenshot(screen -> img[0] = screen); - long time = System.currentTimeMillis(); - Display.getInstance().invokeAndBlock(() -> { - while(img[0] == null) { - Util.sleep(50); - // timeout - if (System.currentTimeMillis() - time > 2000) { - return; - } - } - }); - if (img[0] == null) { - println("CN1SS:ERR:test=" + safeName + " message=Screenshot process timed out"); + Image screenshot = captureScreenshotWithRetries(safeName, current, width, height); + if (screenshot == null) { + println("CN1SS:ERR:test=" + safeName + " message=Unable to capture screenshot"); println("CN1SS:END:" + safeName); + return false; } - Image screenshot = img[0]; try { ImageIO io = ImageIO.getImageIO(); if (io == null || !io.isFormatSupported(ImageIO.FORMAT_PNG)) { println("CN1SS:ERR:test=" + safeName + " message=PNG encoding unavailable"); println("CN1SS:END:" + safeName); + return false; } if(Display.getInstance().isSimulator()) { io.save(screenshot, Storage.getInstance().createOutputStream(safeName + ".png"), ImageIO.FORMAT_PNG, 1); @@ -76,15 +67,72 @@ static void emitCurrentFormScreenshot(String testName) { } else { println("CN1SS:INFO:test=" + safeName + " preview_jpeg_bytes=0 preview_quality=0"); } + return true; } catch (IOException ex) { println("CN1SS:ERR:test=" + safeName + " message=" + ex); Log.e(ex); println("CN1SS:END:" + safeName); + return false; } finally { screenshot.dispose(); } } + static Image captureScreenshotWithRetries(String safeName, Form current, int width, int height) { + final int maxAttempts = 3; + Image screenshot = null; + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + current.revalidate(); + Image[] img = new Image[1]; + Display.getInstance().screenshot(screen -> img[0] = screen); + long time = System.currentTimeMillis(); + Display.getInstance().invokeAndBlock(() -> { + while(img[0] == null) { + Util.sleep(50); + if (System.currentTimeMillis() - time > 2000) { + return; + } + } + }); + screenshot = img[0]; + if (screenshot == null) { + println("CN1SS:WARN:test=" + safeName + " message=Screenshot process timed out attempt=" + attempt); + continue; + } + + int[] imageData = screenshot.getRGBCached(); + if (!isLikelyBlankImage(imageData)) { + return screenshot; + } + + int sample = imageData.length > 0 ? imageData[0] : 0; + println("CN1SS:WARN:test=" + safeName + " message=Blank screenshot detected width=" + width + " height=" + height + " rgb0=" + sample + " attempt=" + attempt + " form=" + current.getClass().getName()); + if (attempt < maxAttempts) { + screenshot.dispose(); + screenshot = null; + Util.sleep(250); + } else { + screenshot.dispose(); + screenshot = null; + } + } + return screenshot; + } + + static boolean isLikelyBlankImage(int[] imageData) { + if (imageData == null || imageData.length == 0) { + return true; + } + int first = imageData[0]; + int maxSamples = Math.min(imageData.length, 4096); + for (int i = 1; i < maxSamples; i++) { + if (imageData[i] != first) { + return false; + } + } + return true; + } + static byte[] encodePreview(ImageIO io, Image screenshot, String safeName) throws IOException { byte[] chosenPreview = null; int chosenQuality = 0; diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/LocalNotificationIdOverrideTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/LocalNotificationIdOverrideTest.java new file mode 100644 index 0000000000..0fd07793c4 --- /dev/null +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/LocalNotificationIdOverrideTest.java @@ -0,0 +1,60 @@ +package com.codenameone.examples.hellocodenameone.tests; + +import com.codename1.io.Log; +import com.codename1.notifications.LocalNotification; +import com.codename1.system.NativeLookup; +import com.codename1.ui.Display; +import com.codename1.ui.util.UITimer; +import com.codenameone.examples.hellocodenameone.LocalNotificationNative; + +public class LocalNotificationIdOverrideTest extends BaseTest { + @Override + public boolean shouldTakeScreenshot() { + return false; + } + + @Override + public boolean runTest() { + LocalNotificationNative nativeInterface = NativeLookup.create(LocalNotificationNative.class); + if (nativeInterface == null || !nativeInterface.isSupported()) { + done(); + return true; + } + + String notificationId = "cn1ss-local-notification-id-override"; + Display.getInstance().cancelLocalNotification(notificationId); + + LocalNotification first = new LocalNotification(); + first.setId(notificationId); + first.setAlertTitle("First"); + first.setAlertBody("First body"); + Display.getInstance().scheduleLocalNotification(first, System.currentTimeMillis() + 60000, LocalNotification.REPEAT_NONE); + + LocalNotification second = new LocalNotification(); + second.setId(notificationId); + second.setAlertTitle("Second"); + second.setAlertBody("Second body"); + Display.getInstance().scheduleLocalNotification(second, System.currentTimeMillis() + 120000, LocalNotification.REPEAT_NONE); + + UITimer.timer(1000, false, Display.getInstance().getCurrent(), () -> { + try { + int pendingWithSameId = nativeInterface.countPendingNotificationsWithId(notificationId); + Display.getInstance().cancelLocalNotification(notificationId); + if (pendingWithSameId < 0) { + fail("Native notification count returned an error for id '" + notificationId + "': " + pendingWithSameId); + return; + } + if (pendingWithSameId != 1) { + fail("Expected a single pending notification for id '" + notificationId + "' but found " + pendingWithSameId); + } else { + done(); + } + } catch (Throwable t) { + Log.e(t); + fail("Native notification count failed with exception: " + t.getMessage()); + } + }); + + return true; + } +} diff --git a/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl.h b/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl.h new file mode 100644 index 0000000000..a24945b79e --- /dev/null +++ b/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl.h @@ -0,0 +1,9 @@ +#import + +@interface com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl : NSObject { +} + +-(int)countPendingNotificationsWithId:(NSString*)notificationId; +-(BOOL)isSupported; + +@end diff --git a/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl.m b/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl.m new file mode 100644 index 0000000000..0f0fa374ee --- /dev/null +++ b/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl.m @@ -0,0 +1,29 @@ +#import "com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl.h" +#import + +@implementation com_codenameone_examples_hellocodenameone_LocalNotificationNativeImpl + +-(int)countPendingNotificationsWithId:(NSString*)notificationId { + if (notificationId == nil) { + return 0; + } + __block NSInteger matches = 0; + dispatch_sync(dispatch_get_main_queue(), ^{ + UIApplication *app = [UIApplication sharedApplication]; + NSArray *scheduledNotifications = [app scheduledLocalNotifications]; + for (UILocalNotification *notification in scheduledNotifications) { + NSDictionary *userInfo = notification.userInfo; + NSString *scheduledId = [NSString stringWithFormat:@"%@", [userInfo valueForKey:@"__ios_id__"]]; + if ([notificationId isEqualToString:scheduledId]) { + matches++; + } + } + }); + return (int)matches; +} + +-(BOOL)isSupported{ + return YES; +} + +@end diff --git a/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl.h b/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl.h new file mode 100644 index 0000000000..37ab2508b4 --- /dev/null +++ b/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl.h @@ -0,0 +1,11 @@ +#import + +@interface com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl : NSObject { +} + +-(void)dumpNativeThreads:(NSString*)reason; +-(void)failFastWithNativeThreadDump:(NSString*)reason; +-(void)enableNativeCrashSignalLogging:(NSString*)reason; +-(BOOL)isSupported; + +@end diff --git a/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl.m b/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl.m new file mode 100644 index 0000000000..8cb032d52a --- /dev/null +++ b/scripts/hellocodenameone/ios/src/main/objectivec/com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl.m @@ -0,0 +1,81 @@ +#import "com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl.h" +#include +#include +#include +#include + +static volatile sig_atomic_t cn1ssSignalHandlersInstalled = 0; + +static void cn1ss_writeLine(const char *line) { + if (line == NULL) { + return; + } + write(STDERR_FILENO, line, strlen(line)); + write(STDERR_FILENO, "\n", 1); +} + +static void cn1ss_signalHandler(int signo) { + cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:BEGIN"); + switch (signo) { + case SIGABRT: cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:type=SIGABRT"); break; + case SIGSEGV: cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:type=SIGSEGV"); break; + case SIGBUS: cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:type=SIGBUS"); break; + case SIGILL: cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:type=SIGILL"); break; + case SIGFPE: cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:type=SIGFPE"); break; + case SIGTRAP: cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:type=SIGTRAP"); break; + default: cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:type=OTHER"); break; + } + + void *stack[64]; + int frames = backtrace(stack, 64); + backtrace_symbols_fd(stack, frames, STDERR_FILENO); + cn1ss_writeLine("CN1SS:NATIVE:SIGNAL:END"); + + signal(signo, SIG_DFL); + raise(signo); +} + +@implementation com_codenameone_examples_hellocodenameone_TestDiagnosticsNativeImpl + +-(void)enableNativeCrashSignalLogging:(NSString*)reason { + if (cn1ssSignalHandlersInstalled) { + return; + } + cn1ssSignalHandlersInstalled = 1; + NSLog(@"CN1SS:NATIVE:SIGNAL:install handlers reason=%@", reason != nil ? reason : @"unspecified"); + signal(SIGABRT, cn1ss_signalHandler); + signal(SIGSEGV, cn1ss_signalHandler); + signal(SIGBUS, cn1ss_signalHandler); + signal(SIGILL, cn1ss_signalHandler); + signal(SIGFPE, cn1ss_signalHandler); + signal(SIGTRAP, cn1ss_signalHandler); +} + +-(void)dumpNativeThreads:(NSString*)reason { + @try { + NSString *label = reason != nil ? reason : @"unspecified"; + NSLog(@"CN1SS:NATIVE:THREAD_DUMP:BEGIN reason=%@", label); + NSLog(@"CN1SS:NATIVE:THREAD_DUMP:current=%@ isMain=%@", [NSThread currentThread], [NSThread isMainThread] ? @"true" : @"false"); + NSArray *symbols = [NSThread callStackSymbols]; + for (NSString *line in symbols) { + NSLog(@"CN1SS:NATIVE:STACK:%@", line); + } + NSLog(@"CN1SS:NATIVE:THREAD_DUMP:END reason=%@", label); + } @catch (NSException *ex) { + NSLog(@"CN1SS:NATIVE:THREAD_DUMP:ERROR reason=%@ exception=%@", reason, ex); + } +} + +-(void)failFastWithNativeThreadDump:(NSString*)reason { + NSString *label = reason != nil ? reason : @"unspecified"; + NSLog(@"CN1SS:NATIVE:FAIL_FAST:BEGIN reason=%@", label); + [self dumpNativeThreads:label]; + NSLog(@"CN1SS:NATIVE:FAIL_FAST:ABORT reason=%@", label); + abort(); +} + +-(BOOL)isSupported{ + return YES; +} + +@end