Some Devices Not Receiving Apple Push Notifications
Asked Answered
A

4

8

I am using Apples iOS Enhanced Notification Format to send push notification in bulk, and using the PHP solution described in this post: https://mcmap.net/q/718821/-php-apple-enhanced-push-notification-read-error-response

The experience at this point is that when I send a push notification, some devices are receiving the message and some devices are not. The results are inconsistent. Sometimes device X will receive a notification and sometimes device X will not. I am logging everything and I am not getting any error responses.

Any thoughts about what is happening would be very helpful.

Apostle answered 10/2, 2014 at 15:34 Comment(4)
Do you not get any delivery, or does it just take very long? We sometimes see a half hour delay if the devices does not have a SIM cardJanice
Additionally, are you sure that the tokens are for the correct version in the correct environment?Janice
The failed devices never receive the message. Yes the tokens are correct, sometimes the same failed devices will receive a different message sent at a different time. The same device might fail on Monday but work on Tuesday - or might fail at 1pm and then work at 3pm and then fail at 6pm on the same day.Apostle
When you say that the same device fails sometimes and works other times, when it fails - are you sending a notification to that device alone, or is it a part of multiple notifications sent on the same connection over a short period of time? If it's the latter, a message sent to an invalid device token prior to that device (that sometimes works) will cause the connection to close and the good token to be ignored. It's easy to miss the error response from Apple which indicates which token is invalid.Apiculate
A
0

The solution in the answer you linked to has a problem. It attemps to read the error response after each message is sent, but the read returns immediately and doesn't wait for a response to become available. While this is more efficient than waiting for a potential error response for X mili-seconds after each message, you might miss the error response and the connection may be dropped by Apple without you knowing any error occured.

While I can't give you code to solve your problem, I get give you some advice.

Here's the logic you should use (according to Apple), but I haven't managed to make it work reliably (at least not in my Java implementation):

Push Notification Throughput and Error Checking

If you're seeing throughput lower than 9,000 notifications per second, your server might benefit from improved error handling logic.

Here's how to check for errors when using the enhanced binary interface. Keep writing until a write fails. If the stream is ready for writing again, resend the notification and keep going. If the stream isn't ready for writing, see if the stream is available for reading.

If it is, read everything available from the stream. If you get zero bytes back, the connection was closed because of an error such as an invalid command byte or other parsing error. If you get six bytes back, that's an error response that you can check for the response code and the ID of the notification that caused the error. You'll need to send every notification following that one again.

Once everything has been sent, do one last check for an error response.

It can take a while for the dropped connection to make its way from APNs back to your server just because of normal latency. It's possible to send over 500 notifications before a write fails because of the connection being dropped. Around 1,700 notifications writes can fail just because the pipe is full, so just retry in that case once the stream is ready for writing again.

Now, here's where the tradeoffs get interesting. You can check for an error response after every write, and you'll catch the error right away. But this causes a huge increase in the time it takes to send a batch of notifications.

Device tokens should almost all be valid if you've captured them correctly and you're sending them to the correct environment. So it makes sense to optimize assuming failures will be rare. You'll get way better performance if you wait for write to fail or the batch to complete before checking for an error response, even counting the time to send the dropped notifications again.

None of this is really specific to APNs, it applies to most socket-level programming.

If your development tool of choice supports multiple threads or interprocess communication, you could have a thread or process waiting for an error response all the time and let the main sending thread or process know when it should give up and retry.

This is taken from Apple's Tech Note: Troubleshooting Push Notifications.

I don't know how you detect in PHP that the write failed, but when it does, you should attempt to write the failed notification once again, and if it fails again, try to read the error response and close the connection.

If you manage to read the error response, you will know which notification failed and you'll know the error type (the most likely error is 8 - invalid device token). The code in the answer you referred to doesn't do anything after identifying that error. If after writing 100 messages you get an error response for the 80th message, you must resend messages 81 to 100, since Apple never received them. In my case (Java server), I don't always manage to read the error response (sometimes I get an error when trying to read the response from the socket). In that case I can only move on an send the next notifications (and have no way of knowing which notifications were actually received by Apple). That's why it's important to keep your database clean of invalid tokens.

If you keep your database clean (i.e. store in it only device tokens that were sent to your App by Apple, and all of them belong to the same push environment - either sandbox or production), you shouldn't encounter any invalid device tokens.

I encountered a similar problem to yours when implementing the push notification server side in Java. I couldn't reliably get all the error responses returned by Apple.

I found that in Java there's a way to disable the TCP Nagle's algorithm, which causes the buffering of multiple messages before sending them in a batch to Apple. Though Apple encourages us to use Nagle's algorithm (for performance reasons), I found that when I disable it and then try to read the response from Apple after each message I send to them, I manage to receive 100% of the error responses (I verified it by writing a process that simulated the APNS server).

By disabling Nagle's algorithm and sending the notifications one by one, slowly, and atempting to read the error response after each message, you can locate all the invalid tokens in your DB and remove them. Once you know your DB is clean you can enable Nagle's algorithm and resume sending notifications quickly without bothering to read the error responses from Apple. Then, whenever you get an error while writing a message to the socket, you can simply create a new socket and retry sending only the last message.

Apiculate answered 13/2, 2014 at 9:50 Comment(1)
This might be along the lines of what I need to fix. I am going the explore this and will report back.Apostle
A
6

There is no guarantee that push notifications will actually be delivered, even if the APNS server accepted them.

