djacobs / PyAPNs

Python library for interacting with the Apple Push Notification service (APNs)
http://pypi.python.org/pypi/apns/
MIT License
1.22k stars 374 forks source link

Sleep delay required between sends? #114

Closed C6silver closed 9 years ago

C6silver commented 9 years ago

I understand from reading past issues that if a bad token is found all remaining tokens will not be sent. The way to deal with this is to send them individually using enhanced mode and the listener to identify which one failed and then start again from the token that follows. However, I find that the error handling doesn't work unless there is a sleep timer in between sends. It seems it may need to be between .5 and 1 seconds but it isn't consistent. What is consistent is that without a sleep timer the error handling simply won't function. Is this delay really required and if so is there a best practice for how long it needs to be?

I have also asked this question out on stackoverflow but with no answers thus far: http://stackoverflow.com/questions/29178740/pyapns-and-the-need-to-sleep-between-sends

C6silver commented 9 years ago

Jim, I saw your note on stackoverflow and I left substantially more details in terms of code. Can you please check that thread again? Adding .5 seconds between sends is going to add a lot of time for a large pool of sends and my pushes are time sensitive. Thanks!

jimhorng commented 9 years ago

Hi @C6silver, I will, just being too busy recently :)

On Tue, Mar 24, 2015 at 12:34 PM, C6silver notifications@github.com wrote:

Jim, I saw your note on stackoverflow and I left substantially more details in terms of code. Can you please check that thread again? Adding .5 seconds between sends is going to add a lot of time for a large pool of sends and my pushes are time sensitive. Thanks!

— Reply to this email directly or view it on GitHub https://github.com/djacobs/PyAPNs/issues/114#issuecomment-85335418.

Best Regards, Jim(洪懷謙)

jimhorng commented 9 years ago

Hi @C6silver , answered: http://stackoverflow.com/a/29237112/324490

C6silver commented 9 years ago

Thanks, @jimhorng. I replied on stackoverflow. Unfortunately a delay is still necessary between sends even with the APNS obect created at the class level. I am using two tokens with the first bad and the second good. With no delay added it never sends the second token. With delay added the second token is sent. I have removed the listener as well as this was not necessary per your comment. Appreciate any further thoughts.

C6silver commented 9 years ago

@jimhorng I've edited my post on stackoverflow to include the edits I made based on your previous suggestion. Thanks for the continued help in trying to get this solved!

C6silver commented 9 years ago

After some back and forth with Jim on stackoverflow I understood that there needs to be some time between the overall sending of APNs and when the code exited. I was testing with only a few tokens and so the loop would get through those quickly and end which appears to be too fast for the APN send process to handle. Rather than impose a sleep delay between each send, I imposed a .5 second delay at the end of all sends before the code exited. This seems to have resolved the issue. Putting a hard coded time like that in feels a little too much like a "magic number" but it is workable. I thank @jimhorng again for his help.

C6silver commented 9 years ago

Actually I have to re-open this overall issue after further testing. While the approach of the sleep timer at end will work, the magic number is more variable than first thought. I believe the amount of time depends on the number of bad tokens. So the loop finishes and it will await the sleep timer but if the bad tokens have not gone through the re-try process fully it will fail. So potentially a sleep time of .5 or 1 second may be enough for a single bad token but the time has to increase if there are others. Since Apple does not give a positive code when the list has completed, we don't really know when it is safe to exit. Any suggestions on this point?

jimhorng commented 9 years ago

@C6silver , the concept is quite simple, just keep apns object in memory before it completed all error handling works. Usually there's a daemon/web server process that holds apns at module level, so that it will keep doing its work while the process is running. My suggestion to try sleep under writeMessage() is just an example to show it can do its work if not being garbage collected :) back to your code, you can either put apns object at module level or all its holder object at module level in order to ensure its life cycle throughout the execution process. test.py

notify = PushAdmin()
class ChannelStore(ndb.Model):
   def writeMessage(self,ID,name,message,imageKey,fileKey):
        notify.devChannelPush(ID,name,True)

or test2.py

