Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions gradle/dependencies.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

def versions = [
kotlin : '1.8.20',
coroutines : '1.7.1',
errorProne : '2.5.1',
gjf : '1.7',
nullawayPlugin: '0.8.0',
Expand All @@ -36,12 +37,12 @@ def build = [
errorProneTestHelpers: "com.google.errorprone:error_prone_test_helpers:${versions.errorProne}",
nullAway : 'com.uber.nullaway:nullaway:0.8.0',
gradlePlugins : [
android : '7.4.0',
android : '8.1.4',
kotlin : "${versions.kotlin}",
errorProne: '2.0.1',
nullAway : '1.3.0',
protobuf : '0.8.12',
spotless : '4.3.0',
spotless : '6.25.0',
mavenPublish : '0.27.0',
dokka : '1.6.10',
]
Expand All @@ -64,7 +65,10 @@ def test = [
]

def kotlin = [
stdLibJdk8 : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}"
stdLibJdk8 : "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}",
coroutinesCore: "org.jetbrains.kotlinx:kotlinx-coroutines-core:${versions.coroutines}",
coroutinesAndroid: "org.jetbrains.kotlinx:kotlinx-coroutines-android:${versions.coroutines}",
coroutinesGuava: "org.jetbrains.kotlinx:kotlinx-coroutines-guava:${versions.coroutines}"
]

def external = [
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Tue Dec 01 12:24:16 PST 2020
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
3 changes: 3 additions & 0 deletions simplestore/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ android {
dependencies {
implementation deps.external.findBugs
implementation deps.kotlin.stdLibJdk8
implementation deps.kotlin.coroutinesCore
implementation deps.kotlin.coroutinesAndroid
implementation deps.kotlin.coroutinesGuava
api deps.external.guavaAndroid

testImplementation deps.test.junit
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2020. Uber Technologies
*
* 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 com.uber.simplestore

import java.io.File

interface DirectoryProvider {
fun cacheDirectoryPath(): File
fun filesDirectoryPath(): File
}
36 changes: 36 additions & 0 deletions simplestore/src/main/java/com/uber/simplestore/NamespaceConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2019. Uber Technologies
*
* 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 com.uber.simplestore

/** Configure how the store accesses a namespace. */
sealed class NamespaceConfig {
/**
* Opens a namespace as performance & integrity critical.
*
* Bypasses future memory use optimizations.
*/
object CRITICAL : NamespaceConfig()

/**
* Use the cache directory.
*
* Hides errors due to data corruption by returning a miss.
*/
object CACHE : NamespaceConfig()

/** Default settings. */
object DEFAULT : NamespaceConfig()
}
144 changes: 144 additions & 0 deletions simplestore/src/main/java/com/uber/simplestore/SimpleStore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (C) 2019. Uber Technologies
*
* 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 com.uber.simplestore

import com.google.common.annotations.Beta
import com.google.common.util.concurrent.ListenableFuture
import java.io.Closeable

/** Fast, reliable storage. */
interface SimpleStore : Closeable {
Copy link
Member

Choose a reason for hiding this comment

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

🔥


/**
* Retrieve a byte[]-backed String.
*
* @param key to fetch from
*/
suspend fun getString(key: String): String

/**
* Stores a String as a byte[].
*
* @param key to store to
* @param value to write
*/
suspend fun putString(key: String, value: String?): String

/**
* Retrieve a byte[] from disk.
*
* @param key to read from
* @return value if present, empty array if absent
*/
suspend fun get(key: String): ByteArray

/**
* Stores a byte[] on disk.
*
* @param key to store to
* @param value to store
*/
suspend fun put(key: String, value: ByteArray?): ByteArray

/**
* Removes a key from memory & disk.
*
* @param key to remove
* @return when complete
*/
suspend fun remove(key: String)

/**
* Determine if a key exists in storage.
*
* @param key to check
* @return if key is set
*/
suspend fun contains(key: String): Boolean

/** Delete all keys in this direct namespace. */
suspend fun clear()

/**
* Recursively delete all keys in this scope and child scopes. Fails all outstanding operations on
* the stores.
*/
@Beta
suspend fun deleteAllNow()

/** Fails all outstanding operations then releases the memory cache. */
override fun close()
}

/**
* Extension functions to provide ListenableFuture compatibility for Java callers
*/
fun SimpleStore.getStringFuture(key: String): ListenableFuture<String> {
return kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async { getString(key) }
Copy link
Member

Choose a reason for hiding this comment

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

Agree Java consumers shouldn't need to know anything about coroutines, but concerned about GlobalScope. In the Java version, lifecycle management is handled entirely inside the store impl. I think we could replicate this in Kotlin without exposing coroutines to Java callers by hiding a default scope inside the store impl. For example, defining an internal interface that exposes a scope, having our impl implement it, then having the extension cast

)
}

fun SimpleStore.putStringFuture(key: String, value: String?): ListenableFuture<String> {
return kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async { putString(key, value) }
)
}

fun SimpleStore.getFuture(key: String): ListenableFuture<ByteArray> {
return kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async { get(key) }
)
}

fun SimpleStore.putFuture(key: String, value: ByteArray?): ListenableFuture<ByteArray> {
return kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async { put(key, value) }
)
}

