Uncaught exception handler on iOS for particular threads - ios

For metric collection threads, we want them to never crash the app. For crashes from those threads, we want catch them and report to the server without crashing the app.
On Android or Java based app, we can set uncaught exception handler for a particular thread or a thread group. See the following example.
Is there similar mechanism on iOS platform for Objective runtime level uncaught exceptions? Is there any example code as reference?
val pipelineThread = Thread({
try {
intArrayOf(1, 2, 3, 4, 5).asList()
.parallelStream()
.map { e ->
e + 1
}.map {
throw RuntimeException("Pipeline map error.")
}.collect(Collectors.toList())
} catch (e: RuntimeException) {
// Exceptions thrown from pipeline will be handled in the same way
// as regular statements, even if it is parallel stream.
throw StreamPipelineException("Java parallel stream.", e)
}
}, "PipelineThread")
with(pipelineThread) {
setUncaughtExceptionHandler { p0, p1 ->
assert(p0.name.contentEquals("PipelineThread"))
assert(p1 is StreamPipelineException)
assert(p1.cause is RuntimeException)
assert(p1.message.contentEquals("Java parallel stream."))
}
}
I only find the top-level handler setting method.

For metric collection threads, we want them to never crash the app.
On Mac, you should move those threads into a separate process. This is how web browsers ensure that bugs impacting one tab do not impact other tabs or crash the whole browser. Usually the best tool for communicating between those processes is XPC.
On iOS, you will need to write your metric collection code carefully to avoid mistakes and not crash. You cannot spawn other processes directly in iOS today. (I keep hoping some day it will be possible. The OS can handle it. Third-party apps just aren't allowed to.)
NSSetUncaughtExceptionHandler is not intended to prevent crashes. It just gives a mechanism to record a little data or clean up certain things before crashing. Even in those cases, it must be used extremely carefully, since the system is in an undefined state during an uncaught exception. While in principle it's possible to write exception-safe Objective-C, the core frameworks are generally not. After raising an exception, there is no promise what state the system is in, and preconditions may be violated. As the docs note:
The Cocoa frameworks are generally not exception-safe. The general pattern is that exceptions are reserved for programmer error only, and the program catching such an exception should quit soon afterwards.
Assuming you're using NSThread or NSOperation for your "metric collection threads," you can create something similar to Java's approach using this, but I don't recommend it:
- (void)main {
#try {
... your code ...
} #catch ( NSException *e ) {
// Handle the exception and end the thread/operation
}
}
I don't recommend this because it can create difficult-to-solve bugs in distant parts of the program because the frameworks are not exception-safe. But I've seen it in enough production code to know it often works well enough. You have to decide if "app crashes and user relaunches it" is worse than "app doesn't crash but now behaves kind of weird and maybe corrupts some user data, but maybe doesn't."
But many Objective-C crashes are not due to exceptions in any case. They are due to signals, most commonly because of memory violations. In this case, the system is in an even less stable state. Even allocating or releasing memory may be invalid. (I once made the mistake of allocating memory in a signal handler, and deadlocked the program in a tight busy-loop. The Mac actually got hot. It would have been better to have just crashed.) For more, see How to prevent EXC_BAD_ACCESS from crashing an app?
Java incurs a lot of overhead, including a full virtual machine and extra bounds checks, to make it possible to catch the vast majority of error conditions (even ones you can't really recover from). It's a trade-off. It's still possible to have Java code that is not exception-safe and can fail to work correctly once an exception has been thrown, and it sometimes would be nice if Java would just crash and show you where the error is rather than programmers quietly swallowing the errors... but that's a different rant. Sometimes the sandbox makes things very nice.
Objective-C is built directly on C and allows accessing raw memory. If you do that incorrectly, it's undefined behavior and just about anything can happen, though usually the OS will kill the program. So even though it's possible to catch some kinds of exceptions, you cannot prevent all crashes.

Related

How to set a new handler/responder for EXC_BAD_ACCESS crashes

I am looking for ways to override the responder/handler for EXC_BAD_ACCESS. This is how I've set the handler for signal crashes or NSException which works fine:
NSSetUncaughtExceptionHandler(newExceptionHandler)
signal(SIGABRT, newSignalHandler)
signal(SIGILL, newSignalHandler)
I tried this but this is not getting called:
signal(EXC_BAD_ACCESS, newSignalHandler)
Any idea?
As Carl mentions, intercepting crashing events on iOS (and macOS) is fraught with peril for variety of reasons. I was the Crashlytics SDK maintainer for a while, and I would recommend strongly against doing it.
But, it definitely is possible.
One thing that seems to trip people up a lot is the relationship between signals, exceptions (ObjC or C++), and mach exceptions. These are all very different things.
On iOS (and tvOS, macOS) the events that terminate a process are mach exceptions. These are the low-level events that you can, in fact, intercept. When you see that EXC_ prefix, you know you're looking at a mach exception. I believe these are all defined in mach/exception.h.
Now, iOS has an interesting implementation where if there are no mach exception handlers, the OS translates the event into a unix signal. The signal function can be used to intercept these. Since EXC_BAD_ACCESS is not a unix signal, it is not a valid argument to the signal function. But, you can add handlers to those signals listed, and they will give you roughly the same information.
Mach exceptions are a significantly more powerful and safer mechanism for intercepting these kinds of events. Unfortunately, they also require a dramatically more complex handling system. Signals have all kinds of problems, but they are a lot easier to use.
I would be interested to know what you're trying to do, in case perhaps there's a better way of achieving what you are after.
Again, I'd avoid going down this road. It just isn't worth your time. It is challenging to get things working at all, and when you do, you'll be lulled into a false sense of security. You might even think things are working right, because the times your code goes completely wrong, you'll never know and you'll just get weird reports from users of random hangs from time to time.
If you are not using an existing crash reporter (they are very hard to write on your own, when dealing with corrupted memory and the like), you might look to their sources to see which signals they are handling. For example, PLCrashReporter's PLCrashReporter.m hooks onto SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, and SIGTRAP, which seems to be the usual list for crash handlers. EXC_BAD_ACCESS should turn into either SIGBUS or SIGSEGV. Writing re-entrant code correctly in signal handlers is extremely difficult (can't use any ObjC or really most C APIs in there), so be careful -- though I guess if you are already crashing, can't do much more harm. But the more careful you are, the more exceptions you will handle without crashing further.

Exception breakpoints in xcode for iOS, is exceptions excusable?

Am new to the iOS development. While looking into one of the App I work on, with exception breakpoint enabled in xcode. I see so many repetitive exceptions been raised.
But till now, we never realised this, as iOS was excusing those exceptions. The main exception was happening in our service layer, where, in an XML parsing, one of the element mentioned as key was missing. since so many back end requests are fired, for every XML response, and for multiple items inside it, this exception is thrown.
Now that our app is published, will this impact the device's stability? We are planning to fix thee kind of issues. But now, Will these be logged into Apple's diagnostic information. Just curious to know these. Can someone shed some light over it?
In Objective-C, Exceptions are used to signal fatal errors, that is programmer errors and "unexpected" and unrecoverable runtime errors. In Objective-C, Exceptions are a debugging and testing aid, not a construct to control program flow or to regain control after an error.
Usually, an application in a release state shouldn't throw exceptions. If it happens anyway, the only correct course of action is to terminate the program as soon as possible. In general, the Objective-C system frameworks are not exception safe. This means, after an exception has been thrown the application's state will be corrupt. If your program continues, even worse things may happen.
There are only a few, always undocumented harmless cases where exceptions will be thrown and caught by system libraries without leaving the program in an unstable condition.
See also https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Exceptions/Exceptions.html

What's the deal with catching exceptions?

