Skip to content

Commit 681c1cb

Browse files
authored
Add retries to apple push notifications (#128)
* Add retries to apple push notifications * Add retry timeout config * Add check for retry timeout being greater than send timeout
1 parent 1b3d152 commit 681c1cb

File tree

4 files changed

+70
-10
lines changed

4 files changed

+70
-10
lines changed

config/mattermost-push-proxy.sample.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"ThrottleVaryByHeader":"X-Forwarded-For",
66
"EnableMetrics": false,
77
"SendTimeoutSec": 30,
8+
"RetryTimeoutSec": 8,
89
"ApplePushSettings":[
910
{
1011
"Type":"apple",

server/apple_notification_server.go

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ type AppleNotificationServer struct {
2525
logger *Logger
2626
ApplePushSettings ApplePushSettings
2727
sendTimeout time.Duration
28+
retryTimeout time.Duration
2829
}
2930

30-
func NewAppleNotificationServer(settings ApplePushSettings, logger *Logger, metrics *metrics, sendTimeoutSecs int) *AppleNotificationServer {
31+
func NewAppleNotificationServer(settings ApplePushSettings, logger *Logger, metrics *metrics, sendTimeoutSecs int, retryTimeoutSecs int) *AppleNotificationServer {
3132
return &AppleNotificationServer{
3233
ApplePushSettings: settings,
3334
metrics: metrics,
3435
logger: logger,
3536
sendTimeout: time.Duration(sendTimeoutSecs) * time.Second,
37+
retryTimeout: time.Duration(retryTimeoutSecs) * time.Second,
3638
}
3739
}
3840

@@ -227,15 +229,8 @@ func (me *AppleNotificationServer) SendNotification(msg *PushNotification) PushR
227229

228230
if me.AppleClient != nil {
229231
me.logger.Infof("Sending apple push notification for device=%v type=%v ackId=%v", me.ApplePushSettings.Type, msg.Type, msg.AckID)
230-
start := time.Now()
231-
232-
ctx, cancel := context.WithTimeout(context.Background(), me.sendTimeout)
233-
defer cancel()
234232

235-
res, err := me.AppleClient.PushWithContext(ctx, notification)
236-
if me.metrics != nil {
237-
me.metrics.observerNotificationResponse(PushNotifyApple, time.Since(start).Seconds())
238-
}
233+
res, err := me.SendNotificationWithRetry(notification)
239234
if err != nil {
240235
me.logger.Errorf("Failed to send apple push sid=%v did=%v err=%v type=%v", msg.ServerID, msg.DeviceID, err, me.ApplePushSettings.Type)
241236
if me.metrics != nil {
@@ -269,3 +264,51 @@ func (me *AppleNotificationServer) SendNotification(msg *PushNotification) PushR
269264
}
270265
return NewOkPushResponse()
271266
}
267+
268+
func (me *AppleNotificationServer) SendNotificationWithRetry(notification *apns.Notification) (*apns.Response, error) {
269+
var res *apns.Response
270+
var err error
271+
waitTime := time.Second
272+
273+
// Keep a general context to make sure the whole retry
274+
// doesn't take longer than the timeout.
275+
generalContext, cancelGeneralContext := context.WithTimeout(context.Background(), me.sendTimeout)
276+
defer cancelGeneralContext()
277+
278+
for retries := 0; retries < MAX_RETRIES; retries++ {
279+
start := time.Now()
280+
281+
retryContext, cancelRetryContext := context.WithTimeout(generalContext, me.retryTimeout)
282+
defer cancelRetryContext()
283+
res, err = me.AppleClient.PushWithContext(retryContext, notification)
284+
if me.metrics != nil {
285+
me.metrics.observerNotificationResponse(PushNotifyApple, time.Since(start).Seconds())
286+
}
287+
288+
if err == nil {
289+
break
290+
}
291+
292+
me.logger.Errorf("Failed to send apple push did=%v retry=%v error=%v", notification.DeviceToken, retries, err)
293+
294+
if retries == MAX_RETRIES-1 {
295+
me.logger.Errorf("Max retries reached did=%v", notification.DeviceToken)
296+
break
297+
}
298+
299+
select {
300+
case <-generalContext.Done():
301+
case <-time.After(waitTime):
302+
}
303+
304+
if generalContext.Err() != nil {
305+
me.logger.Infof("Not retrying because context error did=%v retry=%v error=%v", notification.DeviceToken, retries, generalContext.Err())
306+
err = generalContext.Err()
307+
break
308+
}
309+
310+
waitTime *= 2
311+
}
312+
313+
return res, err
314+
}

server/config_push_proxy.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type ConfigPushProxy struct {
1717
ThrottleVaryByHeader string
1818
LogFileLocation string
1919
SendTimeoutSec int
20+
RetryTimeoutSec int
2021
ApplePushSettings []ApplePushSettings
2122
EnableMetrics bool
2223
EnableConsoleLog bool
@@ -81,6 +82,20 @@ func LoadConfig(fileName string) (*ConfigPushProxy, error) {
8182
if !cfg.EnableConsoleLog && !cfg.EnableFileLog {
8283
cfg.EnableConsoleLog = true
8384
}
85+
86+
// Set timeout defaults
87+
if cfg.SendTimeoutSec == 0 {
88+
cfg.SendTimeoutSec = 30
89+
}
90+
91+
if cfg.RetryTimeoutSec == 0 {
92+
cfg.RetryTimeoutSec = 8
93+
}
94+
95+
if cfg.RetryTimeoutSec > cfg.SendTimeoutSec {
96+
cfg.RetryTimeoutSec = cfg.SendTimeoutSec
97+
}
98+
8499
if cfg.EnableFileLog {
85100
if cfg.LogFileLocation == "" {
86101
// We just do an mkdir -p equivalent.

server/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
HEADER_REAL_IP = "X-Real-IP"
2828
WAIT_FOR_SERVER_SHUTDOWN = time.Second * 5
2929
CONNECTION_TIMEOUT_SECONDS = 60
30+
MAX_RETRIES = 3
3031
)
3132

3233
type NotificationServer interface {
@@ -69,7 +70,7 @@ func (s *Server) Start() {
6970
}
7071

7172
for _, settings := range s.cfg.ApplePushSettings {
72-
server := NewAppleNotificationServer(settings, s.logger, m, s.cfg.SendTimeoutSec)
73+
server := NewAppleNotificationServer(settings, s.logger, m, s.cfg.SendTimeoutSec, s.cfg.RetryTimeoutSec)
7374
err := server.Initialize()
7475
if err != nil {
7576
s.logger.Errorf("Failed to initialize client: %v", err)

0 commit comments

Comments
 (0)