{"record":{"id":"50383e92f1cc1dad","repo":"pentaho/pentaho-kettle","slug":"interrupted-while-waiting-to-retry-callback-server-startup","errorCode":null,"errorMessage":"Interrupted while waiting to retry callback server startup","messagePattern":"Interrupted while waiting to retry callback server startup","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"warning","filePath":"ui/src/main/java/org/pentaho/di/ui/repo/service/BrowserAuthenticationService.java","lineNumber":202,"sourceCode":"\n  void startCallbackServerWithRetry() throws IOException {\n    IOException lastException = null;\n    for ( int attempt = 1; attempt <= SERVER_START_MAX_RETRIES; attempt++ ) {\n      try {\n        startCallbackServer();\n        return;\n      } catch ( IOException e ) {\n        lastException = e;\n        if ( !isAddressAlreadyInUse( e ) || attempt == SERVER_START_MAX_RETRIES ) {\n          throw e;\n        }\n        log.logBasic( \"Callback port still busy; retrying callback server start (attempt \"\n          + attempt + \"/\" + SERVER_START_MAX_RETRIES + \")\" );\n        try {\n          Thread.sleep( SERVER_START_RETRY_DELAY_MS );\n        } catch ( InterruptedException interrupted ) {\n          Thread.currentThread().interrupt();\n          throw new IOException( \"Interrupted while waiting to retry callback server startup\", interrupted );\n        }\n      }\n    }\n    if ( lastException != null ) {\n      throw lastException;\n    }\n  }\n\n  boolean isAddressAlreadyInUse( Throwable throwable ) {\n    Throwable current = throwable;\n    while ( current != null ) {\n      if ( current instanceof BindException ) {\n        return true;\n      }\n      String message = current.getMessage();\n      if ( message != null && message.toLowerCase( Locale.ROOT ).contains( \"address already in use\" ) ) {\n        return true;\n      }","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/ui/src/main/java/org/pentaho/di/ui/repo/service/BrowserAuthenticationService.java#L184-L220","documentation":"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.","triggerScenarios":"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).","commonSituations":"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).","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)."],"exampleFix":"// before\ntry {\n  service.authenticate();\n} catch ( IOException e ) {\n  throw e;\n}\n// after\ntry {\n  service.authenticate();\n} catch ( IOException e ) {\n  if ( e.getCause() instanceof InterruptedException ) {\n    Thread.currentThread().interrupt();\n    return; // user cancelled login; do not retry\n  }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"// Verify the callback port is free before starting authentication\ntry ( ServerSocket probe = new ServerSocket( callbackPort ) ) {\n  // port available\n} catch ( IOException busy ) {\n  chooseAlternativePort();\n}","typeGuard":null,"tryCatchPattern":"try {\n  authService.authenticate();\n} catch ( IOException e ) {\n  if ( e.getCause() instanceof InterruptedException ) {\n    Thread.currentThread().interrupt();\n    LOGGER.info( \"Browser authentication cancelled by user\" );\n  } else {\n    throw e;\n  }\n}","preventionTips":["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."],"tags":["interrupt","retry","port-conflict","oauth","threading"],"backgroundTag":"thread-interrupted","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}