So I've read a lot about catching exceptions. Let's talk about this and iOS together. I've used it with Google Analytics to submit information about the crash and using that to fix bugs.
But this raises a question. Can catching these exceptions help prevent apps from crashing. Can you theoretically prevent that bit of code from crashing the app and keep the app open. Now I get the fact that it would probably be impossible to do if there was no memory to be used but it would still be nice to know about.
Sorry if this sounds like a stupid questions and I really should read more about it and do some more research. Any information would be helpful.
I do have a fairly decent knowledge of iOS obj-c for my age and am willing to look into what you have to say.
Thanks!
Exceptions on iOS should never be caught; they are fatal for a reason. Unlike most languages that have a rich exception hierarchy and multiple means of throwing/catching exceptions for the benefit of the program as a whole, Cocoa-Touch code is built around the principle that all exceptions are fatal. It is a mistake to think that you can catch an exception thrown through any frames of Apple-provided code and have your process continue unhindered. It is an even more grave mistake to catch and rethrow the exception for the purpose of logging.
The exceptions thrown by Cocoa-Touch indicate serious errors in program logic, or undefined and unresolvable state in an object. It is not OK to ignore them, or log them after catching them. They must be fixed and prevented from being thrown in the first place in order to truly guarantee your process remains stable.

Is fast enumeration exception-safe in files compiled with -fobjc-arc-exceptions?

I'm considering using exceptions for internal error handling inside a library I'm writing. If they are used, the library will be built with arc and f-objc-arc-exceptions enabled.
One issue with obj-c exceptions is that care must be taken not to throw through any code you don't know to be exception-safe. When a file is compiled with -fobjc-arc-exceptions, is the enumeration code added in by the compiler exception safe?
As far as I can tell, it should be, because I can't see any reason for it to do anything that requires cleanup. But I'd be much happier using it if this behaviour is documented somewhere.
Edit: I should clarify that when I say "using exceptions for internal error handling", I'm talking about nonfatal errors. So "exception-safe" here means that there should be no memory leaked, no unreleased locks, etc.
Do not use exceptions for internal error handling.
At best, you'll end up with code that has a different design and feel than the system code. The border between the two will always require maintenance and consideration in that it adds a "which side of the exception behavior fence am I on?"
In reality, it will be a maintenance nightmare. Every time the code is refactored -- potentially to take advantage of new features in the system -- you'll constantly have to maintain the border between the two kinds of exception handling.
At worse, there will be a software update that changes an implementation detail and your app will start crashing.
The libraries were designed to not used exceptions for recoverable errors. Your code should follow the same pattern if you want to minimize the cost of ownership of that code.
There are a couple of libraries on the system that have used exceptions internally for recoverable errors. They are a constant source of bugs and maintenance headaches.
To answer your specific question, No, this is never safe:
#try {
for ( id x in array ) { ... do something that might #throw ... }
} #finally {
...
}
When you #throw out of fast enumeration, your are throwing through a frame of Apple code and the behavior is documented as undefined. In practice, such a simple pattern is likely going to work just fine, but "works by coincidence" has a significant maintenance cost.

handling ObjC exceptions in monotouch

Sometimes I'm getting exception from inside objective-c code. in my example these exceptions are not critical and I want the app to keep working.
The question is how do I handle these exceptions?
for example, my app crashes time to time while I'm using GeoCoder class. I don't really care if geocoder failed to geocode location and would like to keep my app alive. including geocoder calls in try-catch blocks doesn't solve the problem.
Any help will be appreciated!
MonoTouch tries to a certain extent to convert ObjC exceptions into managed exceptions. This is done by adding an unhandled exception handler for ObjC exceptions, and in there throw a managed exception. This works on secondary threads, but not on the main thread, because iOS has a try-catch handler in its main run loop, which will kill your app if any exceptions reaches it (in other words the exception is handled, so it will never reach MonoTouch' unhandled exception handler).
The best way is to avoid the ObjC exceptions in the first place (Apple's documentation states that ObjC exceptions should only be used for truly exceptional circumstances, since they have a number of problems - memory leaks is quite common for instance).
It is obviously not possible to avoid all ObjC exceptions, so we try to find workarounds for those cases where it still happens. The best way to get this done for your particular case is for you to create a complete test case and open a bug report here: http://bugzilla.xamarin.com, attaching the test case.

Resources