Redth / PushSharp

A server-side library for sending Push Notifications to iOS (iPhone/iPad APNS), Android (C2DM and GCM - Google Cloud Message), Windows Phone, Windows 8, Amazon, Blackberry, and (soon) FirefoxOS devices!
Other
4.38k stars 1.52k forks source link

System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host #694

Open elliotbd opened 8 years ago

elliotbd commented 8 years ago

What version of PushSharp are you using?

4.0.10

Describe your issue:

Sending push notifications to APNS fails after 1,777 with the connection being closed by remote host.

This is similar to:

https://github.com/Redth/PushSharp/issues/609 (posted by mahdi but closed for V3.0 Beta 63). But user mahdi (https://github.com/mahdi) seems to be experiencing the same problem in PushSharp version 4.0.10 as I am:

http://www.simosh.com/article/dgfbfhbb-pushsharp-unable-to-write-data-to-the-transport-connection.html

What are the steps required to reproduce this issue?

Send 1,7777 APNS messages with the code fragment below with the payload of:

{"aps":{"alert":"Jerry and Judy XXXX: Practical Advice for Grandparents (Part 2 of 2)","badge":0}}

Please provide any Exception Stack Traces

2016-04-27 05:00:12.AM [INFO] APNS-CLIENT[1]: Retrying Batch: Batch ID=3, Error=System.IO.IOException: Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host

at System.Net.Sockets.Socket.EndSend(IAsyncResult asyncResult)

at System.Net.Sockets.NetworkStream.EndWrite(IAsyncResult asyncResult)

--- End of inner exception stack trace ---

at System.Net.Security._SslStream.EndWrite(IAsyncResult asyncResult)

at System.Net.Security.SslStream.EndWrite(IAsyncResult asyncResult)

at System.IO.Stream.<>c.b__53_1(Stream stream, IAsyncResult asyncResult)

at System.Threading.Tasks.TaskFactory1.FromAsyncTrimPromise1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)

--- End of stack trace from previous location where exception was thrown ---

at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)

at System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd(Task task)

at PushSharp.Apple.ApnsConnection.d__21.MoveNext()

2016-04-27 05:00:12.AM [INFO] APNS-Client[1]: Disconnecting (Batch ID=3)

