push_notifications: Lazily import APNS libraries.

The APNS client libraries (especially the hyper.http20 one) were
determined via profiling to take significant time during the import
process, so we move them to be lazily imported in order to optimize
the overall Zulip import process.  This save up to about 100ms in
import time.

These libraries are only used in certain Django processes inside
zulipchat.com, and so are unnecessary both in development as well as
for self-hosted Zulip servers.
This commit is contained in:
Tim Abbott
2018-08-08 09:16:57 -07:00
parent ec9f6702d8
commit 2eebacf2dc
2 changed files with 15 additions and 7 deletions

View File

@@ -12,14 +12,11 @@ import random
from typing import Any, Dict, List, Optional, SupportsInt, Tuple, Type, Union from typing import Any, Dict, List, Optional, SupportsInt, Tuple, Type, Union
from apns2.client import APNsClient
from apns2.payload import Payload as APNsPayload
from django.conf import settings from django.conf import settings
from django.db import IntegrityError, transaction from django.db import IntegrityError, transaction
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from gcm import GCM from gcm import GCM
from hyper.http20.exceptions import HTTP20Error
import requests import requests
import urllib import urllib
import ujson import ujson
@@ -55,10 +52,13 @@ def hex_to_b64(data: str) -> bytes:
# Sending to APNs, for iOS # Sending to APNs, for iOS
# #
_apns_client = None # type: Optional[APNsClient] _apns_client = None # type: Optional[Any]
_apns_client_initialized = False _apns_client_initialized = False
def get_apns_client() -> APNsClient: def get_apns_client() -> Any:
# We lazily do this import as part of optimizing Zulip's base
# import time.
from apns2.client import APNsClient
global _apns_client, _apns_client_initialized global _apns_client, _apns_client_initialized
if not _apns_client_initialized: if not _apns_client_initialized:
# NB if called concurrently, this will make excess connections. # NB if called concurrently, this will make excess connections.
@@ -104,7 +104,15 @@ APNS_MAX_RETRIES = 3
@statsd_increment("apple_push_notification") @statsd_increment("apple_push_notification")
def send_apple_push_notification(user_id: int, devices: List[DeviceToken], def send_apple_push_notification(user_id: int, devices: List[DeviceToken],
payload_data: Dict[str, Any], remote: bool=False) -> None: payload_data: Dict[str, Any], remote: bool=False) -> None:
client = get_apns_client() # We lazily do the APNS imports as part of optimizing Zulip's base
# import time; since these are only needed in the push
# notification queue worker, it's best to only import them in the
# code that needs them.
from apns2.payload import Payload as APNsPayload
from apns2.client import APNsClient
from hyper.http20.exceptions import HTTP20Error
client = get_apns_client() # type: APNsClient
if client is None: if client is None:
logging.warning("APNs: Dropping a notification because nothing configured. " logging.warning("APNs: Dropping a notification because nothing configured. "
"Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE).") "Set PUSH_NOTIFICATION_BOUNCER_URL (or APNS_CERT_FILE).")

View File

@@ -659,7 +659,7 @@ class TestAPNs(PushNotificationTest):
import zerver.lib.push_notifications import zerver.lib.push_notifications
zerver.lib.push_notifications._apns_client_initialized = False zerver.lib.push_notifications._apns_client_initialized = False
with self.settings(APNS_CERT_FILE='/foo.pem'), \ with self.settings(APNS_CERT_FILE='/foo.pem'), \
mock.patch('zerver.lib.push_notifications.APNsClient') as mock_client: mock.patch('apns2.client.APNsClient') as mock_client:
client = get_apns_client() client = get_apns_client()
self.assertEqual(mock_client.return_value, client) self.assertEqual(mock_client.return_value, client)