{"id":13565813,"url":"https://github.com/michaelbull/kotlin-retry","last_synced_at":"2025-04-04T23:09:41.914Z","repository":{"id":40583398,"uuid":"204199690","full_name":"michaelbull/kotlin-retry","owner":"michaelbull","description":"A multiplatform higher-order function for retrying operations that may fail.","archived":false,"fork":false,"pushed_at":"2024-04-20T16:18:24.000Z","size":266,"stargazers_count":353,"open_issues_count":0,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T22:14:05.262Z","etag":null,"topics":["backoff","functional-programming","high-order-function","io","jitter","kotlin","kotlin-multiplatform","retry"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michaelbull.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-08-24T18:56:32.000Z","updated_at":"2025-02-28T09:43:43.000Z","dependencies_parsed_at":"2024-03-17T01:27:48.330Z","dependency_job_id":"9c04d785-0d9e-45d5-a2bc-8ff42230f883","html_url":"https://github.com/michaelbull/kotlin-retry","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaelbull%2Fkotlin-retry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaelbull%2Fkotlin-retry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaelbull%2Fkotlin-retry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaelbull%2Fkotlin-retry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michaelbull","download_url":"https://codeload.github.com/michaelbull/kotlin-retry/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247261612,"owners_count":20910108,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backoff","functional-programming","high-order-function","io","jitter","kotlin","kotlin-multiplatform","retry"],"created_at":"2024-08-01T13:01:55.956Z","updated_at":"2025-04-04T23:09:41.896Z","avatar_url":"https://github.com/michaelbull.png","language":"Kotlin","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"readme":"# kotlin-retry\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.michael-bull.kotlin-retry/kotlin-retry.svg)](https://search.maven.org/search?q=g:com.michael-bull.kotlin-retry)\n[![CI](https://github.com/michaelbull/kotlin-retry/actions/workflows/ci.yaml/badge.svg)](https://github.com/michaelbull/kotlin-retry/actions/workflows/ci.yaml)\n[![License](https://img.shields.io/github/license/michaelbull/kotlin-retry.svg)](https://github.com/michaelbull/kotlin-retry/blob/master/LICENSE)\n\n![badge][badge-android]\n![badge][badge-jvm]\n![badge][badge-js]\n![badge][badge-nodejs]\n![badge][badge-linux]\n![badge][badge-windows]\n![badge][badge-wasm]\n![badge][badge-ios]\n![badge][badge-mac]\n![badge][badge-tvos]\n![badge][badge-watchos]\n![badge][badge-js-ir]\n![badge][badge-android-native]\n![badge][badge-apple-silicon]\n\nA multiplatform higher-order function for retrying operations that may fail.\n\n```kotlin\nval everySecondTenTimes = constantDelay(delayMillis = 1000L) + stopAtAttempts(10)\n\nretry(everySecondTenTimes) {\n    /* your code */\n}\n```\n\n## Installation\n\n```groovy\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation(\"com.michael-bull.kotlin-retry:kotlin-retry:2.0.1\")\n}\n```\n\n## Introduction\n\nIO operations often experience temporary failures that warrant re-execution,\ne.g. a database transaction that may fail due to a deadlock.\u003csup\u003e[1][innodb-deadlocks][2][postgres-deadlocks]\u003c/sup\u003e\n\n\u003e _“even if your application logic is correct, you must still handle the case\n\u003e  where a transaction must be retried”_\n\u003e\n\u003e — _[Deadlocks in InnoDB][innodb-deadlocks]_\n\nThe [`retry`][retry] function simplifies this process by wrapping the\napplication logic and applying a specified [`RetryPolicy`][retry-policy].\n\nIn the example below, either of the calls to `customers.nameFromId` may fail,\nabandoning the remaining logic within the `printExchangeBetween` function. As\nsuch, we may want to retry this operation until 5 invocations in total have been\nexecuted:\n\n```kotlin\nimport com.github.michaelbull.retry.policy.stopAtAttempts\nimport com.github.michaelbull.retry.retry\nimport kotlinx.coroutines.runBlocking\n\nsuspend fun printExchangeBetween(a: Long, b: Long) {\n    val customer1 = customers.nameFromId(a)\n    val customer2 = customers.nameFromId(b)\n    println(\"$customer1 exchanged with $customer2\")\n}\n\nval fiveTimes = stopAtAttempts\u003cThrowable\u003e(5)\n\nfun main() = runBlocking {\n    retry(fiveTimes) {\n        printExchangeBetween(1L, 2L)\n    }\n}\n```\n\nWe can also provide a [`RetryPolicy`][retry-policy] that only retries failures\nof a specific type. The example below will retry the operation only if the\nreason for failure was a `SQLDataException`, pausing for 20 milliseconds before\nretrying and stopping after 5 total invocations.\n\n```kotlin\nimport com.github.michaelbull.retry.ContinueRetrying\nimport com.github.michaelbull.retry.StopRetrying\nimport com.github.michaelbull.retry.policy.RetryPolicy\nimport com.github.michaelbull.retry.policy.constantDelay\nimport com.github.michaelbull.retry.policy.continueIf\nimport com.github.michaelbull.retry.policy.plus\nimport com.github.michaelbull.retry.policy.stopAtAttempts\nimport com.github.michaelbull.retry.retry\nimport kotlinx.coroutines.runBlocking\nimport java.sql.SQLDataException\n\nsuspend fun printExchangeBetween(a: Long, b: Long) {\n    val customer1 = customers.nameFromId(a)\n    val customer2 = customers.nameFromId(b)\n    println(\"$customer1 exchanged with $customer2\")\n}\n\nval continueOnTimeout = continueIf\u003cThrowable\u003e { (failure) -\u003e\n    failure is SQLDataException\n}\n\nval timeoutsEverySecondFiveTimes = continueOnTimeout + constantDelay(1000) + stopAtAttempts(5)\n\nfun main() = runBlocking {\n    retry(timeoutsEverySecondFiveTimes) {\n        printExchangeBetween(1L, 2L)\n    }\n}\n```\n\n## Integration with [kotlin-result][kotlin-result]\n\nIf the code you wish to try returns a `Result\u003cV, E\u003e` instead of throwing an\n`Exception`, add the following dependency for access to a Result-based `retry`\nfunction that shares the same policy code:\n\n```groovy\nrepositories {\n    mavenCentral()\n}\n\ndependencies {\n    implementation(\"com.michael-bull.kotlin-retry:kotlin-retry-result:2.0.1\")\n}\n```\n\nUsage:\n\n```kotlin\n\nimport com.github.michaelbull.result.Result\nimport com.github.michaelbull.retry.policy.constantDelay\nimport com.github.michaelbull.retry.result.retry\n\nfun somethingThatCanFail(): Result\u003cInt, DomainError\u003e = TODO()\n\nval everyTwoSeconds = constantDelay\u003cDomainError\u003e(2000)\n\nfun main() = runBlocking {\n    val result: Result\u003cInt, DomainError\u003e = retry(everyTwoSeconds) {\n        somethingThatCanFail()\n    }\n}\n```\n\n## Backoff\n\nThe examples above retry executions immediately after they fail, however we may\nwish to spread out retries with an ever-increasing delay. This is known as a\n\"backoff\" and comes in many forms. This library includes all the forms of\nbackoff strategy detailed the article by Marc Brooker on the AWS Architecture\nBlog entitled [\"Exponential Backoff And Jitter\"][aws-backoff].\n\n#### Binary Exponential\n\n\u003e _“exponential backoff means that clients multiply their backoff by a constant\n\u003e   after each attempt, up to some maximum value”_\n\u003e\n\u003e ```\n\u003e sleep = min(cap, base * 2 ** attempt)\n\u003e ```\n\n```kotlin\nretry(binaryExponentialBackoff(min = 10L, max = 5000L)) {\n    /* code */\n}\n```\n\n#### Full Jitter\n\n\u003e _“trying to improve the performance of a system by adding randomness ... we\n\u003e  want to spread out the spikes to an approximately constant rate”_\n\u003e\n\u003e ```\n\u003e sleep = random_between(0, min(cap, base * 2 ** attempt))\n\u003e ```\n\n```kotlin\nretry(fullJitterBackoff(min = 10L, max = 5000L)) {\n    /* code */\n}\n```\n\n#### Equal Jitter\n\n\u003e _“Equal Jitter, where we always keep some of the backoff and jitter by a\n\u003e   smaller amount”_\n\u003e\n\u003e ```\n\u003e temp = min(cap, base * 2 ** attempt)\n\u003e sleep = temp / 2 + random_between(0, temp / 2)\n\u003e ```\n\n```kotlin\nretry(equalJitterBackoff(min = 10L, max = 5000L)) {\n    /* code */\n}\n```\n\n#### Decorrelated Jitter\n\n\u003e _“Decorrelated Jitter, which is similar to “Full Jitter”, but we also\n\u003e   increase the maximum jitter based on the last random value”_\n\u003e\n\u003e ```\n\u003e sleep = min(cap, random_between(base, sleep * 3))\n\u003e ```\n\n```kotlin\nretry(decorrelatedJitterBackoff(min = 10L, max = 5000L)) {\n    /* code */\n}\n```\n\n## Inspiration\n\n- [Control.Retry](http://hackage.haskell.org/package/retry-0.8.0.1/docs/Control-Retry.html)\n- [tokio_retry](https://docs.rs/tokio-retry/0.2.0/tokio_retry/)\n\n## Contributing\n\nBug reports and pull requests are welcome on [GitHub][github].\n\n## License\n\nThis project is available under the terms of the ISC license. See the\n[`LICENSE`](LICENSE) file for the copyright information and licensing terms.\n\n[github]: https://github.com/michaelbull/kotlin-retry\n[retry]: https://github.com/michaelbull/kotlin-retry/blob/master/kotlin-retry/src/commonMain/kotlin/com/github/michaelbull/retry/Retry.kt\n[innodb-deadlocks]: https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html\n[postgres-deadlocks]: https://www.postgresql.org/docs/current/explicit-locking.html#LOCKING-DEADLOCKS\n[retry-policy]: https://github.com/michaelbull/kotlin-retry/blob/master/kotlin-retry/src/commonMain/kotlin/com/github/michaelbull/retry/policy/RetryPolicy.kt\n[aws-backoff]: https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/\n[haskell-retry]: http://hackage.haskell.org/package/retry-0.8.0.1/docs/Control-Retry.html\n[kotlin-result]: https://github.com/michaelbull/kotlin-result\n\n[badge-android]: http://img.shields.io/badge/-android-6EDB8D.svg?style=flat\n[badge-android-native]: http://img.shields.io/badge/support-[AndroidNative]-6EDB8D.svg?style=flat\n[badge-jvm]: http://img.shields.io/badge/-jvm-DB413D.svg?style=flat\n[badge-js]: http://img.shields.io/badge/-js-F8DB5D.svg?style=flat\n[badge-js-ir]: https://img.shields.io/badge/support-[IR]-AAC4E0.svg?style=flat\n[badge-nodejs]: https://img.shields.io/badge/-nodejs-68a063.svg?style=flat\n[badge-linux]: http://img.shields.io/badge/-linux-2D3F6C.svg?style=flat\n[badge-windows]: http://img.shields.io/badge/-windows-4D76CD.svg?style=flat\n[badge-wasm]: https://img.shields.io/badge/-wasm-624FE8.svg?style=flat\n[badge-apple-silicon]: http://img.shields.io/badge/support-[AppleSilicon]-43BBFF.svg?style=flat\n[badge-ios]: http://img.shields.io/badge/-ios-CDCDCD.svg?style=flat\n[badge-mac]: http://img.shields.io/badge/-macos-111111.svg?style=flat\n[badge-watchos]: http://img.shields.io/badge/-watchos-C0C0C0.svg?style=flat\n[badge-tvos]: http://img.shields.io/badge/-tvos-808080.svg?style=flat\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaelbull%2Fkotlin-retry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichaelbull%2Fkotlin-retry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaelbull%2Fkotlin-retry/lists"}