Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loom Support #7367

Merged
merged 19 commits into from
Jan 3, 2023
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
8 changes: 8 additions & 0 deletions okhttp-loom/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
OkHttp Loom
===========

Support for Loom

```kotlin
implementation("com.squareup.okhttp3:okhttp-loom:4.10.0")
```
47 changes: 47 additions & 0 deletions okhttp-loom/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import com.vanniktech.maven.publish.JavadocJar
import com.vanniktech.maven.publish.KotlinJvm

plugins {
kotlin("jvm")
id("org.jetbrains.dokka")
id("com.vanniktech.maven.publish.base")
id("binary-compatibility-validator")
}

project.applyOsgi(
"Export-Package: okhttp3.loom",
"Automatic-Module-Name: okhttp3.loom",
"Bundle-SymbolicName: com.squareup.okhttp3.loom"
)

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(19))
}
}

tasks.withType<Test> {
val javaToolchains = project.extensions.getByType<JavaToolchainService>()
javaLauncher.set(javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(19))
})
jvmArgs("--enable-preview")
}

tasks.withType<JavaCompile> {
options.compilerArgs.add("--enable-preview")
}

dependencies {
api(projects.okhttp)
compileOnly(libs.findbugs.jsr305)

testImplementation(projects.okhttpTestingSupport)
testImplementation(libs.conscrypt.openjdk)
testImplementation(libs.junit)
testImplementation(libs.assertj.core)
}

mavenPublishing {
configure(KotlinJvm(javadocJar = JavadocJar.Dokka("dokkaGfm")))
}
56 changes: 56 additions & 0 deletions okhttp-loom/src/main/kotlin/okhttp3/loom/LoomBackend.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2022 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.loom

import java.util.concurrent.BlockingQueue
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import okhttp3.internal.concurrent.TaskRunner
import okhttp3.internal.notify

