Skip to content

Instantly share code, notes, and snippets.

@vanstee
Last active December 15, 2015 01:09
Show Gist options
  • Select an option

  • Save vanstee/5178383 to your computer and use it in GitHub Desktop.

Select an option

Save vanstee/5178383 to your computer and use it in GitHub Desktop.

Plan for Fixing Up Grocer

+---------+
| Pusher  |
+---------+
     |
     v
+---------+                                            +----------------+
|  Queue  |------------------------------------------->| ConnectionPool |
+---------+                                            +----------------+
     ^                                                          |
     |                                                          |
     +---------------------------+                              |
                                 |                              v
+---------+         +------------------------+         +----------------+
| History |-------->| Error Response Checker |-------->|   Connection   |------------------+
+---------+         +------------------------+         +----------------+                  |
     ^                           ^                              |                          |
     |                           |                              v                          |
     |                           |             +---------------------------------+         |
     |                           +-------------| Apple Push Notification Service |         |
     |                                         +---------------------------------+         |
     |                                                                                     |
     |                                                                                     |
     |                                                                                     |
     +-------------------------------------------------------------------------------------+

So we currently have 3 problems/missing features:

  • Sending messages is not thread safe
  • Not receiving error responses
  • Not aware of which messages Apple successfully received

I figured this layout would be a good way to solve all three. We can probably have a DispatchCenter class or something that holds on to the Queue of notifications to be sent, ConnectionPool of open connections to APNS, ErrorResponseChecker for each connection that blocks reading data on the APNS connection, and a History of notification messages.

Here's an example of the client code:

# Raises an error if we can't connect
pusher = Grocer.pusher(...)

notification = Grocer::Notification.new(...)
invalid_notification = Grocer::Notification.new(...)
notifications = [invalid_notification, notification]

# Adds the notification to the queue and immediately returns
pusher.push!(notification)

# Adds the notification to the queue and waits for it to be delivered
pusher.push(notification)

# Adds the notification to the queue, waits for it to be delivered, and
# waits an extra `ERROR_TIMEOUT` milliseconds to give APNS a chance to
# reject the notification. If a notification is rejected it rewinds to
# the position of the rejected notification and replays unsent
# notifications (since they wouldn't have been delivered).
pusher.ensure_push(notification)

# Adds a list of notifications to the queue, then waits once at the end
# once all of them have been added.
notifications.each do |notification|
  pusher.push(notification)
end

pusher.ensure_delivery

# Similar to the above but waits at the end of the block
pusher.ensure_delivery do
  notifications.each do |notification|
    push(notification)
  end
end

# I'm not sure if we need this yet, but it might also be nice to hold
# on to notifications that could not be delivered
pusher.failed_notifications # => [invalid_notification]
@stevenharman
Copy link
Copy Markdown

If we have a thread-safe queue of notifications to be sent, and another of those that were sent, I don't think we'd need a connection pool w/in Grocer - clients cold simply have multiple connections if they need, or share a single, fast connection amongst many threads. That would also let us not worry about the "batch send" thing (where we send and check plus one extra read at the end) b/c we'd always have a listening thread.

I think that would also let us keep the API small and consistent.

A back of the napkin design: pusher.push(notification) and it is added to the send queue and then immediately returns (perhaps returning an :identifier?). A send-thread constantly pops off the send queue, writes the notification to the TCP connection, and pushes it onto the sent queue. The error_response_listener thread is always reading data from the TCP connection, re-enqueuing when an error happens.

@vanstee
Copy link
Copy Markdown
Author

vanstee commented Mar 17, 2013

What about short running processes like a script that just runs this:

pusher = Grocer.pusher(...)
pusher.push(notification)

The notification would get pushed onto the queue, but then the program would exit before the Connection thread had a chance to grab it and send it, right?

Then there's the case where you wait for the connection to actually send the notification, but you don't wait long enough for the ErrorResponseChecker to receive the error from Apple and you exit.

One more example. Let's say we wait for the notification to be sent for each notification or even wait for some length of time to make sure Apple doesn't send us anything, if you do pusher.push(...) a few times in a row, wouldn't you be unnecessarily waiting multiple times when really you just want to wait at the end?

These were the three cases I was trying to cover with push!, push, ensure_push, and ensure_delivery, but I don't think I fully understand how the network connection + threads stuff works. Do these "levels of async" that I'm trying to plan for even make sense?

@vanstee
Copy link
Copy Markdown
Author

vanstee commented Mar 17, 2013

Oh but I agree with the connection pool piece. I think it would be pretty easy to add once the shared queue was in place. I guess each new connection would just have a handle on some singleton queue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment