perf: replace synchronous OkHttp execute() with executeAsync()

Every coroutine dispatched to Dispatchers.IO is stamped BlockingContext at
dispatch time, so its worker releases its CPU permit and the shared kotlinx
scheduler grows past ncpu. A blocking execute() holds one of those threads for
the whole request; executeAsync() suspends until the response headers arrive
and releases the thread across the network round-trip.

Converts 27 call sites across quartz, commons, amethyst, nestsClient,
desktopApp and cli. executeAsync() was already the house pattern (63 existing
uses); these were the stragglers.

The withContext(Dispatchers.IO) wrappers are kept on purpose: executeAsync()
only suspends until headers, and reading the body (string()/bytes()) is still
a blocking read. Moving those onto Dispatchers.Default would hold its
core-sized CPU permits and starve the pool.

Four private helpers become suspend (decodeGifFrames, fetchFromNetwork,
downloadFirstChunk, extractFirstFrame). Each had exactly one caller, already
inside a withContext(Dispatchers.IO) in a suspend function, so nothing
propagates further.

desktopApp gains the okhttp-coroutines dependency (Apache-2.0, same version as
the okhttp it already ships).

Not converted: NipCommand.fetchText and RelayCommands.info in the CLI. amy is a
short-lived process whose main is runBlocking { dispatch(argv) } — there is no
long-lived dispatcher pool or UI thread to protect there, so blocking is by
design and making them suspend would only add risk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vitor Pamplona
2026-08-12 19:01:08 -04:00
co-authored by Claude Opus 5
parent c94bb81df5
commit 8020102364
19 changed files with 50 additions and 32 deletions
@@ -30,6 +30,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.coroutines.executeAsync
/** /**
* Mints a `block/buzz` workspace invite link by POSTing to the relay's **Buzz-specific** * Mints a `block/buzz` workspace invite link by POSTing to the relay's **Buzz-specific**
@@ -86,7 +87,7 @@ object BuzzInviteMinter {
.post(bodyBytes.toRequestBody("application/json".toMediaType())) .post(bodyBytes.toRequestBody("application/json".toMediaType()))
.build() .build()
okHttpClient(url).newCall(request).execute().use { response -> okHttpClient(url).newCall(request).executeAsync().use { response ->
val payload = response.body.string() val payload = response.body.string()
val tree = runCatching { json.readTree(payload) }.getOrNull() val tree = runCatching { json.readTree(payload) }.getOrNull()
@@ -30,6 +30,7 @@ import com.vitorpamplona.quartz.utils.Log
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.io.File import java.io.File
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@@ -70,7 +71,7 @@ fun rememberConcordImageModel(
if (!cacheFile.exists()) { if (!cacheFile.exists()) {
val client = accountViewModel.httpClientBuilder.okHttpClientForImage(pointer.url) val client = accountViewModel.httpClientBuilder.okHttpClientForImage(pointer.url)
val ciphertext = val ciphertext =
client.newCall(Request.Builder().url(pointer.url).build()).execute().use { resp -> client.newCall(Request.Builder().url(pointer.url).build()).executeAsync().use { resp ->
if (!resp.isSuccessful) return@runCatching null if (!resp.isSuccessful) return@runCatching null
resp.body.bytes() resp.body.bytes()
} }
@@ -29,6 +29,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.io.File import java.io.File
import java.security.MessageDigest import java.security.MessageDigest
@@ -63,7 +64,7 @@ internal object RoomFontLoader {
}.getOrNull() }.getOrNull()
} }
private fun ensureCached( private suspend fun ensureCached(
url: String, url: String,
context: Context, context: Context,
clientFor: (String) -> OkHttpClient, clientFor: (String) -> OkHttpClient,
@@ -74,7 +75,7 @@ internal object RoomFontLoader {
val client = clientFor(url) val client = clientFor(url)
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { resp -> client.newCall(request).executeAsync().use { resp ->
if (!resp.isSuccessful) return null if (!resp.isSuccessful) return null
file.outputStream().use { out -> resp.body.byteStream().copyTo(out) } file.outputStream().use { out -> resp.body.byteStream().copyTo(out) }
} }
@@ -74,6 +74,7 @@ import kotlinx.coroutines.withTimeoutOrNull
import okhttp3.Dispatcher import okhttp3.Dispatcher
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -304,7 +305,7 @@ class Context(
.url(relay.toHttp()) .url(relay.toHttp())
.header("Accept", "application/nostr+json") .header("Accept", "application/nostr+json")
.build() .build()
okhttp.newCall(request).execute().use { resp -> okhttp.newCall(request).executeAsync().use { resp ->
resp.body.string().let { Nip11RelayInformation.fromJson(it) } resp.body.string().let { Nip11RelayInformation.fromJson(it) }
} }
}.getOrNull() }.getOrNull()
@@ -57,6 +57,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.coroutines.executeAsync
/** /**
* `amy buzz …` — first-class access to the `block/buzz` workspace protocol, driving the * `amy buzz …` — first-class access to the `block/buzz` workspace protocol, driving the
@@ -372,7 +373,7 @@ object BuzzCommands {
.url(url) .url(url)
.get() .get()
.build(), .build(),
).execute() ).executeAsync()
.use { it.code to it.body.string() } .use { it.code to it.body.string() }
} }
@@ -385,7 +386,7 @@ object BuzzCommands {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val builder = Request.Builder().url(url).post(body.toRequestBody(jsonMedia)) val builder = Request.Builder().url(url).post(body.toRequestBody(jsonMedia))
if (auth != null) builder.header("Authorization", auth) if (auth != null) builder.header("Authorization", auth)
http.newCall(builder.build()).execute().use { it.code to it.body.string() } http.newCall(builder.build()).executeAsync().use { it.code to it.body.string() }
} }
/** `buzz post RELAY GID <text>` → publishes a kind-40002 stream message with an `h` tag. */ /** `buzz post RELAY GID <text>` → publishes a kind-40002 stream message with an `h` tag. */
@@ -24,6 +24,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
/** /**
* Fetches and parses the live georelays CSV so the [GeoRelayDirectory] can route * Fetches and parses the live georelays CSV so the [GeoRelayDirectory] can route
@@ -44,7 +45,7 @@ class GeoRelayCsvLoader(
.url(url) .url(url)
.get() .get()
.build() .build()
okHttpClient(url).newCall(request).execute().use { response -> okHttpClient(url).newCall(request).executeAsync().use { response ->
if (response.isSuccessful) { if (response.isSuccessful) {
GeoRelayDirectory.parseCsv(response.body.string()) GeoRelayDirectory.parseCsv(response.body.string())
} else { } else {
@@ -30,6 +30,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
import java.net.URLEncoder import java.net.URLEncoder
@@ -191,7 +192,7 @@ class LightningAddressResolver(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
httpClient.newCall(request).execute().use { response -> httpClient.newCall(request).executeAsync().use { response ->
if (response.isSuccessful) { if (response.isSuccessful) {
response.body.string() response.body.string()
} else { } else {
@@ -222,7 +223,7 @@ class LightningAddressResolver(
} }
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
httpClient.newCall(request).execute().use { response -> httpClient.newCall(request).executeAsync().use { response ->
// Return body even on error — caller extracts "reason" or "message" from JSON // Return body even on error — caller extracts "reason" or "message" from JSON
response.body.string() response.body.string()
} }
@@ -28,6 +28,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.cancellation.CancellationException
/** /**
@@ -58,7 +59,7 @@ class OkHttpLnurlEndpointResolver(
try { try {
val client = okHttpClient(url) val client = okHttpClient(url)
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
client.newCall(request).execute().use { response -> client.newCall(request).executeAsync().use { response ->
if (!response.isSuccessful) return@use null if (!response.isSuccessful) return@use null
val body = response.body.string() val body = response.body.string()
val root = mapper.readTree(body) ?: return@use null val root = mapper.readTree(body) ?: return@use null
@@ -36,6 +36,7 @@ import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import okhttp3.coroutines.executeAsync
import okio.BufferedSink import okio.BufferedSink
import okio.source import okio.source
import java.io.File import java.io.File
@@ -149,7 +150,7 @@ open class BlossomClient(
paymentProof?.headers()?.forEach { (name, value) -> addHeader(name, value) } paymentProof?.headers()?.forEach { (name, value) -> addHeader(name, value) }
}.put(body) }.put(body)
.build() .build()
okHttpClient.newCall(request).execute().use { response -> okHttpClient.newCall(request).executeAsync().use { response ->
if (response.code in MIRROR_UNSUPPORTED_CODES) { if (response.code in MIRROR_UNSUPPORTED_CODES) {
throw BlossomMirrorUnsupportedException(serverBaseUrl, response.code) throw BlossomMirrorUnsupportedException(serverBaseUrl, response.code)
} }
@@ -208,7 +209,7 @@ open class BlossomClient(
.apply { authHeader?.let { addHeader("Authorization", it) } } .apply { authHeader?.let { addHeader("Authorization", it) } }
.get() .get()
.build() .build()
okHttpClient.newCall(request).execute().use { response -> okHttpClient.newCall(request).executeAsync().use { response ->
check402(response, serverBaseUrl) check402(response, serverBaseUrl)
if (!response.isSuccessful) { if (!response.isSuccessful) {
val reason = response.headers[BlossomServerUrl.REASON_HEADER] ?: response.code.toString() val reason = response.headers[BlossomServerUrl.REASON_HEADER] ?: response.code.toString()
@@ -237,7 +238,7 @@ open class BlossomClient(
.apply { authHeader?.let { addHeader("Authorization", it) } } .apply { authHeader?.let { addHeader("Authorization", it) } }
.delete() .delete()
.build() .build()
okHttpClient.newCall(request).execute().use { it.isSuccessful } okHttpClient.newCall(request).executeAsync().use { it.isSuccessful }
} }
/** /**
@@ -256,7 +257,7 @@ open class BlossomClient(
.head() .head()
.build() .build()
try { try {
okHttpClient.newCall(request).execute().use { it.isSuccessful } okHttpClient.newCall(request).executeAsync().use { it.isSuccessful }
} catch (e: CancellationException) { } catch (e: CancellationException) {
throw e throw e
} catch (_: Exception) { } catch (_: Exception) {
@@ -289,7 +290,7 @@ open class BlossomClient(
.addHeader(BlossomServerUrl.X_CONTENT_TYPE_HEADER, contentType) .addHeader(BlossomServerUrl.X_CONTENT_TYPE_HEADER, contentType)
.apply { authHeader?.let { addHeader("Authorization", it) } } .apply { authHeader?.let { addHeader("Authorization", it) } }
.build() .build()
okHttpClient.newCall(request).execute().use { response -> okHttpClient.newCall(request).executeAsync().use { response ->
BlossomPreflightResult( BlossomPreflightResult(
accepted = response.isSuccessful, accepted = response.isSuccessful,
status = response.code, status = response.code,
@@ -313,7 +314,7 @@ open class BlossomClient(
.url(BlossomServerUrl.report(serverBaseUrl)) .url(BlossomServerUrl.report(serverBaseUrl))
.put(reportEventJson.toRequestBody("application/json".toMediaType())) .put(reportEventJson.toRequestBody("application/json".toMediaType()))
.build() .build()
okHttpClient.newCall(request).execute().use { it.isSuccessful } okHttpClient.newCall(request).executeAsync().use { it.isSuccessful }
} }
/** /**
@@ -335,7 +336,7 @@ open class BlossomClient(
.url(url) .url(url)
.get() .get()
.build() .build()
okHttpClient.newCall(request).execute().use { response -> okHttpClient.newCall(request).executeAsync().use { response ->
if (response.isSuccessful) response.body.bytes() else null if (response.isSuccessful) response.body.bytes() else null
} }
} }
@@ -368,7 +369,7 @@ open class BlossomClient(
.apply { authHeader?.let { addHeader("Authorization", it) } } .apply { authHeader?.let { addHeader("Authorization", it) } }
.put(body) .put(body)
.build() .build()
okHttpClient.newCall(request).execute().use { parseDescriptor(it, serverBaseUrl) } okHttpClient.newCall(request).executeAsync().use { parseDescriptor(it, serverBaseUrl) }
} }
private fun parseDescriptor( private fun parseDescriptor(
+1
View File
@@ -47,6 +47,7 @@ dependencies {
// Networking // Networking
implementation(libs.okhttp) implementation(libs.okhttp)
implementation(libs.okhttpCoroutines)
// JSON // JSON
implementation(libs.jackson.module.kotlin) implementation(libs.jackson.module.kotlin)
@@ -30,6 +30,7 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
/** /**
@@ -67,7 +68,7 @@ class Nip11Fetcher {
} }
} }
private fun fetchFromNetwork(url: NormalizedRelayUrl): Nip11RelayInformation? { private suspend fun fetchFromNetwork(url: NormalizedRelayUrl): Nip11RelayInformation? {
// FAIL-CLOSED: use currentClient() not getHttpClient() // FAIL-CLOSED: use currentClient() not getHttpClient()
val client = DesktopHttpClient.currentClient() val client = DesktopHttpClient.currentClient()
val httpUrl = url.toHttp() val httpUrl = url.toHttp()
@@ -78,7 +79,7 @@ class Nip11Fetcher {
.header("Accept", "application/nostr+json") .header("Accept", "application/nostr+json")
.build() .build()
return try { return try {
client.newCall(request).execute().use { response -> client.newCall(request).executeAsync().use { response ->
if (response.isSuccessful) { if (response.isSuccessful) {
val source = response.body.source() val source = response.body.source()
source.request(MAX_RESPONSE_BYTES) // buffer up to limit source.request(MAX_RESPONSE_BYTES) // buffer up to limit
@@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.utils.ciphers.AESGCM
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
/** /**
@@ -51,7 +52,7 @@ object EncryptedMediaService {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
val response = httpClient.newCall(request).execute() val response = httpClient.newCall(request).executeAsync()
val encryptedBytes = val encryptedBytes =
response.use { response.use {
if (!it.isSuccessful) throw RuntimeException("Download failed: ${it.code}") if (!it.isSuccessful) throw RuntimeException("Download failed: ${it.code}")
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.desktop.network.DesktopHttpClient
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
object ServerHealthCheck { object ServerHealthCheck {
enum class ServerStatus { enum class ServerStatus {
@@ -45,7 +46,7 @@ object ServerHealthCheck {
.url(url) .url(url)
.head() .head()
.build() .build()
val response = DesktopHttpClient.currentClient().newCall(request).execute() val response = DesktopHttpClient.currentClient().newCall(request).executeAsync()
response.use { response.use {
if (it.isSuccessful || it.code == 405) ServerStatus.ONLINE else ServerStatus.OFFLINE if (it.isSuccessful || it.code == 405) ServerStatus.ONLINE else ServerStatus.OFFLINE
} }
@@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.desktop.network.DesktopHttpClient
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import org.jcodec.api.FrameGrab import org.jcodec.api.FrameGrab
import org.jcodec.common.io.NIOUtils import org.jcodec.common.io.NIOUtils
import org.jcodec.common.model.ColorSpace import org.jcodec.common.model.ColorSpace
@@ -122,7 +123,7 @@ object VideoThumbnailCache {
} }
} }
private fun extractFirstFrame(url: String): ImageBitmap? { private suspend fun extractFirstFrame(url: String): ImageBitmap? {
// For HLS we skip straight to ffmpeg — JCodec can't read m3u8. // For HLS we skip straight to ffmpeg — JCodec can't read m3u8.
val isHls = url.contains(".m3u8", ignoreCase = true) || url.contains("/hls/", ignoreCase = true) val isHls = url.contains(".m3u8", ignoreCase = true) || url.contains("/hls/", ignoreCase = true)
@@ -163,7 +164,7 @@ object VideoThumbnailCache {
* *
* Cleans up zero-byte cache files on failure so a transient empty response isn't sticky. * Cleans up zero-byte cache files on failure so a transient empty response isn't sticky.
*/ */
private fun downloadFirstChunk(url: String): Download? { private suspend fun downloadFirstChunk(url: String): Download? {
val hash = sha1Hex(url) val hash = sha1Hex(url)
val cached = File(downloadCacheDir, "$hash.mp4") val cached = File(downloadCacheDir, "$hash.mp4")
if (cached.length() > 0L) return Download(cached, persistable = true) if (cached.length() > 0L) return Download(cached, persistable = true)
@@ -171,7 +172,7 @@ object VideoThumbnailCache {
var wrote = false var wrote = false
var rangeHonored = false var rangeHonored = false
DesktopHttpClient.currentClient().newCall(buildRangeRequest(url)).execute().use { resp -> DesktopHttpClient.currentClient().newCall(buildRangeRequest(url)).executeAsync().use { resp ->
if (!resp.isSuccessful && resp.code != 206) return null if (!resp.isSuccessful && resp.code != 206) return null
val contentType = resp.header("Content-Type")?.lowercase().orEmpty() val contentType = resp.header("Content-Type")?.lowercase().orEmpty()
if (contentType.startsWith("text/") || "html" in contentType) return null if (contentType.startsWith("text/") || "html" in contentType) return null
@@ -82,6 +82,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.net.URLEncoder import java.net.URLEncoder
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -141,7 +142,7 @@ private suspend fun resolveNip05Http(identifier: String): String? {
.readTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS)
.build() .build()
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
val response = client.newCall(request).execute() val response = client.newCall(request).executeAsync()
response.use { resp -> response.use { resp ->
if (!resp.isSuccessful) return@withContext null if (!resp.isSuccessful) return@withContext null
val body = resp.body.string() val body = resp.body.string()
@@ -41,6 +41,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import org.jetbrains.skia.Bitmap import org.jetbrains.skia.Bitmap
import org.jetbrains.skia.Codec import org.jetbrains.skia.Codec
import org.jetbrains.skia.Data import org.jetbrains.skia.Data
@@ -131,10 +132,10 @@ fun AnimatedGifImage(
} }
} }
private fun decodeGifFrames(url: String): GifFrames? = private suspend fun decodeGifFrames(url: String): GifFrames? =
try { try {
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
val response = gifHttpClient.newCall(request).execute() val response = gifHttpClient.newCall(request).executeAsync()
val bytes = response.body.bytes() val bytes = response.body.bytes()
val skData = Data.makeFromBytes(bytes) val skData = Data.makeFromBytes(bytes)
@@ -24,6 +24,7 @@ import com.vitorpamplona.amethyst.desktop.network.DesktopHttpClient
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Request import okhttp3.Request
import okhttp3.coroutines.executeAsync
import java.awt.FileDialog import java.awt.FileDialog
import java.awt.Frame import java.awt.Frame
import java.io.File import java.io.File
@@ -59,7 +60,7 @@ object SaveMediaAction {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
try { try {
val request = Request.Builder().url(url).build() val request = Request.Builder().url(url).build()
val response = httpClient.newCall(request).execute() val response = httpClient.newCall(request).executeAsync()
response.use { resp -> response.use { resp ->
if (!resp.isSuccessful) return@withContext null if (!resp.isSuccessful) return@withContext null
val total = resp.body.contentLength() val total = resp.body.contentLength()
@@ -31,6 +31,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import okhttp3.coroutines.executeAsync
import java.io.EOFException import java.io.EOFException
import java.io.IOException import java.io.IOException
import java.net.SocketException import java.net.SocketException
@@ -167,7 +168,7 @@ class OkHttpNestsClient(
val request = buildRequest() val request = buildRequest()
val response: Response = val response: Response =
try { try {
httpClient(url).newCall(request).execute() httpClient(url).newCall(request).executeAsync()
} catch (e: SocketException) { } catch (e: SocketException) {
transportError = e transportError = e
if (++transportAttempts >= MAX_TRANSPORT_RETRIES) throw NestsException("Failed to reach $url", e) if (++transportAttempts >= MAX_TRANSPORT_RETRIES) throw NestsException("Failed to reach $url", e)
@@ -58,7 +58,7 @@ class OkHttpBitcoinExplorer(
.get() .get()
.build() .build()
return client.newCall(request).execute().use { return client.newCall(request).executeAsync().use {
if (it.isSuccessful) { if (it.isSuccessful) {
Log.d("OkHttpBlockstreamExplorer") { "$baseAPI/block/$hash" } Log.d("OkHttpBlockstreamExplorer") { "$baseAPI/block/$hash" }