Skip to content

fix: share token cache across Laravel Octane workers - #38

Open
Falgunimoridhara wants to merge 1 commit into
openapi:mainfrom
Falgunimoridhara:bug/octane-token-cache-sync
Open

Falgunimoridhara wants to merge 1 commit into
openapi:mainfrom
Falgunimoridhara:bug/octane-token-cache-sync

Conversation

@Falgunimoridhara

Copy link
Copy Markdown

Fix: share token cache across Laravel Octane workers (prevents HTTP 429)

Closes #10

Problem

Every call to OauthClient::createToken() sends a new request to the OAuth server. Under Laravel Octane
(Swoole/RoadRunner) each worker has its own memory, so all workers request tokens independently, and the OAuth
server answers with HTTP 429.

Solution

Add an opt-in TokenManager that keeps the token in a Laravel cache store shared by all workers, with atomic
locks so only one worker requests a token at a time.

getToken($scopes, $ttl = 3600):

  1. Fresh token in cache: returned directly. No lock, no OAuth request.
  2. Token due for refresh (still valid): one worker takes the lock and refreshes it. The others keep using the
    current token (stale-while-revalidate) and never block.
  3. No valid token: one worker requests it under the lock. The others wait up to lock_wait seconds, then read it
    from the cache. A double-check inside the lock avoids duplicate requests.
  4. Refresh fails (e.g. 429) while the current token is still valid: the current token keeps being served.

The real expiry comes from the expire field of the /token response (unix timestamp), capped at the requested
$ttl. If expire is missing or already in the past, $ttl is used.

Changes

New

  • src/TokenManager.php: cache-backed token provider (takes an Illuminate\Contracts\Cache\Repository; throws if
    the store has no lock support).
  • src/Laravel/OpenapiServiceProvider.php: auto-discovered provider binding OauthClient and TokenManager.
  • config/openapi.php: credentials plus a token block (store, prefix, refresh_skew, lock_seconds,
    lock_wait). store = null uses the application's default cache store.
  • tests/TokenManagerTest.php, tests/Fakes/CountingOauthClient.php, tests/Fakes/StaleFirstReadRepository.php.

Modified

  • src/OauthClient.php: added getUsername() and getBaseUrl() (used to build the cache key).
  • composer.json: illuminate/cache (^9 || ^10 || ^11 || ^12) in require-dev and suggest;
    extra.laravel.providers for auto-discovery.
  • README.md, CLAUDE.md: documentation.

Client, CurlTransport and ArrayCache are unchanged.

Usage

$token  = app(\Openapi\TokenManager::class)->getToken(['GET:test.imprese.openapi.it/advance']);
$client = new \Openapi\Client($token);
OPENAPI_OAUTH_USERNAME=...
OPENAPI_OAUTH_APIKEY=...
OPENAPI_CACHE_STORE=redis   # shared store with atomic locks; empty = application's default store

How Has This Been Tested?

./vendor/bin/phpunit tests/ : 44 tests (6 skipped, as before).

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.

Laravel Octane: Token cache not shared across workers

1 participant