fun SimpleStore.removeFuture(key: String): ListenableFuture<Void> {
return kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async {
remove(key)
null
}
)
}

fun SimpleStore.containsFuture(key: String): ListenableFuture<Boolean> {
return kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async { contains(key) }
)
}

fun SimpleStore.clearFuture(): ListenableFuture<Void> {
return kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async {
clear()
null
}
)
}

fun SimpleStore.deleteAllNowFuture(): ListenableFuture<Void> {
return kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async {
deleteAllNow()
null
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2019. Uber Technologies
*
* 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 com.uber.simplestore

import com.uber.simplestore.executors.StorageExecutors
import java.util.concurrent.Executor

/**
* Configure executors used by SimpleStore.
*
* Set may only be called once, and should be called before any use of stores.
*/
object SimpleStoreConfig {

private val writeLock = Any()

@Volatile
private var ioExecutor: Executor? = null

@Volatile
private var computationExecutor: Executor? = null

fun getIOExecutor(): Executor {
if (ioExecutor == null) {
synchronized(writeLock) {
if (ioExecutor == null) {
ioExecutor = StorageExecutors.ioExecutor()
}
}
}
return ioExecutor!!
}

/**
* Override the executor used for IO operations.
*
* @param executor to set, null unsets.
*/
fun setIOExecutor(executor: Executor?) {
synchronized(writeLock) {
ioExecutor = executor
}
}

fun getComputationExecutor(): Executor {
if (computationExecutor == null) {
synchronized(writeLock) {
if (computationExecutor == null) {
computationExecutor = StorageExecutors.computationExecutor()
}
}
}
return computationExecutor!!
}

/**
* Override the executor used for computation.
*
* @param executor to set, null unsets.
*/
fun setComputationExecutor(executor: Executor?) {
synchronized(writeLock) {
computationExecutor = executor
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2019. Uber Technologies
*
* 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 com.uber.simplestore

import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope

/** Useful wrappers for common storage operations. */
object SimpleStoreHelpers {

/**
* Prefetch specified keys into the memory cache.
*
* @param store to warm
* @param keys to fetch
*/
suspend fun prefetch(store: SimpleStore, vararg keys: String) {
coroutineScope {
keys.map { key ->
async { store.get(key) }
}.awaitAll()
}
}

/**
* Java-compatible version that returns ListenableFuture
*/
fun prefetchFuture(store: SimpleStore, vararg keys: String) =
kotlinx.coroutines.guava.asListenableFuture(
kotlinx.coroutines.GlobalScope.async { prefetch(store, *keys) }
)
}
Loading
Loading