Skip to content

Commit

Permalink
Big changes on Apple notification management
Browse files Browse the repository at this point in the history
There were several changes to the underlying base Channel and Service
classes to, but many changes to the Apple Channel, for how it manages
notifications, and how it decides when all queued messages have been
dealt with and it can stop.  This should really improve the durability
of the library, especially when calling .Stop() or stopping a service
immediately after queueing some notifications!

Added some Logging statements in the service that may be useful to some.

Added nuget version 1.1.0.0

Fixes Redth#69, Fixes Redth#75
  • Loading branch information
Redth committed Jan 23, 2013
1 parent 5d1cb87 commit fb09696
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 133 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ TestResults
#NuGet
packages/

Xamarin/
Xamarin/
PushSharp.Sample.LocalTesting/
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PushSharp.ClientSample.WindowsPhone", "PushSharp.ClientSample.WindowsPhone\PushSharp.ClientSample.WindowsPhone.csproj", "{03FB674A-89FA-4E76-9CA0-6F3869D57336}"
EndProject
Global
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<CurrentDeployCmdId>256</CurrentDeployCmdId>
<CurrentDeployID>F4D8DB2A-0043-45A9-8411-F003F31519F7</CurrentDeployID>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
<CurrentDeployCmdId>256</CurrentDeployCmdId>
<CurrentDeployID>F4D8DB2A-0043-45A9-8411-F003F31519F7</CurrentDeployID>
</PropertyGroup>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{C089C8C0-30E0-4E22-80C0-CE093F111A43}">
<SilverlightMobileCSProjectFlavor>
<FullDeploy>False</FullDeploy>
<DebuggerType>Managed</DebuggerType>
<DebuggerAgentType>Managed</DebuggerAgentType>
<Tombstone>False</Tombstone>
</SilverlightMobileCSProjectFlavor>
</FlavorProperties>
</VisualStudio>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void RegisterForToast()
HttpNotificationChannel pushChannel;

// The name of our push channel.
string channelName = "PushSharp.NotificationChannel";
string channelName = "PushSharp.NotificationChannel.Tile";

// Try to find the push channel.
pushChannel = HttpNotificationChannel.Find(channelName);
Expand Down Expand Up @@ -50,11 +50,6 @@ public void RegisterForToast()
//});

pushChannel.Open();

// Bind this new channel for toast events.
pushChannel.BindToShellToast();


}
else
{
Expand All @@ -69,15 +64,38 @@ public void RegisterForToast()
System.Diagnostics.Debug.WriteLine("PushChannel Error: " + e.ErrorType.ToString() + " -> " + e.ErrorCode + " -> " + e.Message + " -> " + e.ErrorAdditionalData);
});

// Bind this new channel for toast events.
if (pushChannel.IsShellToastBound)
Console.WriteLine("Already Bound to Toast");
else
pushChannel.BindToShellToast();

if (pushChannel.IsShellTileBound)
Console.WriteLine("Already Bound to Tile");
else
pushChannel.BindToShellTile();

//// Register for this notification only if you need to receive the notifications while your application is running.
//pushChannel.ShellToastNotificationReceived += new EventHandler<NotificationEventArgs>((sender, e) =>
//{
// //Yay
//});
}

// Bind this new channel for toast events.
if (pushChannel.IsShellToastBound)
Console.WriteLine("Already Bound to Toast");
else
pushChannel.BindToShellToast();

// Display the URI for testing purposes. Normally, the URI would be passed back to your web service at this point.
if (pushChannel.IsShellTileBound)
Console.WriteLine("Already Bound to Tile");
else
pushChannel.BindToShellTile();

// Display the URI for testing purposes. Normally, the URI would be passed back to your web service at this point.
if (pushChannel != null && pushChannel.ChannelUri != null)
System.Diagnostics.Debug.WriteLine(pushChannel.ChannelUri.ToString());
}
}
}
}
Binary file added NuGet/PushSharp.1.1.0.0.nupkg
Binary file not shown.
2 changes: 1 addition & 1 deletion PushSharp.Android/C2dm/C2dmPushChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public override void Stop(bool waitForQueueToDrain)
base.Stop(waitForQueueToDrain);

