Skip to content

Commit 313d721

Browse files
committed
O(1) happy path for access token hits
1 parent c1a0ce1 commit 313d721

File tree

2 files changed

+54
-13
lines changed

2 files changed

+54
-13
lines changed

msal/application.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1357,13 +1357,14 @@ def _acquire_token_silent_from_cache_and_possibly_refresh_it(
13571357
key_id = kwargs.get("data", {}).get("key_id")
13581358
if key_id: # Some token types (SSH-certs, POP) are bound to a key
13591359
query["key_id"] = key_id
1360-
matches = self.token_cache.find(
1361-
self.token_cache.CredentialType.ACCESS_TOKEN,
1362-
target=scopes,
1363-
query=query)
13641360
now = time.time()
13651361
refresh_reason = msal.telemetry.AT_ABSENT
1366-
for entry in matches:
1362+
for entry in self.token_cache._find( # It returns a generator
1363+
self.token_cache.CredentialType.ACCESS_TOKEN,
1364+
target=scopes,
1365+
query=query,
1366+
): # Note that _find() holds a lock during this for loop;
1367+
# that is fine because this loop is fast
13671368
expires_in = int(entry["expires_on"]) - now
13681369
if expires_in < 5*60: # Then consider it expired
13691370
refresh_reason = msal.telemetry.AT_EXPIRED

msal/token_cache.py

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,60 @@ def __init__(self):
8888
"appmetadata-{}-{}".format(environment or "", client_id or ""),
8989
}
9090

91-
def find(self, credential_type, target=None, query=None):
92-
target = target or []
91+
def _get_access_token(
92+
self,
93+
home_account_id, environment, client_id, realm, target, # Together they form a compound key
94+
default=None,
95+
): # O(1)
96+
return self._get(
97+
self.CredentialType.ACCESS_TOKEN,
98+
self.key_makers[TokenCache.CredentialType.ACCESS_TOKEN](
99+
home_account_id=home_account_id,
100+
environment=environment,
101+
client_id=client_id,
102+
realm=realm,
103+
target=" ".join(target),
104+
),
105+
default=default)
106+
107+
def _get(self, credential_type, key, default=None): # O(1)
108+
with self._lock:
109+
return self._cache.get(credential_type, {}).get(key, default)
110+
111+
def _find(self, credential_type, target=None, query=None): # O(n) generator
112+
"""Returns a generator of matching entries.
113+
114+
It is O(1) for AT hits, and O(n) for other types.
115+
Note that it holds a lock during the entire search.
116+
"""
117+
target = sorted(target or []) # Match the order sorted by add()
93118
assert isinstance(target, list), "Invalid parameter type"
119+
120+
preferred_result = None
121+
if (credential_type == self.CredentialType.ACCESS_TOKEN
122+
and "home_account_id" in query and "environment" in query
123+
and "client_id" in query and "realm" in query and target
124+
): # Special case for O(1) AT lookup
125+
preferred_result = self._get_access_token(
126+
query["home_account_id"], query["environment"],
127+
query["client_id"], query["realm"], target)
128+
if preferred_result:
129+
yield preferred_result
130+
94131
target_set = set(target)
95132
with self._lock:
96133
# Since the target inside token cache key is (per schema) unsorted,
97134
# there is no point to attempt an O(1) key-value search here.
98135
# So we always do an O(n) in-memory search.
99-
return [entry
100-
for entry in self._cache.get(credential_type, {}).values()
101-
if is_subdict_of(query or {}, entry)
102-
and (target_set <= set(entry.get("target", "").split())
103-
if target else True)
104-
]
136+
for entry in self._cache.get(credential_type, {}).values():
137+
if is_subdict_of(query or {}, entry) and (
138+
target_set <= set(entry.get("target", "").split())
139+
if target else True):
140+
if entry != preferred_result: # Avoid yielding the same entry twice
141+
yield entry
142+
143+
def find(self, credential_type, target=None, query=None): # Obsolete. Use _find() instead.
144+
return list(self._find(credential_type, target=target, query=query))
105145

106146
def add(self, event, now=None):
107147
"""Handle a token obtaining event, and add tokens into cache."""

0 commit comments

Comments
 (0)