Skip to content

Commit aa65722

Browse files
committed
Add retry logic to translation requests
1 parent 673ee46 commit aa65722

1 file changed

Lines changed: 59 additions & 18 deletions

File tree

src/main/kotlin/com/airsaid/localization/translate/AbstractTranslator.kt

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,34 +45,59 @@ abstract class AbstractTranslator : Translator, TranslatorConfigurable {
4545
companion object {
4646
protected val LOG = Logger.getInstance(AbstractTranslator::class.java)
4747
private const val DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded"
48-
private const val DEFAULT_TIMEOUT_MS = 60 * 1000
48+
private const val DEFAULT_TIMEOUT_MS = 30 * 1000
49+
private const val MAX_RETRY_COUNT = 3
50+
private const val BASE_RETRY_DELAY_MS = 500L
4951
}
5052

5153
@Throws(TranslationException::class)
5254
override fun doTranslate(fromLang: Lang, toLang: Lang, text: String): String {
5355
checkSupportedLanguages(fromLang, toLang, text)
5456

5557
val requestUrl = getRequestUrl(fromLang, toLang, text)
56-
val requestBuilder = createRequestBuilder(requestUrl)
57-
configureRequestBuilder(requestBuilder)
58-
59-
return try {
60-
val payload = buildRequestPayload(fromLang, toLang, text)
61-
requestBuilder.connect { request ->
62-
payload.form?.let { request.write(it) }
63-
payload.body?.let { body ->
64-
if (payload.form != null && body.isNotEmpty()) {
65-
request.write("&")
58+
var lastException: Exception? = null
59+
60+
for (attempt in 0..MAX_RETRY_COUNT) {
61+
val requestBuilder = createRequestBuilder(requestUrl)
62+
configureRequestBuilder(requestBuilder)
63+
64+
try {
65+
val payload = buildRequestPayload(fromLang, toLang, text)
66+
return requestBuilder.connect { request ->
67+
payload.form?.let { request.write(it) }
68+
payload.body?.let { body ->
69+
if (payload.form != null && body.isNotEmpty()) {
70+
request.write("&")
71+
}
72+
request.write(body)
6673
}
67-
request.write(body)
74+
val resultText = request.readString()
75+
parsingResult(fromLang, toLang, text, resultText)
76+
}
77+
} catch (e: Exception) {
78+
lastException = e
79+
val shouldRetry = e.isRecoverable() && attempt < MAX_RETRY_COUNT
80+
if (!shouldRetry) {
81+
LOG.error("Translation request failed: ${e.message}", e)
82+
throw TranslationException(fromLang, toLang, text, e)
83+
}
84+
LOG.warn("Recoverable translation failure, retrying (attempt ${attempt + 1} of ${MAX_RETRY_COUNT + 1}): ${e.message}")
85+
val backoffDelayMs = BASE_RETRY_DELAY_MS * (1L shl attempt)
86+
try {
87+
Thread.sleep(backoffDelayMs)
88+
} catch (interruptedException: InterruptedException) {
89+
Thread.currentThread().interrupt()
90+
throw TranslationException(fromLang, toLang, text, interruptedException)
6891
}
69-
val resultText = request.readString()
70-
parsingResult(fromLang, toLang, text, resultText)
7192
}
72-
} catch (e: Exception) {
73-
LOG.warn("Translation request failed: ${e.message}", e)
74-
throw TranslationException(fromLang, toLang, text, e)
7593
}
94+
95+
throw TranslationException(
96+
fromLang,
97+
toLang,
98+
text,
99+
lastException ?: IllegalStateException("Translation failed without capturing an exception")
100+
)
76101
}
77102

78103
protected open fun createRequestBuilder(requestUrl: String): RequestBuilder {
@@ -143,5 +168,21 @@ abstract class AbstractTranslator : Translator, TranslatorConfigurable {
143168
return RequestPayload(requestParams, requestBody)
144169
}
145170
}
146-
147171
private data class RequestPayload(val form: String?, val body: String?)
172+
173+
private fun Throwable.isRecoverable(): Boolean {
174+
return generateSequence(this) { it.cause }
175+
.any { cause ->
176+
when (cause) {
177+
is java.net.SocketTimeoutException,
178+
is java.net.ConnectException,
179+
is java.net.NoRouteToHostException,
180+
is java.net.SocketException,
181+
is java.util.concurrent.TimeoutException -> true
182+
else -> {
183+
val message = cause.message?.lowercase() ?: return@any false
184+
message.contains("timeout") || message.contains("connection refused")
185+
}
186+
}
187+
}
188+
}

0 commit comments

Comments
 (0)