{"record":{"id":"8936b9c054a1eb73","repo":"signalapp/Signal-Server","slug":"registration-service-failure","errorCode":null,"errorMessage":"Registration service failure","messagePattern":"Registration service failure","errorType":"http","errorClass":"IOException","httpStatus":503,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/auth/PhoneVerificationTokenManager.java","lineNumber":110,"sourceCode":"      throws UnverifiedRegistrationSessionException, InvalidRegistrationSessionException, IOException {\n    try {\n      final RegistrationServiceSession session = registrationServiceClient\n          .getSession(sessionId, REGISTRATION_RPC_TIMEOUT)\n          .orElseThrow(UnverifiedRegistrationSessionException::new);\n\n      if (!MessageDigest.isEqual(number.getBytes(), session.number().getBytes())) {\n        throw new InvalidRegistrationSessionException(\"number does not match session\");\n      }\n      if (!session.verified()) {\n        throw new UnverifiedRegistrationSessionException();\n      }\n    } catch (final StatusRuntimeException e) {\n      if (e.getStatus().getCode() == Status.Code.INVALID_ARGUMENT) {\n        throw new InvalidRegistrationSessionException(e.getMessage());\n      }\n\n      logger.error(\"Registration service failure\", e);\n      throw new IOException(\"Registration service failure\", e);\n    }\n  }\n\n  private void verifyByRecoveryPassword(\n      final String number,\n      @Nullable final String userAgent,\n      @Nullable final String acceptLanguage,\n      @Nullable final String mostRecentProxy,\n      final byte[] recoveryPassword)\n      throws InterruptedException, IOException, RecoveryPasswordVerificationFailedException {\n\n    if (!registrationRecoveryChecker.checkRegistrationRecoveryAttempt(number, userAgent, acceptLanguage, mostRecentProxy)) {\n      throw new RecoveryPasswordVerificationFailedException();\n    }\n\n    try {\n      final UUID phoneNumberIdentifier = phoneNumberIdentifiers.getPhoneNumberIdentifier(number)\n          .get(VERIFICATION_TIMEOUT_SECONDS, TimeUnit.SECONDS);","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/auth/PhoneVerificationTokenManager.java#L92-L128","documentation":"PhoneVerificationTokenManager wraps calls to the external registration service (gRPC). When the remote verify call fails with a gRPC status other than INVALID_ARGUMENT (e.g. UNAVAILABLE, DEADLINE_EXCEEDED, INTERNAL), the error is logged and rethrown as an IOException so callers can treat it as a transient backend failure rather than a client mistake.","triggerScenarios":"Calling PhoneVerificationTokenManager.verify() -> verifyBySessionId() with a valid session id while the registration service is down, unreachable, times out, or returns an unexpected gRPC error code.","commonSituations":"Registration service outage or deployment; network partition between Signal service and registration service; gRPC deadline exceeded under load; misconfigured registration service endpoint in production configuration.","solutions":["Check registration service health/availability and its logs for the correlation error logged alongside this message.","Verify the registration service URI/credentials in the service configuration and network connectivity/DNS from this host.","Retry the request after the transient condition clears; wrap the IOException in retry-with-backoff logic at the resource layer.","Confirm the gRPC call's deadline is generous enough for production latency."],"exampleFix":"// before\ntry {\n  phoneVerificationTokenManager.verify(...);\n} catch (IOException e) {\n  return Response.status(503).build();\n}\n// after\ntry {\n  phoneVerificationTokenManager.verify(...);\n} catch (IOException e) {\n  logger.warn(\"registration service transient failure, retrying\", e);\n  return retryWithBackoff(() -> phoneVerificationTokenManager.verify(...));\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  phoneVerificationTokenManager.verify(...);\n} catch (IOException e) {\n  if (e.getCause() instanceof StatusRuntimeException sre && isRetryable(sre.getStatus().getCode())) {\n    scheduleRetryWithBackoff();\n  } else {\n    return Response.status(503).build();\n  }\n}","preventionTips":["Monitor registration service health and alert on elevated gRPC error rates","Use retry with exponential backoff for IOException from verification paths","Keep gRPC deadlines generous but bounded","Log the cause StatusRuntimeException for diagnosability"],"tags":["network","grpc","backend-service","transient-failure"],"backgroundTag":"upstream-api-error","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}