The pitfalls of iOS App background tasks

The pitfalls of iOS App background tasks

Most iOS apps will encapsulate some key tasks into Background Task after entering the background, otherwise the program will be suspended by the system after a few seconds. After starting Background Task, you can get 3 minutes to continue executing the code.

I was recently investigating the Background Crash issue in Messenger, and finally traced it back to Background Task. I would like to share some key points with you.

Crash Signal

Generally, apps have their own crash log collection tools, which generally have three problems. The first is that the crash logs before the tool is started cannot be captured, the second is that if the app crashes at startup, the logs cannot be uploaded, and the third is that the system cannot capture crash signals in some special scenarios.

  • To solve the first problem, just move the tool execution time as early as possible, or ensure that the previous code is as simple and reliable as possible.
  • To solve the second problem, you can use the background mode of NSURLSession, which I shared before.
  • To solve the third problem, we need to rely on Apple's own crash signal, which is also a point that many development teams ignore.

Apple also has its own crash log collection, but based on user privacy considerations, this crash log is not reliable and has the following main flaws:

  • Users need to agree to upload and share data. It is reported that the consent rate is less than 20%, so it is impossible to accurately determine the actual impact of a crash.
  • The crash log tool is simple. You can open it through Xcode -> Organizer. Select the App to download a certain version of the crash log from the Apple backend. You cannot filter by a certain condition, for example, you cannot filter out all SIGKILL logs.
  • The log is incomplete. Apple presents crash samples according to its own rules. An App may actually have a lot of online crashes, but Apple only lists dozens of crash samples, and the rules are unclear.
  • The crash log is only saved for one week and refreshed once a week, so the wiser approach is to write a script to synchronize it and upload it to your own backend.

Background Task Fancy Crash

Background Task's API is very simple. All the codes between begin and end fall into the scope of Background Task. However, simple codes hide considerable risks. Here are three crashes that are more likely to occur. Moreover, these three crashes cannot be captured by the crash collection tools that come with the client. Signals can only be obtained through Apple's crash log. The reason is simple. When these crashes occur, the app is usually in a suspended state and has no chance to execute any code. The system directly sends a SIGKILL signal to kill the app and generate a system log, a log that can only be accessed by Apple. The user must agree to upload and share it first.

0xdead10cc

The crash log usually looks like this:

  1. Exception Type: EXC_CRASH (SIGKILL)  
  2. Exception Codes: 0x0000000000000000, 0x00000000000000000  
  3. Exception Note: EXC_CORPSE_NOTIFY  
  4. Termination Reason: Namespace SPRINGBOARD, Code 0xdead10cc  
  5. Termination Description: SPRINGBOARD, com.xxx.xxx was task-suspended with locked system file

I have introduced the reason before. When your App has Extension, and Extension needs to share data with Host App, the general practice is to put the db file in the shared container directory. At this time, your App is likely to crash.

The App goes into the background to run Background Task. After it ends, the App is suspended by the system. If there is any operation to access the database after suspension, the App will be immediately killed by the system. This is for Apple's consideration to protect the integrity of the database file.

Therefore, the correct approach is to enclose all db operations that may occur after the App enters the background into the Background Task to ensure safety. It may be more appropriate to write this code in the db layer.

Moreover, Apple recommends that when you want to start Background Task, you don’t need to consider whether the current App is in the foreground or background. Even if the App starts Background Task in the foreground, it will not occupy the 3-minute quota after entering the background, so feel free to put the key code into Background Task.

0xbada5e47

When you follow the above advice and generously enclose as much critical code as possible in Background Task, you may encounter the following crash:

  1. Exception Type: EXC_CRASH (SIGKILL)  
  2. Exception Codes: 0x0000000000000000, 0x00000000000000000  
  3. Exception Note: EXC_CORPSE_NOTIFY  
  4. Termination Reason: Namespace ASSERTIOND, Code 0xbada5e47

The same is true for Background Tasks. Apple thinks you have started too many Background Tasks, so they need to be killed. How many are considered too many? A few dozen is not a lot. The current threshold is 1,000. If the number exceeds 1,000, it will be killed. If your Background Task encapsulation occurs in the db layer, when a large amount of data needs to be stored or read, it is still possible to hit this limit.

Another possible reason for 0xbada5e47 is that the Background Task will call the expiry handler after the timeout. No matter how many Background Tasks you have, the execution time of all expiry handlers cannot exceed a certain number of seconds. Once the time exceeds, they will be killed. Therefore, do not have any time-consuming operations such as disk io in the expiry handler.

0x8badf00d

Speaking of 0x8badf00d, everyone is familiar with it. When your main thread is stuck for too long, the system's Watchdog will kill your App and generate a crash log with 0x8badf00d.

Background Task can also be 0x8badf00d, for example:

  1. Exception Type: EXC_CRASH (SIGKILL)  
  2. Exception Codes: 0x0000000000000000, 0x00000000000000000  
  3. Exception Note: EXC_CORPSE_NOTIFY  
  4. Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d

