From 1fa094bd799cb6102f51888036660b5ecf2655e1 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 22 Feb 2026 12:30:41 +0000 Subject: [PATCH 1/6] Run CacheTest emulating windows Investigate and fix issues affecting windows. From #9335 --- okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt index 573f085b8642..18d983e329d1 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt @@ -15,6 +15,7 @@ */ package okhttp3 +import app.cash.burst.Burst import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isCloseTo @@ -70,7 +71,8 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension @Tag("Slow") -class CacheTest { +@Burst +class CacheTest(val emulateWindows: Boolean = false) { val fileSystem = FakeFileSystem() @RegisterExtension @@ -94,7 +96,11 @@ class CacheTest { fun setUp() { platform.assumeNotOpenJSSE() server.protocolNegotiationEnabled = false - fileSystem.emulateUnix() + if (emulateWindows) { + fileSystem.emulateWindows() + } else { + fileSystem.emulateUnix() + } cache = Cache(fileSystem, "/cache/".toPath(), Long.MAX_VALUE) client = clientTestRule From 7a605a2e5bfd073b80fba5b4efcda939af8a9dd2 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 22 Feb 2026 12:30:41 +0000 Subject: [PATCH 2/6] Run CacheTest emulating windows Investigate and fix issues affecting windows. From #9335 --- okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt index 573f085b8642..5591ddb99f11 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt @@ -15,6 +15,7 @@ */ package okhttp3 +import app.cash.burst.Burst import assertk.assertThat import assertk.assertions.containsExactly import assertk.assertions.isCloseTo @@ -70,7 +71,10 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension @Tag("Slow") -class CacheTest { +@Burst +class CacheTest( + val emulatedFileSystem: EmulatedFileSystem = EmulatedFileSystem.Unix, +) { val fileSystem = FakeFileSystem() @RegisterExtension @@ -94,7 +98,11 @@ class CacheTest { fun setUp() { platform.assumeNotOpenJSSE() server.protocolNegotiationEnabled = false - fileSystem.emulateUnix() + if (emulatedFileSystem == EmulatedFileSystem.Windows) { + fileSystem.emulateWindows() + } else { + fileSystem.emulateUnix() + } cache = Cache(fileSystem, "/cache/".toPath(), Long.MAX_VALUE) client = clientTestRule @@ -4126,6 +4134,10 @@ CLEAN $urlKey ${entryMetadata.length} ${entryBody.length} return result } + enum class EmulatedFileSystem { + Unix, Windows + } + companion object { private val NULL_HOSTNAME_VERIFIER = HostnameVerifier { hostname, session -> true } } From 330a3b8366c3264b804c2988f6025cda71dea6d2 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 22 Feb 2026 17:37:13 +0000 Subject: [PATCH 3/6] clenaup --- okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt index 5591ddb99f11..212e437e2ba2 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt @@ -4135,7 +4135,8 @@ CLEAN $urlKey ${entryMetadata.length} ${entryBody.length} } enum class EmulatedFileSystem { - Unix, Windows + Unix, + Windows, } companion object { From 3b4d0af7c99d934647d1c4e30fee035d9854572f Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 22 Feb 2026 22:01:09 +0000 Subject: [PATCH 4/6] Fix basic issues --- .../src/jvmTest/kotlin/okhttp3/CacheTest.kt | 52 ++++++++++++++----- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt index 212e437e2ba2..4b1aa927730c 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt @@ -418,6 +418,9 @@ class CacheTest( assertThat(cache.requestCount()).isEqualTo(2) assertThat(cache.networkCount()).isEqualTo(2) assertThat(cache.hitCount()).isEqualTo(0) + + response1.close() + response2.close() } private fun corruptCertificate(cacheEntry: Path) { @@ -3796,6 +3799,8 @@ CLEAN $urlKey ${entryMetadata.length} ${entryBody.length} assertThat(response.request.url).isEqualTo(request.url) assertThat(response.cacheResponse!!.request.url).isEqualTo(request.url) + + response.close() } @Test @@ -3815,6 +3820,8 @@ CLEAN $urlKey ${entryMetadata.length} ${entryBody.length} assertThat(response.request.url).isEqualTo(request.url) assertThat(response.cacheResponse!!.request.url).isEqualTo(cacheUrlOverride) + + response.close() } private fun testBasicCachingRules(request: Request): Response { @@ -4033,22 +4040,41 @@ CLEAN $urlKey ${entryMetadata.length} ${entryBody.length} val c = Cache(loggingFileSystem, path, 100000L) assertThat(c.directoryPath).isEqualTo(path) c.size() - assertThat(events).containsExactly( - "metadataOrNull:/cache/journal.bkp", - "metadataOrNull:/cache", - "sink:/cache/journal.bkp", - "delete:/cache/journal.bkp", - "metadataOrNull:/cache/journal", - "metadataOrNull:/cache", - "sink:/cache/journal.tmp", - "metadataOrNull:/cache/journal", - "atomicMove:/cache/journal.tmp", - "atomicMove:/cache/journal", - "appendingSink:/cache/journal", - ) + if (emulatedFileSystem == EmulatedFileSystem.Unix) { + assertThat(events).containsExactly( + "metadataOrNull:/cache/journal.bkp", + "metadataOrNull:/cache", + "sink:/cache/journal.bkp", + "delete:/cache/journal.bkp", + "metadataOrNull:/cache/journal", + "metadataOrNull:/cache", + "sink:/cache/journal.tmp", + "metadataOrNull:/cache/journal", + "atomicMove:/cache/journal.tmp", + "atomicMove:/cache/journal", + "appendingSink:/cache/journal", + ) + } else { + assertThat(events).containsExactly( + "metadataOrNull:/cache/journal.bkp", + "metadataOrNull:/cache", + "sink:/cache/journal.bkp", + "delete:/cache/journal.bkp", + "delete:/cache/journal.bkp", // investigate + "metadataOrNull:/cache/journal", + "metadataOrNull:/cache", + "sink:/cache/journal.tmp", + "metadataOrNull:/cache/journal", + "atomicMove:/cache/journal.tmp", + "atomicMove:/cache/journal", + "appendingSink:/cache/journal", + ) + } events.clear() c.size() assertThat(events).isEmpty() + + c.close() } private fun assertFullyCached(response: MockResponse) { From 9bab8a9d287bc5287da0392689f8acc3c0976e27 Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 22 Feb 2026 22:33:16 +0000 Subject: [PATCH 5/6] Attempt a Fix for windows caching --- .../cache/DeferredForwardingSource.kt | 39 +++++++++++++++++++ .../okhttp3/internal/cache/DiskLruCache.kt | 26 ++++++++----- 2 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/DeferredForwardingSource.kt diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/DeferredForwardingSource.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/DeferredForwardingSource.kt new file mode 100644 index 000000000000..750829e813e9 --- /dev/null +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/DeferredForwardingSource.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Block, 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.internal.cache + +import okio.Buffer +import okio.Source + +/** + * Okio ForwardingSource, but without eagerly opening the file. + */ +abstract class DeferredForwardingSource( + delegateFn: () -> Source, +) : Source { + val delegate by lazy(delegateFn) + + override fun read( + sink: Buffer, + byteCount: Long, + ): Long = delegate.read(sink, byteCount) + + override fun timeout() = delegate.timeout() + + override fun close() = delegate.close() + + override fun toString() = "${javaClass.simpleName}($delegate)" +} diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/DiskLruCache.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/DiskLruCache.kt index dbf022e26d19..da134cb55713 100644 --- a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/DiskLruCache.kt +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/internal/cache/DiskLruCache.kt @@ -19,7 +19,6 @@ import java.io.Closeable import java.io.EOFException import java.io.Flushable import java.io.IOException -import okhttp3.internal.cache.DiskLruCache.Editor import okhttp3.internal.closeQuietly import okhttp3.internal.concurrent.Lockable import okhttp3.internal.concurrent.Task @@ -35,7 +34,6 @@ import okio.BufferedSink import okio.FileNotFoundException import okio.FileSystem import okio.ForwardingFileSystem -import okio.ForwardingSource import okio.Path import okio.Sink import okio.Source @@ -1072,21 +1070,29 @@ class DiskLruCache( } private fun newSource(index: Int): Source { - val fileSource = fileSystem.source(cleanFiles[index]) - if (civilizedFileSystem) return fileSource + val file = cleanFiles[index] + if (civilizedFileSystem) return fileSystem.source(file) - lockingSourceCount++ - return object : ForwardingSource(fileSource) { + // Whether the source has been actually acquired + var inited = false + + return object : DeferredForwardingSource({ + inited = true + lockingSourceCount++ + fileSystem.source(file) + }) { private var closed = false override fun close() { super.close() if (!closed) { closed = true - synchronized(this@DiskLruCache) { - lockingSourceCount-- - if (lockingSourceCount == 0 && zombie) { - removeEntry(this@Entry) + if (inited) { + synchronized(this@DiskLruCache) { + lockingSourceCount-- + if (lockingSourceCount == 0 && zombie) { + removeEntry(this@Entry) + } } } } From 7e3e220e6c9457f9a0fd117a30f2319280b9bf9f Mon Sep 17 00:00:00 2001 From: Yuri Schimke Date: Sun, 22 Feb 2026 22:37:51 +0000 Subject: [PATCH 6/6] Attempt a Fix for windows caching --- okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt index 4b1aa927730c..0b75f608fe25 100644 --- a/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt +++ b/okhttp/src/jvmTest/kotlin/okhttp3/CacheTest.kt @@ -424,7 +424,7 @@ class CacheTest( } private fun corruptCertificate(cacheEntry: Path) { - var content = fileSystem.source(cacheEntry).buffer().readUtf8() + var content = fileSystem.source(cacheEntry).buffer().use { it.readUtf8() } content = content.replace("MII", "!!!") fileSystem .sink(cacheEntry)