pentaho/pentaho-kettle · warning · IOException
Interrupted while waiting to retry callback server startup
Error message
Interrupted while waiting to retry callback server startup
What it means
startCallbackServerWithRetry retries binding the OAuth callback HTTP server while the port is busy, sleeping SERVER_START_RETRY_DELAY_MS between attempts. If Thread.sleep is interrupted during the retry wait, it restores the interrupt flag and throws IOException('Interrupted while waiting to retry callback server startup') with the InterruptedException as cause. authenticate() propagates this to the caller.
Solutions
- Treat this as a user-initiated cancellation: detect it via cause instanceof InterruptedException and abort the login flow cleanly.
- Free the callback port: kill the stale process listening on the port (or pick another free port) so retries are not needed.
- If interruption is not expected, audit who calls Thread.interrupt() on this thread (dialog cancel handlers, executors shut down mid-login).
Example fix
// before
try {
service.authenticate();
} catch ( IOException e ) {
throw e;
}
// after
try {
service.authenticate();
} catch ( IOException e ) {
if ( e.getCause() instanceof InterruptedException ) {
Thread.currentThread().interrupt();
return; // user cancelled login; do not retry
}
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the callback port is free before starting authentication
try ( ServerSocket probe = new ServerSocket( callbackPort ) ) {
// port available
} catch ( IOException busy ) {
chooseAlternativePort();
} Try / catch
try {
authService.authenticate();
} catch ( IOException e ) {
if ( e.getCause() instanceof InterruptedException ) {
Thread.currentThread().interrupt();
LOGGER.info( "Browser authentication cancelled by user" );
} else {
throw e;
}
} Prevention
- Free the callback port before retry loops begin; kill stale instances of the app.
- Design UI cancel actions to interrupt only via well-defined cancellation tokens.
- Keep retry attempts/delay bounded so interruption is rare.
- Always preserve the interrupt flag after catching InterruptedException.
When it happens
Trigger: Calling authenticate() when the callback port is occupied for longer than the retry budget, and the waiting thread's interrupt() is invoked (e.g. user cancels the dialog, VM shutdown, or a watchdog cancels the login).
Common situations: User closes the browser-auth window while the callback server is stuck retrying; application shutdown hooks interrupting background login; another process holding the callback port (port conflict from a stale previous instance).
Related errors
- Waiting for transformation to be finished interrupted!
- An unexpected error has occurred
- Cannot open browser on OS:
- CmsTokenProvider: Keycloak token request failed — HTTP
- Failed to re-invoke operation after successful login
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/50383e92f1cc1dad.
Report an issue: GitHub.
Appendix: source
Thrown at ui/src/main/java/org/pentaho/di/ui/repo/service/BrowserAuthenticationService.java:202
void startCallbackServerWithRetry() throws IOException {
IOException lastException = null;
for ( int attempt = 1; attempt <= SERVER_START_MAX_RETRIES; attempt++ ) {
try {
startCallbackServer();
return;
} catch ( IOException e ) {
lastException = e;
if ( !isAddressAlreadyInUse( e ) || attempt == SERVER_START_MAX_RETRIES ) {
throw e;
}
log.logBasic( "Callback port still busy; retrying callback server start (attempt "
+ attempt + "/" + SERVER_START_MAX_RETRIES + ")" );
try {
Thread.sleep( SERVER_START_RETRY_DELAY_MS );
} catch ( InterruptedException interrupted ) {
Thread.currentThread().interrupt();
throw new IOException( "Interrupted while waiting to retry callback server startup", interrupted );
}
}
}
if ( lastException != null ) {
throw lastException;
}
}
boolean isAddressAlreadyInUse( Throwable throwable ) {
Throwable current = throwable;
while ( current != null ) {
if ( current instanceof BindException ) {
return true;
}
String message = current.getMessage();
if ( message != null && message.toLowerCase( Locale.ROOT ).contains( "address already in use" ) ) {
return true;
}View on GitHub (pinned to f3058517a1)