Skip to content

Bugfix/devexp 765 syncronize token managers#156

Open
marcos-sinch wants to merge 6 commits into
v2.1-nextfrom
bugfix/DEVEXP-765-syncronize-token-managers
Open

Bugfix/devexp 765 syncronize token managers#156
marcos-sinch wants to merge 6 commits into
v2.1-nextfrom
bugfix/DEVEXP-765-syncronize-token-managers

Conversation

@marcos-sinch

@marcos-sinch marcos-sinch commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Synchronize token managers and refactor HTTP transport

Fixes a race condition in OAuth token creation and renewal under concurrent requests.

Token manager (token_manager.py)

  • Adds a threading.Lock to TokenManagerBase.
  • get_auth_token uses double-checked locking so that the initial token fetch happens exactly once even when many threads call it simultaneously.
  • Replaces the EXPIRED state + handle_invalid_token side-channel with a new refresh_auth_token(used_token) method: whichever thread holds the lock compares the used (stale) token against the currently cached one and fetches a new token only if they still match, deduplicating concurrent renewal attempts.
  • Removes the now-unnecessary TokenState.EXPIRED value.

HTTP transport (http_transport.py, requests_http_transport.py)

  • Moves prepare_request and authenticate out of send() and into request(), so send() now receives an already-prepared HttpRequest instead of a raw HTTPEndpoint. This makes send() a pure I/O primitive and simplifies subclassing.
  • Extracts _get_bearer_token_from_request and _set_bearer_token helpers to keep the token-stamping logic in one place.

Tests

  • Replaces the abstract MockHTTPTransport test harness with tests that drive the real HTTPTransportRequests adapter, giving better coverage of the actual I/O path.
  • Adds concurrency tests for both get_auth_token and refresh_auth_token that assert a single fetch under 20 simultaneous threads.

@JPPortier

Copy link
Copy Markdown

Hi @marcos-sinch : target branch should be 2.1 next but not main

@marcos-sinch

Copy link
Copy Markdown
Contributor Author

Hi @marcos-sinch : target branch should be 2.1 next but not main

@marcos-sinch marcos-sinch reopened this Jun 12, 2026
@marcos-sinch marcos-sinch changed the base branch from main to v2.1-next June 12, 2026 09:38

@JPPortier JPPortier left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update also CHANGELOG file

Comment on lines +40 to +41
f"Sync HTTP response {response.status_code} with headers: {response.headers}"
f" and body: {response_body} from URL: {request_data.url}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to this PR but curious about python and "overload" here.
A logger is used

  • which will decide if message is having to be send regarding severity of message and config level for logger
  • finally message won't be send by logger because of severity is configured to "warn" (debug is "lower")
  • but here, sent of not by logger, the message is formatted before being sent to logger
    Right ?

This mean with "heavy" calls to send: "heavy" load due to formatting which won't be consumed.

Can we do "more efficient" ?

  • format message from logger if having to be sent by accepting list of parameter to be used when formatting message (logger.debug("Processing %s with data %r", value1, value2))
  • guard before call: if logger.isEnabledFor(logging.DEBUG):
  • provide a function to be called by logger if it decide to send the message (like Supplier in Java? )
  • ???

HTTP call, deserialize the response, and return an ``HTTPResponse``.
They should **not** handle token refresh — that is done by
``request``.
def send(self, request_data: HttpRequest) -> HTTPResponse:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a breaking change for any existing integration.
We should find another way to address the migration

response=None
)

def _fetch_new_token(self) -> OAuthToken:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function name may be not ideal ?
It do not only fetch a new token, it also store it in instance itself (then duplicate code from set_auth_token)

Thread-safe synchronous OAuth token manager.
"""

def get_auth_token(self) -> OAuthToken:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment here about naming: it do not get the token in term of "getter": it also refresh and store it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants