_refresh_worker.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. # Copyright 2023 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import copy
  15. import logging
  16. import threading
  17. import google.auth.exceptions as e
  18. _LOGGER = logging.getLogger(__name__)
  19. class RefreshThreadManager:
  20. """
  21. Organizes exactly one background job that refresh a token.
  22. """
  23. def __init__(self):
  24. """Initializes the manager."""
  25. self._worker = None
  26. self._lock = threading.Lock() # protects access to worker threads.
  27. def start_refresh(self, cred, request):
  28. """Starts a refresh thread for the given credentials.
  29. The credentials are refreshed using the request parameter.
  30. request and cred MUST not be None
  31. Returns True if a background refresh was kicked off. False otherwise.
  32. Args:
  33. cred: A credentials object.
  34. request: A request object.
  35. Returns:
  36. bool
  37. """
  38. if cred is None or request is None:
  39. raise e.InvalidValue(
  40. "Unable to start refresh. cred and request must be valid and instantiated objects."
  41. )
  42. with self._lock:
  43. if self._worker is not None and self._worker._error_info is not None:
  44. return False
  45. if self._worker is None or not self._worker.is_alive(): # pragma: NO COVER
  46. self._worker = RefreshThread(cred=cred, request=copy.deepcopy(request))
  47. self._worker.start()
  48. return True
  49. def clear_error(self):
  50. """
  51. Removes any errors that were stored from previous background refreshes.
  52. """
  53. with self._lock:
  54. if self._worker:
  55. self._worker._error_info = None
  56. def __getstate__(self):
  57. """Pickle helper that serializes the _lock attribute."""
  58. state = self.__dict__.copy()
  59. state["_lock"] = None
  60. return state
  61. def __setstate__(self, state):
  62. """Pickle helper that deserializes the _lock attribute."""
  63. state["_lock"] = threading.Lock()
  64. self.__dict__.update(state)
  65. class RefreshThread(threading.Thread):
  66. """
  67. Thread that refreshes credentials.
  68. """
  69. def __init__(self, cred, request, **kwargs):
  70. """Initializes the thread.
  71. Args:
  72. cred: A Credential object to refresh.
  73. request: A Request object used to perform a credential refresh.
  74. **kwargs: Additional keyword arguments.
  75. """
  76. super().__init__(**kwargs)
  77. self._cred = cred
  78. self._request = request
  79. self._error_info = None
  80. def run(self):
  81. """
  82. Perform the credential refresh.
  83. """
  84. try:
  85. self._cred.refresh(self._request)
  86. except Exception as err: # pragma: NO COVER
  87. _LOGGER.error(f"Background refresh failed due to: {err}")
  88. self._error_info = err