As far as your server is concerned, push notifications are fire-and-forget; there is no way to find out what the status of a notification is after you’ve sent it to APNS. The delivery time may also vary, from seconds up to half an hour.
Also, the user’s iPhone may not be able to receive push notifications all the time. They could be on a WiFi network that does not allow connections to be made to APNS because the required ports are blocked. Or the phone could be turned off.

APNS will try to deliver the last notification it received for that device when it comes back online, but it will only try for a limited time. Once it times out, the push notification will be lost forever!

Altonaltona answered 13/2, 2014 at 8:13 Comment(1)
I understand that this is the reality, but I do not think we are experiencing loss due to delivery failure on Apple's side. The same devices will receive 100% of messages sent via a different script, so it may be a result of sending a bulk message versus sending individualized messages.Apostle
A
0

The solution in the answer you linked to has a problem. It attemps to read the error response after each message is sent, but the read returns immediately and doesn't wait for a response to become available. While this is more efficient than waiting for a potential error response for X mili-seconds after each message, you might miss the error response and the connection may be dropped by Apple without you knowing any error occured.

While I can't give you code to solve your problem, I get give you some advice.

Here's the logic you should use (according to Apple), but I haven't managed to make it work reliably (at least not in my Java implementation):

Push Notification Throughput and Error Checking

If you're seeing throughput lower than 9,000 notifications per second, your server might benefit from improved error handling logic.

Here's how to check for errors when using the enhanced binary interface. Keep writing until a write fails. If the stream is ready for writing again, resend the notification and keep going. If the stream isn't ready for writing, see if the stream is available for reading.

If it is, read everything available from the stream. If you get zero bytes back, the connection was closed because of an error such as an invalid command byte or other parsing error. If you get six bytes back, that's an error response that you can check for the response code and the ID of the notification that caused the error. You'll need to send every notification following that one again.

Once everything has been sent, do one last check for an error response.

It can take a while for the dropped connection to make its way from APNs back to your server just because of normal latency. It's possible to send over 500 notifications before a write fails because of the connection being dropped. Around 1,700 notifications writes can fail just because the pipe is full, so just retry in that case once the stream is ready for writing again.

Now, here's where the tradeoffs get interesting. You can check for an error response after every write, and you'll catch the error right away. But this causes a huge increase in the time it takes to send a batch of notifications.

Device tokens should almost all be valid if you've captured them correctly and you're sending them to the correct environment. So it makes sense to optimize assuming failures will be rare. You'll get way better performance if you wait for write to fail or the batch to complete before checking for an error response, even counting the time to send the dropped notifications again.

None of this is really specific to APNs, it applies to most socket-level programming.

If your development tool of choice supports multiple threads or interprocess communication, you could have a thread or process waiting for an error response all the time and let the main sending thread or process know when it should give up and retry.

This is taken from Apple's Tech Note: Troubleshooting Push Notifications.

I don't know how you detect in PHP that the write failed, but when it does, you should attempt to write the failed notification once again, and if it fails again, try to read the error response and close the connection.

If you manage to read the error response, you will know which notification failed and you'll know the error type (the most likely error is 8 - invalid device token). The code in the answer you referred to doesn't do anything after identifying that error. If after writing 100 messages you get an error response for the 80th message, you must resend messages 81 to 100, since Apple never received them. In my case (Java server), I don't always manage to read the error response (sometimes I get an error when trying to read the response from the socket). In that case I can only move on an send the next notifications (and have no way of knowing which notifications were actually received by Apple). That's why it's important to keep your database clean of invalid tokens.

If you keep your database clean (i.e. store in it only device tokens that were sent to your App by Apple, and all of them belong to the same push environment - either sandbox or production), you shouldn't encounter any invalid device tokens.

I encountered a similar problem to yours when implementing the push notification server side in Java. I couldn't reliably get all the error responses returned by Apple.

I found that in Java there's a way to disable the TCP Nagle's algorithm, which causes the buffering of multiple messages before sending them in a batch to Apple. Though Apple encourages us to use Nagle's algorithm (for performance reasons), I found that when I disable it and then try to read the response from Apple after each message I send to them, I manage to receive 100% of the error responses (I verified it by writing a process that simulated the APNS server).

By disabling Nagle's algorithm and sending the notifications one by one, slowly, and atempting to read the error response after each message, you can locate all the invalid tokens in your DB and remove them. Once you know your DB is clean you can enable Nagle's algorithm and resume sending notifications quickly without bothering to read the error responses from Apple. Then, whenever you get an error while writing a message to the socket, you can simply create a new socket and retry sending only the last message.

Apiculate answered 13/2, 2014 at 9:50 Comment(1)
This might be along the lines of what I need to fix. I am going the explore this and will report back.Apostle
J
0

I have also experienced this problem in my several application. The reason of some devices that are not receiving the push notifications can be:

  • Using sandbox APNS server with production APNS certificate. -> Please check it
  • Both development and production provisional profiles installed on device. -> Remove both provisional and then you have to wait for atleast 24hr so that apple also remove the device token from its server.
Joe answered 15/2, 2014 at 15:1 Comment(0)
B
0

In my case, during development, some APNs where not being received in one device (I was testing with PushMeBaby).

I removed from my device all previous provisioning profiles related to my project, and from that moment everything went fine. To do that go to Settings > Profiles.

Maybe I had some kind of conflict with profiles since the project I am working on has changed the bundle id and the provisioning profiles since along the way.

Border answered 26/3, 2014 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.