Implement automatic license token invalidation on failed heartbeat
Feature Proposal: Automate License Token Invalidation on Heartbeat Failure
Summary
This enhancement introduces a configurable mechanism to automatically and safely invalidate (delete) locally stored license tokens when a heartbeating operation fails with specific, critical error codes. This ensures that stale or permanently invalid tokens are removed promptly, preventing unnecessary future license heartbeat attempts and improving system robustness.
1. Motivation
Currently, when a license heartbeat request fails, the local token is typically retained until its natural expiry. Certain error codes returned by the license server (e.g., leaseIdNotFound, licenseCheckoutExpired) indicate a permanent or unrecoverable invalid state for the local token.
The goal is to:
- Improve System Hygiene: Immediately remove tokens that are known to be permanently invalid.
- Enhance Reliability: Prevent clients from repeatedly attempting heartbeats with a token that the server has definitively rejected.
- Provide Flexibility: Allow consumers of the SDK to customize or disable this automatic invalidation behavior.
2. Proposed Changes
2.1. Configuration Enhancement
A new configuration key will be introduced to control this automation: token_automatic_invalidation_disabled.
-
Key Name:
token_automatic_invalidation_disabled -
Default Value:
false -
Description: When
true, the automatic invalidation of tokens on specific heartbeat failures is disabled. Note: The default isfalse(automation enabled) to activate this behavior for new and existing applications who do not explicitly set the key.
2.2. Foundational Implementation Architecture
The core logic will be introduced within the existing DefaultLicenseCheckoutClient and refactored using a new strategy pattern.
Refactoring DefaultLicenseCheckoutClient.mapHeartbeatResultToLicenseTokens
The current implementation of this method will be modified to process the heartbeat argument-result pairs by splitting them into two distinct groups: Successful and Failed.
Processing the Successful List Pairs in this list will be processed exactly as the current logic dictates (i.e., passed to the TokenManager for saving or updating).
Processing the Failed List Each failed pair will be passed to the new TokenInvalidationHandler to determine if the locally stored token should be deleted.
2.3. Strategy Pattern: TokenInvalidationHandler
A new interface, TokenInvalidationHandler, will be created to decouple the decision logic from the DefaultLicenseCheckoutClient. The TokenInvalidationHandler shall be placed in the api artifact into package tenduke/scale/api.
Interface and Integration The handler will expose a method that accepts a heartbeat argument/result pair and returns a boolean decision (true for deletion, false to keep). DefaultLicenseCheckoutClient will be updated with an instance member for this handler.
Fluent Configuration A new method, withTokenInvalidationHandler(...), will be added to the client's fluent configuration builder (similar to withTokenManager) to allow users of the SDK to inject a custom handler.
Action on Decision If the handler returns true for a failed token, the client will call TokenManager.removeSafelyByLeaseId(leaseId), where the leaseId is extracted from the failed heartbeat call's arguments.
3. Handler Implementation
3.1. Built-in (Default) Handler
A default implementation of TokenInvalidationHandler will be provided, which is used unless an external handler is configured. This handler will use a predefined list of critical server error codes to trigger invalidation. This class shall be called DefaultTokenInvalidationHandler. and reside in the same package as the class DefaultLicenseCheckoutClient.
For more context on these error codes, please refer to the license consumption error responses documentation: https://docs.scale.10duke.com/api/access-the-apis/api-error-handling/#license-consumption-error-responses
Error Codes Triggering Automatic Token Removal (Default Behavior)
The following error codes indicate a permanent client-side issue with the token and will trigger its removal:
-
leaseIdNotFound: The server doesn't recognize the token's lease ID. Rationale: Token is definitively invalid/stale on the server side. -
licenseCheckoutExpired: The license lease time has expired. Rationale: Token is no longer valid. -
licenseKeyVsLeaseIdMismatch: The provided key and lease ID don't match. Rationale: Token is permanently invalid or corrupt. -
licenseConsumptionQtyEnforcementTypeMismatch: This is specific to the licensing scheme and indicates a fundamental mismatch/invalidation. -
clientHwIdMismatch: The client's hardware ID has changed/does not match. Rationale: Token is bound to the wrong machine. -
clientVersionNotAllowed: The client version is blocked/deprecated. Rationale: Unlikely to resolve with a retry; new checkout is needed. -
clientVersionMissing: The client version was not provided. Rationale: Unlikely to resolve with a retry; new checkout is needed.
3.2. Custom Handler
The SDK's architecture will allow advanced users to implement and inject their own TokenInvalidationHandler to define a customized set of error codes or decision-making logic.
4. Technical Notes & Next Steps
- Error Handling for Non-Identified Codes: Heartbeat failures that do not match the removal list must be treated as before unless an external handler steps in and provides a decision (default is to save a failed result token when error code does not match one in the list). The application should be responsible for implementing a backoff and retry strategy for these scenarios (no change in the SDK to implement retry at this stage).
-
Safety: The use of
TokenManager.removeSafelyByLeaseIdensures that the operation is non-critical and handles potential lease id not found cases, concurrency issues or race conditions. - Testing: Thorough unit and integration tests must be written to cover both the default and disabled configuration scenarios, and to confirm correct behavior for both codes that do and do not trigger removal. Instruction for first commit: do not change existing test apart from configuration and method signature changes. This is to get understanding of how "breaking" this feature will be.