apns = APNs(use_sandbox=True,cert_file="mycert.pem", key_file="mykey.pem", enhanced=True)
class PushAdmin(webapp2.RequestHandler):

    retryAPNList=[]
    channelID=""
    channelName = ""
    userName=""

    apns = APNs(use_sandbox=True,cert_file="mycert.pem", key_file="mykey.pem", enhanced=True)

    def devChannelPush(self,channel,name,sendAlerts):
        ucs = UsedChannelStore()
        pus = PushUpdateStore()
        channelName = ""
        refreshApnList = pus.getAPN(channel)
        if sendAlerts:
            alertApnList,channelName = ucs.getAPN(channel)
            if not alertApnList: alertApnList=[]
            if not refreshApnList: refreshApnList=[]
            pushApnList = list(set(alertApnList+refreshApnList))
        elif refreshApnList:
            pushApnList = refreshApnList
        else:
            pushApnList = []

        self.retryAPNList = pushApnList
        self.channelID = channel
        self.channelName = channelName
        self.userName = name
        self.retryAPNPush()

    def retryAPNPush(self):
        token = -1
        payload = Payload(alert="A message from " +self.userName+ " posted to "+self.channelName, sound="default", badge=1, custom={"channel":self.channelID})

        if len(self.retryAPNList)>0:
            token +=1
            for x in self.retryAPNList:
                    apns.gateway_server.send_notification(x, payload, identifier = token)
                    time.sleep(0.5)
C6silver commented 9 years ago

@jimhorng Thank you. I understand what you are saying. I went ahead and moved the apns = APNs(use_sandbox=True,cert_file="mycert.pem", key_file="mykey.pem", enhanced=True) outside of the class and this did work without the need for sleep! So to your point this will keep it in memory for as long as is required without magic numbers. The only issue I am experiencing now is when there is a bad token. By bad bad token I am using a production token in a development environment along with good development tokens. I have this "bad" production token at index 0. What I find is that the sending of good tokens (index 1) can be spotty. It will at times send the the payload at index 1 just fine while other times nothing will be sent. For a while during testing this was every other time but it changes and sometimes it is every 4th or 5th time. This never happens when all tokens are good. Is this something that can be dealt with inside of this program or is this at Apple?

Thanks!

jimhorng commented 9 years ago

@C6silver, no problem :) for good token after bad token problem, please paste code if it differs with original one. it's better to capture log and paste here, I sincerely suggest, there must be some way to get logs from google App Engine :)

C6silver commented 9 years ago

@jimhorng There is a log but it only shows things that raise to the level of an error within Python (or an appengine issue). It would seem that isn't happening with these or they are not being caught. Having said that there is an error I am getting that is unrelated as it largely doesn't generate when the problem I described above occurs. However, it has been happening with enough frequency to be of concern. I am pasting it below: 2015-03-26 18:34:48.729

exception occur when reading APNS error-response: <class 'google.appengine.api.remote_socket._remote_socketerror.error'>: [Errno 22] Invalid argument Traceback (most recent call last): File "/base/data/home/apps/s~browersr-ios/2.383163080487977390/apns.py", line 600, in run rlist, , _ = select.select([self._apns_connection._connection()], [], [], WAIT_READ_TIMEOUT_SEC) File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/remote_socket/_remote_socket.py", line 493, in select raise _SystemExceptionFromAppError(e) error: [Errno 22] Invalid argument

E 2015-03-26 18:34:48.729

sending notification with id:0 to APNS failed: <class 'google.appengine.api.remote_socket._remote_socket_error.error'>: [Errno 22] Invalid argument in 1th attempt, will wait 10 secs for next action Traceback (most recent call last): File "/base/data/home/apps/s~browersr-ios/2.383163080487977390/apns.py", line 533, in sendnotification self.write(message) File "/base/data/home/apps/s~browersr-ios/2.383163080487977390/apns.py", line 263, in write , wlist, _ = select.select([], [self._connection()], [], WAIT_WRITE_TIMEOUT_SEC) File "/base/data/home/runtimes/python27/python27_lib/versions/1/google/appengine/api/remote_socket/_remote_socket.py", line 493, in select raise _SystemExceptionFromAppError(e) error: [Errno 22] Invalid argument

C6silver commented 9 years ago

@jimhorng One way I seem to be able to make the above happen is to wait for some period of time and then try to run this process again. So I may step away for a while and then try to use this process and it seems I will always get the error message above. Now we did move the instantiation of the object out of the class. Do you think that is not closing properly such that the next time I come in it throws this error?

C6silver commented 9 years ago

I am going to close this and open another related to the socket error described above. I think the sleep issue was resolved as described and this is a different problem.

ibushong commented 9 years ago

Regarding: https://github.com/djacobs/PyAPNs/issues/114#issuecomment-86624499

Don't you need to declare apns as global? Or else it's treated as a separate reference (I tested this by subclassing APNs and adding a print statement to del())

Additionally, I can reproduce this issue. Even if the APNs reference is garbage collected, the error handling/resending still works (confirmed with logging). Maybe this is dependent on hardware/system set up