var slept = 0;
while (Interlocked.Read(ref waitCounter) > 0 && slept <= 30000)
while (Interlocked.Read(ref waitCounter) > 0 && slept <= 5000)
{
slept += 100;
Thread.Sleep(100);
Expand Down
5 changes: 2 additions & 3 deletions PushSharp.Android/Gcm/GcmPushChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ void transport_UnhandledException(GcmNotification notification, Exception except
//Raise individual failures for each registration id for the notification
foreach (var r in notification.RegistrationIds)
{
this.Events.RaiseNotificationSendFailure(GcmNotification.ForSingleRegistrationId(notification, r),
exception);
this.Events.RaiseNotificationSendFailure(GcmNotification.ForSingleRegistrationId(notification, r), exception);
}

this.Events.RaiseChannelException(exception, PlatformType.AndroidGcm, notification);
Expand Down Expand Up @@ -102,7 +101,7 @@ public override void Stop(bool waitForQueueToDrain)
base.Stop(waitForQueueToDrain);

var slept = 0;
while (Interlocked.Read(ref waitCounter) > 0 && slept <= 30000)
while (Interlocked.Read(ref waitCounter) > 0 && slept <= 5000)
{
slept += 100;
Thread.Sleep(100);
Expand Down
179 changes: 97 additions & 82 deletions PushSharp.Apple/ApplePushChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public ApplePushChannel(ApplePushChannelSettings channelSettings, PushServiceSet
certificates.Add(addlCert);

//Start our cleanup task
taskCleanup = new Task(() => Cleanup(), TaskCreationOptions.LongRunning);
taskCleanup = new Task(() => { Cleanup(); }, TaskCreationOptions.LongRunning);
taskCleanup.ContinueWith((t) => { var ex = t.Exception; }, TaskContinuationOptions.OnlyOnFaulted);
taskCleanup.Start();
}
Expand All @@ -85,46 +85,68 @@ public override PlatformType PlatformType
SslStream stream;
System.IO.Stream networkStream;
Task taskCleanup;

protected override void SendNotification(Common.Notification notification)
{
var appleNotification = notification as AppleNotification;

bool isOkToSend = true;
byte[] notificationData = new byte[] {};
protected long trackedNotificationCount = 0;

try
{
notificationData = appleNotification.ToBytes();
}
catch (NotificationFailureException nfex)

public override bool QueueNotification(Notification notification, bool countsAsRequeue = true, bool ignoreStoppingChannel = false)
{
if (base.QueueNotification(notification, countsAsRequeue, ignoreStoppingChannel))
{
//Bad notification format already
isOkToSend = false;
if (!ignoreStoppingChannel)
Interlocked.Increment(ref trackedNotificationCount);

this.Events.RaiseNotificationSendFailure(notification, nfex);
return true;
}

if (isOkToSend)
return false;
}

protected override void SendNotification(Common.Notification notification)
{
lock (sentLock)
{
Connect();

try
var appleNotification = notification as AppleNotification;

bool isOkToSend = true;
byte[] notificationData = new byte[] {};

try
{
lock (streamWriteLock)
notificationData = appleNotification.ToBytes();
}
catch (NotificationFailureException nfex)
{
//Bad notification format already
isOkToSend = false;

Interlocked.Decrement(ref trackedNotificationCount);

this.Events.RaiseNotificationSendFailure(notification, nfex);
}

if (isOkToSend)
{
Connect();

try
{
lock (sentLock)
lock (streamWriteLock)
{
networkStream.Write(notificationData, 0, notificationData.Length);
lock (sentLock)
{
networkStream.Write(notificationData, 0, notificationData.Length);

sentNotifications.Add(new SentNotification(appleNotification));
sentNotifications.Add(new SentNotification(appleNotification));
}
}
}
catch (Exception)
{
//If this failed, we probably had a networking error, so let's requeue the notification
this.QueueNotification(notification, true, true);
}
}
catch (Exception)
{
this.QueueNotification(notification);
} //If this failed, we probably had a networking error, so let's requeue the notification
}
}

Expand All @@ -135,19 +157,24 @@ public override void Stop(bool waitForQueueToDrain)
//See if we want to wait for the queue to drain before stopping
if (waitForQueueToDrain)
{
while (QueuedNotificationCount > 0 || sentNotifications.Count > 0)
Thread.Sleep(50);
}
var sentNotificationCount = 0;
lock (sentLock)
sentNotificationCount = sentNotifications.Count;

//Sleep a bit to prevent any race conditions
//especially since our cleanup method may need 3 seconds
Thread.Sleep(5000);
while (QueuedNotificationCount > 0 || sentNotificationCount > 0 || Interlocked.Read(ref trackedNotificationCount) > 0)
{
Thread.Sleep(100);

lock (sentLock)
sentNotificationCount = sentNotifications.Count;
}
}

if (!CancelTokenSource.IsCancellationRequested)
CancelTokenSource.Cancel();

//Wait on our tasks for a maximum of 30 seconds
Task.WaitAll(new Task[] { base.taskSender, taskCleanup }, 30000);
//Wait on our tasks for a maximum of 5 seconds
Task.WaitAll(new Task[] { base.taskSender, taskCleanup }, 5000);
}

void Reader()
Expand Down Expand Up @@ -178,53 +205,8 @@ void Reader()
var status = readBuffer[1];
var identifier = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(readBuffer, 2));

int failedNotificationIndex = -1;
SentNotification failedNotification = null;

//Try and find the failed notification in our sent list
for (int i = 0; i < sentNotifications.Count; i++)
{
var n = sentNotifications[i];

if (n.Identifier.Equals(identifier))
{
failedNotificationIndex = i;
failedNotification = n;
break;
}
}

//Don't bother doing anything unless we know what failed
if (failedNotification != null && failedNotificationIndex > -1)
{
//Anything before the failed message must have sent OK
// so let's expedite the success status Success for all those before the failed one
if (failedNotificationIndex > 0)
{
for (int i = 0; i < failedNotificationIndex; i++)
this.Events.RaiseNotificationSent(sentNotifications[i].Notification);
}

//The notification that failed needs to have a failure event raised
// we don't requeue it because apple told us it failed for real
this.Events.RaiseNotificationSendFailure(failedNotification.Notification,
new NotificationFailureException(status, failedNotification.Notification));

// finally, raise failure for anything after the index of this failed one
// in the sent list, since we may have sent them but apple will have disregarded
// anything after the failed one and not told us about it
if (failedNotificationIndex < sentNotifications.Count - 1)
{
//Requeue the failed notification since we're not sure it's a bad
// notification, just that it was sent after a bad one was
for (int i = failedNotificationIndex + 1; i <= sentNotifications.Count - 1; i++)
this.QueueNotification(sentNotifications[i].Notification, false);
}

//Now clear out the sent list since we processed them all manually above
sentNotifications.Clear();
}

HandleFailedNotification(identifier, status);

//Start reading again
Reader();
}
Expand All @@ -248,6 +230,36 @@ void Reader()
}
}

void HandleFailedNotification(int identifier, byte status)
{
//Get the index of our failed notification (by identifier)
var failedIndex = sentNotifications.FindIndex(n => n.Identifier == identifier);

if (failedIndex < 0)
return;

//Get the failed notification itself
var failedNotification = sentNotifications[failedIndex];

//Fail and remove the failed index from the list
Interlocked.Decrement(ref trackedNotificationCount);
this.Events.RaiseNotificationSendFailure(failedNotification.Notification, new NotificationFailureException(status, failedNotification.Notification));
sentNotifications.RemoveAt(failedIndex);

//All Notifications after the failed one have been shifted back one space now
//Grab all the notifications from the list that are after the failed index
var toRequeue = sentNotifications.GetRange(failedIndex, sentNotifications.Count - (failedIndex + 1)).ToList();
//Remove that same range (those ones failed since they were sent after the one apple told us failed, so
// apple will ignore them, and we need to requeue them to be tried again
sentNotifications.RemoveRange(failedIndex, sentNotifications.Count - (failedIndex + 1));

//Requeue all the messages that were sent afte the failed one, be sure it doesn't count as a 'requeue' to go towards the maximum # of retries
//Also ignore that the channel is stopping
foreach (var n in toRequeue)
this.QueueNotification(n.Notification, false, true);

}

void Cleanup()
{
while (true)
Expand All @@ -270,6 +282,9 @@ void Cleanup()
if (n.SentAt < DateTime.UtcNow.AddMilliseconds(-1 * appleSettings.MillisecondsToWaitBeforeMessageDeclaredSuccess))
{
wasRemoved = true;

Interlocked.Decrement(ref trackedNotificationCount);

this.Events.RaiseNotificationSent(n.Notification);
sentNotifications.RemoveAt(0);
}
Expand Down
4 changes: 2 additions & 2 deletions PushSharp.Common/AssemblyVersionInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.18.0")]
[assembly: AssemblyFileVersion("1.0.18.0")]
[assembly: AssemblyVersion("1.1.0.0")]
[assembly: AssemblyFileVersion("1.1.0.0")]
Loading

0 comments on commit fb09696

Please sign in to comment.