mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	push_notifications: Send all APNS devices in parallel.
Instead of starting up one event loop for every device send, use asyncio.gather to send to all of a user's devices at once.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							69825cd54c
						
					
				
				
					commit
					7787fe3f49
				
			@@ -6,7 +6,19 @@ import logging
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
from dataclasses import dataclass
 | 
					from dataclasses import dataclass
 | 
				
			||||||
from functools import lru_cache
 | 
					from functools import lru_cache
 | 
				
			||||||
from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Optional, Sequence, Tuple, Type, Union
 | 
					from typing import (
 | 
				
			||||||
 | 
					    TYPE_CHECKING,
 | 
				
			||||||
 | 
					    Any,
 | 
				
			||||||
 | 
					    Dict,
 | 
				
			||||||
 | 
					    Iterable,
 | 
				
			||||||
 | 
					    List,
 | 
				
			||||||
 | 
					    Mapping,
 | 
				
			||||||
 | 
					    Optional,
 | 
				
			||||||
 | 
					    Sequence,
 | 
				
			||||||
 | 
					    Tuple,
 | 
				
			||||||
 | 
					    Type,
 | 
				
			||||||
 | 
					    Union,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import gcm
 | 
					import gcm
 | 
				
			||||||
import lxml.html
 | 
					import lxml.html
 | 
				
			||||||
@@ -242,25 +254,41 @@ def send_apple_push_notification(
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
    payload_data = dict(modernize_apns_payload(payload_data))
 | 
					    payload_data = dict(modernize_apns_payload(payload_data))
 | 
				
			||||||
    message = {**payload_data.pop("custom", {}), "aps": payload_data}
 | 
					    message = {**payload_data.pop("custom", {}), "aps": payload_data}
 | 
				
			||||||
    for device in devices:
 | 
					 | 
				
			||||||
        # TODO obviously this should be made to actually use the async
 | 
					 | 
				
			||||||
        request = aioapns.NotificationRequest(
 | 
					 | 
				
			||||||
            device_token=device.token, message=message, time_to_live=24 * 3600
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					    async def send_all_notifications() -> Iterable[
 | 
				
			||||||
            result = apns_context.loop.run_until_complete(
 | 
					        Tuple[DeviceToken, Union[aioapns.common.NotificationResult, BaseException]]
 | 
				
			||||||
                apns_context.apns.send_notification(request)
 | 
					    ]:
 | 
				
			||||||
 | 
					        requests = [
 | 
				
			||||||
 | 
					            aioapns.NotificationRequest(
 | 
				
			||||||
 | 
					                device_token=device.token, message=message, time_to_live=24 * 3600
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        except aioapns.exceptions.ConnectionError:
 | 
					            for device in devices
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        results = list(
 | 
				
			||||||
 | 
					            await asyncio.gather(
 | 
				
			||||||
 | 
					                *(apns_context.apns.send_notification(request) for request in requests),
 | 
				
			||||||
 | 
					                return_exceptions=True,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return zip(devices, results)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    results = apns_context.loop.run_until_complete(send_all_notifications())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for device, result in results:
 | 
				
			||||||
 | 
					        if isinstance(result, aioapns.exceptions.ConnectionError):
 | 
				
			||||||
            logger.error(
 | 
					            logger.error(
 | 
				
			||||||
                "APNs: ConnectionError sending for user %s to device %s; check certificate expiration",
 | 
					                "APNs: ConnectionError sending for user %s to device %s; check certificate expiration",
 | 
				
			||||||
                user_identity,
 | 
					                user_identity,
 | 
				
			||||||
                device.token,
 | 
					                device.token,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
            continue
 | 
					        elif isinstance(result, BaseException):
 | 
				
			||||||
 | 
					            logger.exception(
 | 
				
			||||||
        if result.is_successful:
 | 
					                "APNs: Error sending for user %s to device %s",
 | 
				
			||||||
 | 
					                user_identity,
 | 
				
			||||||
 | 
					                device.token,
 | 
				
			||||||
 | 
					                exc_info=result,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        elif result.is_successful:
 | 
				
			||||||
            logger.info(
 | 
					            logger.info(
 | 
				
			||||||
                "APNs: Success sending for user %s to device %s", user_identity, device.token
 | 
					                "APNs: Success sending for user %s to device %s", user_identity, device.token
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1878,6 +1878,18 @@ class TestAPNs(PushNotificationTest):
 | 
				
			|||||||
                logger.output,
 | 
					                logger.output,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_other_exception(self) -> None:
 | 
				
			||||||
 | 
					        self.setup_apns_tokens()
 | 
				
			||||||
 | 
					        with self.mock_apns() as (apns_context, send_notification), self.assertLogs(
 | 
				
			||||||
 | 
					            "zerver.lib.push_notifications", level="INFO"
 | 
				
			||||||
 | 
					        ) as logger:
 | 
				
			||||||
 | 
					            send_notification.side_effect = IOError
 | 
				
			||||||
 | 
					            self.send(devices=self.devices()[0:1])
 | 
				
			||||||
 | 
					            self.assertIn(
 | 
				
			||||||
 | 
					                f"ERROR:zerver.lib.push_notifications:APNs: Error sending for user <id:{self.user_profile.id}> to device {self.devices()[0].token}",
 | 
				
			||||||
 | 
					                logger.output[1],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_internal_server_error(self) -> None:
 | 
					    def test_internal_server_error(self) -> None:
 | 
				
			||||||
        self.setup_apns_tokens()
 | 
					        self.setup_apns_tokens()
 | 
				
			||||||
        with self.mock_apns() as (apns_context, send_notification), self.assertLogs(
 | 
					        with self.mock_apns() as (apns_context, send_notification), self.assertLogs(
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user