/**
* May not be needed, if doesn't change from real backend.
*/
class LoomBackend(
internal val executor: ExecutorService = Executors.newVirtualThreadPerTaskExecutor()
) : TaskRunner.Backend {
override fun nanoTime(): Long = System.nanoTime()


override fun coordinatorNotify(taskRunner: TaskRunner) {
taskRunner.notify()
yschimke marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Wait a duration in nanoseconds. Unlike [java.lang.Object.wait] this interprets 0 as
* "don't wait" instead of "wait forever".
*/
@Throws(InterruptedException::class)
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
override fun coordinatorWait(taskRunner: TaskRunner, nanos: Long) {
val ms = nanos / 1_000_000L
val ns = nanos - (ms * 1_000_000L)
if (ms > 0L || nanos > 0) {
(taskRunner as Object).wait(ms, ns.toInt())
}
}

override fun <T> decorate(queue: BlockingQueue<T>) = queue

override fun execute(taskRunner: TaskRunner, runnable: Runnable) {
executor.execute(runnable)
}
}
28 changes: 28 additions & 0 deletions okhttp-loom/src/main/kotlin/okhttp3/loom/LoomClientBuilder.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package okhttp3.loom

import java.util.concurrent.TimeUnit
import okhttp3.ConnectionPool
import okhttp3.Dispatcher
import okhttp3.OkHttpClient
import okhttp3.internal.concurrent.TaskRunner

object LoomClientBuilder {
fun clientBuilder(): OkHttpClient.Builder {
val backend = LoomBackend()
val taskRunner = TaskRunner(backend)

return OkHttpClient.Builder()
.dispatcher(Dispatcher(backend.executor))
.connectionPool(
ConnectionPool(
maxIdleConnections = 5,
keepAliveDuration = 5,
timeUnit = TimeUnit.MINUTES,
taskRunner = taskRunner
)
)
.taskRunner(taskRunner)
}

fun client(): OkHttpClient = clientBuilder().build()
}
89 changes: 89 additions & 0 deletions okhttp-loom/src/test/java/okhttp3/loom/LoomBackendTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2019 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package okhttp3.loom

import java.net.InetSocketAddress
import java.net.Proxy
import java.util.concurrent.CompletableFuture
import okhttp3.Call
import okhttp3.Callback
import okhttp3.EventListener
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response
import okio.IOException
import org.assertj.core.api.Assertions.*
import org.junit.jupiter.api.Test

class LoomBackendTest {
private var assertVirtual = false

val client = LoomClientBuilder.clientBuilder()
.eventListener(object : EventListener() {
override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) {
assertVirtual()
}

override fun requestHeadersStart(call: Call) {
assertVirtual()
}

override fun responseHeadersStart(call: Call) {
assertVirtual()
}
})
.build()

fun assertVirtual() {
if (assertVirtual) {
assertThat(Thread.currentThread().isVirtual).isTrue()
}
}

@Test
fun makeExecuteRequest() {
val testThread = Thread.currentThread()

val response =
client.newCall(Request("https://www.google.com/robots.txt".toHttpUrl())).execute()

assertThat(response.protocol).isEqualTo(Protocol.HTTP_2)
assertThat(response.body.string()).contains("Disallow")
}

@Test
fun makeEnqueueRequest() {
assertVirtual = true

val completableFuture = CompletableFuture<String>()

val response =
client.newCall(Request("https://www.google.com/robots.txt".toHttpUrl())).enqueue(
object: Callback {
override fun onFailure(call: Call, e: IOException) {
completableFuture.completeExceptionally(e)
}

override fun onResponse(call: Call, response: Response) {
completableFuture.complete(response.body.string())
}
}
)

assertThat(completableFuture.get()).contains("Disallow")
}
}
18 changes: 16 additions & 2 deletions okhttp/src/jvmMain/kotlin/okhttp3/Cache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.util.TreeSet
import java.util.concurrent.TimeUnit
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.internal.EMPTY_HEADERS
Expand All @@ -33,6 +34,7 @@ import okhttp3.internal.cache.DiskLruCache
import okhttp3.internal.closeQuietly
import okhttp3.internal.code
import okhttp3.internal.concurrent.TaskRunner
import okhttp3.internal.connection.RealConnectionPool
import okhttp3.internal.http.HttpMethod
import okhttp3.internal.http.StatusLine
import okhttp3.internal.platform.Platform
Expand Down Expand Up @@ -145,15 +147,27 @@ import okio.buffer
class Cache(
directory: Path,
maxSize: Long,
fileSystem: FileSystem
fileSystem: FileSystem,
taskRunner: TaskRunner
yschimke marked this conversation as resolved.
Show resolved Hide resolved
) : Closeable, Flushable {
constructor(
directory: Path,
maxSize: Long,
fileSystem: FileSystem,
) : this(
directory,
maxSize,
fileSystem,
TaskRunner.INSTANCE
)

internal val cache = DiskLruCache(
fileSystem = fileSystem,
directory = directory,
appVersion = VERSION,
valueCount = ENTRY_COUNT,
maxSize = maxSize,
taskRunner = TaskRunner.INSTANCE
taskRunner = taskRunner
)

// read and write statistics, all guarded by 'this'.
Expand Down
16 changes: 14 additions & 2 deletions okhttp/src/jvmMain/kotlin/okhttp3/ConnectionPool.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,26 @@ class ConnectionPool internal constructor(
constructor(
maxIdleConnections: Int,
keepAliveDuration: Long,
timeUnit: TimeUnit
timeUnit: TimeUnit,
taskRunner: TaskRunner
) : this(RealConnectionPool(
taskRunner = TaskRunner.INSTANCE,
taskRunner = taskRunner,
maxIdleConnections = maxIdleConnections,
keepAliveDuration = keepAliveDuration,
timeUnit = timeUnit
))

constructor(
maxIdleConnections: Int,
keepAliveDuration: Long,
timeUnit: TimeUnit,
) : this(RealConnectionPool(
taskRunner = TaskRunner.INSTANCE,
maxIdleConnections = maxIdleConnections,
keepAliveDuration = keepAliveDuration,
timeUnit = timeUnit
))

constructor() : this(5, 5, TimeUnit.MINUTES)

/** Returns the number of idle connections in the pool. */
Expand Down
4 changes: 4 additions & 0 deletions okhttp/src/jvmMain/kotlin/okhttp3/OkHttpClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,10 @@ open class OkHttpClient internal constructor(
this.cache = cache
}

fun taskRunner(taskRunner: TaskRunner) = apply {
yschimke marked this conversation as resolved.
Show resolved Hide resolved
this.taskRunner = taskRunner
}

/**
* Sets the DNS service used to lookup IP addresses for hostnames.
*
Expand Down
2 changes: 1 addition & 1 deletion okhttp/src/jvmMain/kotlin/okhttp3/internal/-UtilJvm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ internal fun Int.toHexString(): String = Integer.toHexString(this)
internal inline fun Any.wait() = (this as Object).wait()

@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
internal inline fun Any.notify() = (this as Object).notify()
inline fun Any.notify() = (this as Object).notify()
yschimke marked this conversation as resolved.
Show resolved Hide resolved

@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
internal inline fun Any.notifyAll() = (this as Object).notifyAll()
Expand Down
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,6 @@ include(":samples:slack")
include(":samples:static-server")
include(":samples:unixdomainsockets")

include(":okhttp-loom")

enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")