signalapp/Signal-Server · error · InternalServerErrorException
delivery cancelled
Error message
delivery cancelled
What it means
Thrown as HTTP 500 when one of the futures delivering a multi-recipient message is cancelled before completion. The server logs the cancellation and returns InternalServerErrorException with 'delivery cancelled'.
Solutions
- Retry the send; cancellation is usually server-side and transient
- Increase client timeout to reduce the chance of server-side cancellation
- Inspect server logs to find which timeout/cancellation policy triggered it
Defensive patterns
Strategy: retry
Try / catch
try { await send(msg); } catch (e) { if (e.status === 500 && e.message === 'delivery cancelled') await withBackoff(() => send(msg)); else throw e; } Prevention
- Retry the send with exponential backoff
- Keep request sizes moderate so delivery completes before server timeouts
- Monitor server shutdown schedules when sending in bulk
When it happens
Trigger: Future.cancel() invoked on the per-recipient delivery futures, typically by a timeout or shutdown racing the delivery loop in sendMultiRecipientMessage.
Common situations: Server request timeout cancelling outstanding deliveries; executor shutdown during a large fan-out send.
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.
Related errors
- interrupted during delivery
- failure during delivery
- Only primary devices may register attestations
- Invalid action:
- Multi-recipient messages must be addressed to ACI service…
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/5e6905669607f026.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java:655
spamChecker.checkForMultiRecipientSpamHttp(messageType, context).response().ifPresent(response -> {
throw new WebApplicationException(response);
});
try {
if (!resolvedRecipients.isEmpty()) {
messageSender.sendMultiRecipientMessage(multiRecipientMessage,
resolvedRecipients,
timestamp, isStory,
ephemeral,
urgent,
context.getHeaderString(HttpHeaders.USER_AGENT)).get();
}
} catch (final InterruptedException e) {
logger.error("interrupted while delivering multi-recipient messages", e);
throw new InternalServerErrorException("interrupted during delivery");
} catch (final CancellationException e) {
logger.error("cancelled while delivering multi-recipient messages", e);
throw new InternalServerErrorException("delivery cancelled");
} catch (final ExecutionException e) {
logger.error("partial failure while delivering multi-recipient messages", e.getCause());
throw new InternalServerErrorException("failure during delivery");
} catch (final MessageTooLargeException e) {
throw new WebApplicationException(Status.REQUEST_ENTITY_TOO_LARGE);
} catch (final MultiRecipientMismatchedDevicesException e) {
final List<AccountMismatchedDevices> accountMismatchedDevices =
e.getMismatchedDevicesByServiceIdentifier().entrySet().stream()
.filter(entry -> !entry.getValue().missingDeviceIds().isEmpty() || !entry.getValue().extraDeviceIds().isEmpty())
.map(entry -> new AccountMismatchedDevices(entry.getKey(),
new MismatchedDevicesResponse(entry.getValue().missingDeviceIds(), entry.getValue().extraDeviceIds())))
.toList();
if (!accountMismatchedDevices.isEmpty()) {
throw new WebApplicationException(Response
.status(409)
.type(MediaType.APPLICATION_JSON_TYPE)
.entity(accountMismatchedDevices)View on GitHub (pinned to 100ab61c82)