Code Fragment

  try
            {
                // Get device list
                DeviceTokenContext repository = new DeviceTokenContext();
                IEnumerable<DeviceToken> deviceTokenList = repository.GetDeviceTokens();

                if (deviceTokenList.Count() > 0)
                {

                    // Registering the Apple Service and sending an iOS Notification
                    var certPath = section["certificatePath"];
                    var appleCert = File.ReadAllBytes(certPath);

                    var password = section["apnsCertificatePW"];

                    var environment = ApnsConfiguration.ApnsServerEnvironment.Sandbox;
                    var prodFlag = section["productionFlag"];
                    if (string.Equals(prodFlag, "true", StringComparison.OrdinalIgnoreCase))
                    {
                        environment = ApnsConfiguration.ApnsServerEnvironment.Production;
                    }

                    var config = new ApnsConfiguration(environment, appleCert, password);

                    // Create our push services broker
                    var apnsBroker = new ApnsServiceBroker(config);

                    // Wire up events
                    apnsBroker.OnNotificationFailed += (notification, aggregateEx) => {

                            aggregateEx.Handle (ex => {

                            // See what kind of exception it was to further diagnose
                            if (ex is ApnsNotificationException) {
                                var notificationException = (ApnsNotificationException)ex;

                                // Deal with the failed notification
                                var apnsNotification = notificationException.Notification;
                                var statusCode = notificationException.ErrorStatusCode;

                                Console.WriteLine ("Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}");

                            } else {
                                // Inner exception might hold more useful information like an ApnsConnectionException           
                                Console.WriteLine ("Apple Notification Failed for some unknown reason : {ex.InnerException}");
                            }

                            // Mark it as handled
                            return true;
                        });
                    };

                    apnsBroker.OnNotificationSucceeded += (notification) => {
                        Program.messagesSent++;
                        Console.WriteLine("Sent: (" + Program.messagesSent + ") -- " + DateTime.Now + " -> " + notification);
                        Console.WriteLine ("Apple Notification Sent!");
                    };

                    // Start the broker
                    apnsBroker.Start ();

                    // Loop thru devices and sent out notifications
                    foreach (DeviceToken deviceToken in deviceTokenList)
                    {
                        // check for test token ids and skip
                        if (deviceToken.Name.ToString().Length > 8)
                        {
                            SendPushNotificationMessage(apnsBroker, alertMessage, deviceToken.Name.ToString());
                        }
                    }

                    Console.WriteLine("Waiting for Queue to Finish...");

                    // Stop and wait for the queues to drains
                    apnsBroker.Stop ();

                    Console.WriteLine("Done");
                }

            }
            // Handle notification expections
            catch (Exception ex)
            {
                Console.WriteLine("Exception: {0}", ex.ToString());
            }

            Console.SetOut(oldOut);
            if (pushLogging)
            {
                writer.Close();
                ostrm.Close();
            }

        }

        /// <summary>
        /// Send out individual notification per device
        /// </summary>
        /// <param name="alertMessage">String containing notification message</param>
        /// <param name="deviceToken">Device identifier token</param>
        private static void SendPushNotificationMessage(ApnsServiceBroker apnsBroker, string alertMessage, string deviceToken)
        {
            Console.WriteLine("Sending ... deviceToken: -> " + deviceToken);

            String jsonPayload = "{ \"aps\" : { \"alert\" : \"" + alertMessage + "\",\"badge\" : 0 }}";

            apnsBroker.QueueNotification(new ApnsNotification
            {
                DeviceToken = deviceToken,
                Payload = JObject.Parse(jsonPayload)
            });

        }
kretovd commented 8 years ago

Hello! I have the same issue

kretovd commented 8 years ago

But if i send 100 deviceToken per 1 broker session(start, stop) it will work.

hleivacr commented 8 years ago

i have the same issue!

i tested the certificate and the device using http://apns-gcm.bryantan.info/ and seems to work.

Darazhanski commented 8 years ago

I have the same issue. I cannot send to more than 100 devices at a time. I will try to stop the broker every 100 devices in order to flush the queue and start it again for the next 100. I am hoping this works, but it is a kind of a hack. Has anyone found a better solution to this?

zdjohn commented 8 years ago

same here, wondering if there is any proper fix on this issue.

our notification server do auto scaling, I am wondering if there is any more robust work around then hard setting limits on number of devices.

attilabicsko commented 8 years ago

I've been struggling the same issue over weeks now. In my case, there were a few device tokens which were not marked as expired by the Feedback Service somehow, so they did remain in our DB. After you try to enqueue a notification for a number of invalid tokens, Apple closes the connection, and PushSharp fails to deliver the rest of the messages, because it doesn't open a new connection in that case.

My solution was to set up the broker's event listener to log all device IDs and exception stack trace in the OnNotificationFailed event, and send one push for each device one-by-one, so by making a new connection for each to avoid Apple's connection refusal after a number of invalid devices. Then collected device IDs from the logs which received an "InvalidToken" or "Invalid DeviceToken" error in the AggregateException and marked them as invalid in the DB.

In my experience these devices somehow never get reported by the Feedback Service, so a possible solution is to invalidate/delete devices from the DB directly in the OnNotificationFailed event if the exception is an ApnsNotificationException and the status code is 8 (InvalidToken).

jilnesta commented 7 years ago

Hi. I have the same issue when I try send to 400 device with the same message in payload.

penguinawesome commented 4 years ago

Hi, we have the same issue. Is there any workaround? aside from stopping and starting the broker? In our case we just leave the broker opened for the entire duration of the service