signalapp/Signal-Server · error · IOException
registration service unavailable
Error message
registration service unavailable
What it means
verifyByRecoveryPassword verifies a phone-number recovery password via an async call to the registration service. If that future fails with ExecutionException or TimeoutException, the failure is translated into an IOException with message 'registration service unavailable', signaling a transient backend problem rather than a wrong recovery password.
Solutions
- Check registration service availability and latency; inspect the underlying cause via getCause().
- Increase the future timeout if the service is consistently slow but healthy.
- Retry the verification request with backoff; the condition is typically transient.
- Verify network paths and load balancing between the services.
Example fix
// before
try {
phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
}
// after
try {
phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
if (isTransient(e)) {
return retryWithBackoff(() -> phoneVerificationTokenManager.verify(...));
}
throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
} Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
null
Try / catch
try {
phoneVerificationTokenManager.verify(...);
} catch (IOException e) {
Throwable cause = e.getCause();
if (cause instanceof TimeoutException || cause instanceof ExecutionException) {
retryWithBackoff(() -> verify());
} else {
throw e;
}
} Prevention
- Size the async timeout above p99 registration-service latency
- Distinguish wrong-password failures (RecoveryPasswordVerificationFailedException) from availability failures before retrying
- Add circuit breakers around registration-service calls
- Alert on rising ExecutionException/TimeoutException rates
When it happens
Trigger: Calling verify() with a recovery password while the async registration-service lookup throws (wrapped in ExecutionException) or does not complete within the configured timeout (TimeoutException).
Common situations: Registration service slow or overloaded causing future timeouts; network interruption; registration service restarted mid-request; timeout configured too tightly.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- Registration service failure
- failed to make http request to Cloudflare Turn
- 503 Service Unavailable
- Response body was below minimum
- Got a non-200 reply from source URI:
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/921c08a586e23164.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/auth/PhoneVerificationTokenManager.java:136
@Nullable final String mostRecentProxy,
final byte[] recoveryPassword)
throws InterruptedException, IOException, RecoveryPasswordVerificationFailedException {
if (!registrationRecoveryChecker.checkRegistrationRecoveryAttempt(number, userAgent, acceptLanguage, mostRecentProxy)) {
throw new RecoveryPasswordVerificationFailedException();
}
try {
final UUID phoneNumberIdentifier = phoneNumberIdentifiers.getPhoneNumberIdentifier(number)
.get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);
final boolean verified = phoneNumberRecoveryPasswordsManager.verify(phoneNumberIdentifier, recoveryPassword);
if (!verified) {
throw new RecoveryPasswordVerificationFailedException();
}
} catch (final ExecutionException | TimeoutException e) {
throw new IOException("registration service unavailable", e);
}
}
}
View on GitHub (pinned to 100ab61c82)