When your code logic generates a leaked Background Task, the above system crash log will appear. What is a leaked Background Task? See the code:

  1. - (void)startBgTask
  2. {
  3. self.bgTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
  4. NSLog(@ "Expired: %lu" , (unsigned long)self.bgTaskID);
  5. [[UIApplication sharedApplication] endBackgroundTask:self.bgTaskID];
  6. }];
  7. }
  8.  
  9. - (void)endBgTask
  10. {
  11. [[UIApplication sharedApplication] endBackgroundTask:self.bgTaskID];
  12. }

If startBgTask is executed twice in the above code, a leaked Background Task will definitely appear, because self.bgTaskID will be assigned a new ID the second time, and the previous task ID will be lost, so end cannot be called correctly.

So how do you determine whether 0x8badf00d is caused by the main thread being stuck or a leaked Background Task? It's very simple. Look at the stack of the main thread. If it looks like this:

  1. Thread 0 Crashed:0
  2.  
  3. libsystem_kernel.dylib 0x000000018472be08 0x18472b000 + 35921  
  4. libsystem_kernel.dylib 0x000000018472bc80 0x18472b000 + 32002
  5. CoreFoundation 0x0000000184c6ee40 0x184b81000 + 9744003
  6. CoreFoundation 0x0000000184c6c908 0x184b81000 + 9648724
  7. CoreFoundation 0x0000000184b8cda8 0x184b81000 + 485525
  8. GraphicsServices 0x0000000186b6f020 0x186b64000 + 450886
  9. UIKit 0x000000018eb6d78c 0x18e850000 + 32664447
  10. Messenger 0x0000000103015ee4 0x102ff8000 + 1225968
  11. libdyld.dylib 0x000000018461dfc0 0x18461d000 + 4032

This stack is very classic and you will see it often. You can know what it is without symbolicating. This is the stack of the UI thread runloop in idle state, waiting for kernel message. It means the UI thread is idle at this time. The system kill in this state is most likely caused by leaked Background Task.

Make good use of the local crash log of the device

When a user's phone crashes and you can neither reproduce the crash nor find the crash log in the background, your best hope is the local crash log on the phone.

The local log is located in Settings -> Privacy -> Analytics -> Analytics Data. Open it and you may find the crash log of the app you developed. I have many logs for WeChat and Alipay on my phone.

Logs are sorted first by the name of the App, then by the date the log occurred.

If you are investigating a crash involving excessive memory usage, you can view logs starting with JetsamEvent-xxx.

If you want to know what abnormal logs the system has before the App crashes, you need to first install a loggingiOS.mobileconfig file on the device. This file basically allows users to authorize you to record system behavior. When a crash occurs, the user presses the two volume buttons + the power button at the same time. After releasing the buttons and vibrating, the system will record the key logs of the past period of time. This is very helpful for analyzing some difficult and complicated problems. This log usually starts with sysdiagnose_xxx.

After installing the loggingiOS.mobileconfig file, there is another benefit: Apple will record more and more detailed crash logs, because the user has authorized it, so Apple can do it boldly. The file name of this type of log is generally: stacks + appName - date.ips.

If the user's device can reproduce the problem you are investigating, there is another simple and efficient way. Connect the phone to the Mac via USB, and then start the Console App on the Mac. You can then intuitively view all key system logs, such as nsurlsessiond for network exception logs, locationd for location exception logs, and assertiond for Background Task exception logs. You can also filter directly by the process name of your app to view the life cycle and the reason for being killed.

Summarize

The above is some knowledge points shared from the recent investigation of Background Task crash. I hope it will be helpful to everyone.

<<:  Finally betting on dual SIM cards and dual standby, is iPhone taking the wrong path?

>>:  Summary of App Store review of financial apps 3.2.1 rejection and 23 other reasons

Recommend

The eighth course of the Xiaomao Qianqian Writing Training Camp

The course comes from the eighth session of Xiaom...

What does Baidu bidding ROI mean in online marketing?

ROI is return on investment = total profit/cost*1...

Why do some people always pick their toes? And then smell them?

This article was reviewed by Tao Ning, PhD, Assoc...

Toutiao’s addictive data mining

Due to some irresistible forces, Toutiao 's p...

Children's tic disorder: beware of the health risks hidden in "small movements"

This is the 5293th article of Da Yi Xiao Hu In th...

Six essential product strategies for planning and promotion in 2020

Introduction丨The “link between the past and the f...

Inventory of essential tools for new media operations (dry goods collection)

We also need to arrange a good-looking layout How...

How can brands use TikTok efficiently? Try this method!

Recently, a friend from the brand marketing depar...

Over 1 billion monthly active users! Check out the advertising in Moments!

On March 21, 2018, Tencent announced its fourth q...

How did humanity win the 3,000-year history of fighting smallpox?

Infectious diseases are the endless enemy of huma...

Musee d'Orsay: a concerto between technology and art

The Musée d'Orsay, which has been open to the...