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

Exclusive Cache Lock #8209

Open
wants to merge 33 commits into
base: master
Choose a base branch
from
Open

Exclusive Cache Lock #8209

wants to merge 33 commits into from

Conversation

yschimke
Copy link
Collaborator

@yschimke yschimke commented Jan 20, 2024

Implement a strict CacheLock that works within the process, and also via file system locking if supported.

We can expect a bunch of failures in apps that aren't strictly using a single Cache instance, but should be.

Follow up to #8083

@yschimke yschimke changed the title [for discussion] Exclusive Cache Lock Exclusive Cache Lock Apr 1, 2024
@yschimke yschimke marked this pull request as ready for review April 1, 2024 16:31
@yschimke yschimke added the android Relates to usage specifically on Android label Apr 1, 2024
Copy link
Member

@swankjesse swankjesse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good feature but I’d like a lot more integration tests before we put this into end users’ hands

okhttp/src/main/kotlin/okhttp3/internal/cache/CacheLock.kt Outdated Show resolved Hide resolved
okhttp/src/main/kotlin/okhttp3/internal/cache/CacheLock.kt Outdated Show resolved Hide resolved
okhttp/src/test/java/okhttp3/CacheLockTest.kt Show resolved Hide resolved

private fun openCache(directory: okio.Path): Cache {
return Cache(directory, 10_000, FileSystem.SYSTEM).apply {
// force early LRU initialisation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m a bit torn on this. You don’t get a failure immediately; you get it eventually.

This is probably fine; this new feature is advisory.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this currently works until it doesn't. Then it ends up as 40+ bugs raised.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't really do IO operations on the main thread, so this should always be the case.

Which reminds me, for SSL init on main thread #8248

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with ‘We can’t really do I/O operations on the main thread’, but I don’t agree that OkHttpClient is being initialized on the main thread. We’re an I/O library!

lockFile.toFile().createNewFile()
val channel = FileChannel.open(lockFile.toNioPath(), StandardOpenOption.APPEND)

checkNotNull(channel.tryLock()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m anxious that this is going to fail for reasons related to the host file system’s lack of support, and not because the file is positively locked.

Would it be horrible to probe the file system if a lock fails to see if EVERY lock is going to fail? Maybe we attempt to lock a file named with a random token or a timestamp? If that also fails we know it’s the file system’s fault?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A big +1 to okio solving this in a "Swanky" new API

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I'm not blocked on that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll need to think more about this case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made an attempt. Let me know if it's suitable.

Copy link
Member

@swankjesse swankjesse left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the PR!

But I’m still anxious about how it’ll work on real-world file systems. We probably should confirm it does something reasonable on Mac, Windows, Linux and Android? (Why is this off-by-default on Android?)

okhttp/src/main/kotlin/okhttp3/internal/cache/CacheLock.kt Outdated Show resolved Hide resolved
okhttp/src/main/kotlin/okhttp3/internal/cache/CacheLock.kt Outdated Show resolved Hide resolved
okhttp/src/test/java/okhttp3/CacheLockTest.kt Outdated Show resolved Hide resolved
okhttp/src/test/java/okhttp3/CacheLockTest.kt Outdated Show resolved Hide resolved
okhttp/src/test/java/okhttp3/CacheLockTest.kt Outdated Show resolved Hide resolved
okhttp/src/test/java/okhttp3/CacheLockTest.kt Show resolved Hide resolved
@yschimke
Copy link
Collaborator Author

yschimke commented Apr 3, 2024

Yep, next stage is forcing it through.

I forget why it's not on Android, I'll enable again and see. Or comment for future self.

@yschimke yschimke added windows Windows related jdkversions JDK 8, 17, 19 etc. labels Apr 3, 2024
@yschimke yschimke marked this pull request as draft April 3, 2024 06:01
@yschimke
Copy link
Collaborator Author

yschimke commented Apr 3, 2024

OK, running ok on Android.

Most tests failing validly since they don't close Cache instances with

 DiskLruCacheTest > recoverFromInitializationFailure(Pair) > [3] (FakeFileSystem, true) FAILED
    java.lang.AssertionError: Expected an exception of class java.io.IOException to be thrown, but was java.lang.IllegalStateException: Cache already open at '/cache' in same process
        at org.junit.Assert.fail(Assert.java:89)
        at kotlin.test.junit.JUnitAsserter.fail(JUnitSupport.kt:64)
        at kotlin.test.AssertionsKt__AssertionsImplKt.checkResultIsFailure(AssertionsImpl.kt:30)
        at kotlin.test.AssertionsKt.checkResultIsFailure(Unknown Source)
        at okhttp3.internal.cache.DiskLruCacheTest.recoverFromInitializationFailure(DiskLruCacheTest.kt:140)

        Caused by:
        java.lang.IllegalStateException: Cache already open at '/cache' in same process
            at okhttp3.internal.cache.CacheLock.inMemoryLock(CacheLock.kt:51)
            at okhttp3.internal.cache.CacheLock.openLock(CacheLock.kt:39)
            at okhttp3.internal.cache.DiskLruCache.initialize(DiskLruCache.kt:248)
            at okhttp3.internal.cache.DiskLruCache.get(DiskLruCache.kt:454)
            at okhttp3.internal.cache.DiskLruCacheTest.recoverFromInitializationFailure(DiskLruCacheTest.kt:141)

            Caused by:
            java.lang.Exception: CacheLock(/cache)
                at okhttp3.internal.cache.CacheLock.inMemoryLock(CacheLock.kt:49)
                at okhttp3.internal.cache.CacheLock.openLock(CacheLock.kt:39)
                at okhttp3.internal.cache.DiskLruCache.initialize(DiskLruCache.kt:248)
                at okhttp3.internal.cache.DiskLruCacheTest.createNewCacheWithSize(DiskLruCacheTest.kt:84)
                at okhttp3.internal.cache.DiskLruCacheTest.createNewCache(DiskLruCacheTest.kt:76)
                at okhttp3.internal.cache.DiskLruCacheTest.setUp(DiskLruCacheTest.kt:101)
                at okhttp3.internal.cache.DiskLruCacheTest.recoverFromInitializationFailure(DiskLruCacheTest.kt:124)

I'll fix tonight.

@swankjesse
Copy link
Member

Excellent!

@yschimke yschimke marked this pull request as ready for review April 6, 2024 09:00
@yschimke
Copy link
Collaborator Author

yschimke commented Apr 6, 2024

Working on Windows, but tests are brittle :(

FileOperatorTest > write() FAILED
    java.io.IOException: Failed to delete temp directory C:\Users\RUNNER~1\AppData\Local\Temp\junit13626620294798743510. The following paths could not be deleted (see suppressed exceptions for details): <root>, test
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:395)
        at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:261)
        at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:261)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
android Relates to usage specifically on Android jdkversions JDK 8, 17, 19 etc. windows Windows related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants