Compare commits
8 Commits
0.1.2-alph
...
graal-nati
Author | SHA1 | Date | |
---|---|---|---|
a4e9d58aa7
|
|||
5fef1b932e
|
|||
5e173dbf62
|
|||
53b24e3d54
|
|||
7d0f24fa58
|
|||
1b6cf1bd96
|
|||
4180df2352
|
|||
c2e388b931
|
@@ -41,7 +41,7 @@ allprojects { subproject ->
|
|||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
modularity.inferModulePath = true
|
modularity.inferModulePath = true
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
languageVersion = JavaLanguageVersion.of(23)
|
||||||
vendor = JvmVendorSpec.ORACLE
|
vendor = JvmVendorSpec.ORACLE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,7 @@ WORKDIR /home/luser
|
|||||||
|
|
||||||
FROM base-release AS release
|
FROM base-release AS release
|
||||||
ADD gbcs-cli-envelope-*.jar gbcs.jar
|
ADD gbcs-cli-envelope-*.jar gbcs.jar
|
||||||
ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar", "server"]
|
ENTRYPOINT ["java", "-XX:+UseZGC", "-XX:+ZGenerational", "-jar", "/home/luser/gbcs.jar", "server"]
|
||||||
|
|
||||||
FROM base-release AS release-memcache
|
FROM base-release AS release-memcache
|
||||||
ADD --chown=luser:luser gbcs-cli-envelope-*.jar gbcs.jar
|
ADD --chown=luser:luser gbcs-cli-envelope-*.jar gbcs.jar
|
||||||
@@ -13,4 +13,4 @@ RUN mkdir plugins
|
|||||||
WORKDIR /home/luser/plugins
|
WORKDIR /home/luser/plugins
|
||||||
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/gbcs-server-memcache*.tar
|
RUN --mount=type=bind,source=.,target=/build/distributions tar -xf /build/distributions/gbcs-server-memcache*.tar
|
||||||
WORKDIR /home/luser
|
WORKDIR /home/luser
|
||||||
ENTRYPOINT ["java", "-jar", "/home/luser/gbcs.jar", "server"]
|
ENTRYPOINT ["java", "-XX:+UseZGC", "-XX:+ZGenerational", "-jar", "/home/luser/gbcs.jar", "server"]
|
||||||
|
@@ -5,6 +5,7 @@ import net.woggioni.gbcs.cli.impl.GbcsCommand
|
|||||||
import net.woggioni.gbcs.cli.impl.commands.BenchmarkCommand
|
import net.woggioni.gbcs.cli.impl.commands.BenchmarkCommand
|
||||||
import net.woggioni.gbcs.cli.impl.commands.ClientCommand
|
import net.woggioni.gbcs.cli.impl.commands.ClientCommand
|
||||||
import net.woggioni.gbcs.cli.impl.commands.GetCommand
|
import net.woggioni.gbcs.cli.impl.commands.GetCommand
|
||||||
|
import net.woggioni.gbcs.cli.impl.commands.HealthCheckCommand
|
||||||
import net.woggioni.gbcs.cli.impl.commands.PasswordHashCommand
|
import net.woggioni.gbcs.cli.impl.commands.PasswordHashCommand
|
||||||
import net.woggioni.gbcs.cli.impl.commands.PutCommand
|
import net.woggioni.gbcs.cli.impl.commands.PutCommand
|
||||||
import net.woggioni.gbcs.cli.impl.commands.ServerCommand
|
import net.woggioni.gbcs.cli.impl.commands.ServerCommand
|
||||||
@@ -24,8 +25,12 @@ class GradleBuildCacheServerCli : GbcsCommand() {
|
|||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(vararg args: String) {
|
fun main(vararg args: String) {
|
||||||
Thread.currentThread().contextClassLoader = GradleBuildCacheServerCli::class.java.classLoader
|
val currentClassLoader = GradleBuildCacheServerCli::class.java.classLoader
|
||||||
GbcsUrlStreamHandlerFactory.install()
|
Thread.currentThread().contextClassLoader = currentClassLoader
|
||||||
|
if(currentClassLoader.javaClass.name == "net.woggioni.envelope.loader.ModuleClassLoader") {
|
||||||
|
//We're running in an envelope jar and custom URL protocols won't work
|
||||||
|
GbcsUrlStreamHandlerFactory.install()
|
||||||
|
}
|
||||||
val log = contextLogger()
|
val log = contextLogger()
|
||||||
val app = Application.builder("gbcs")
|
val app = Application.builder("gbcs")
|
||||||
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
||||||
@@ -44,6 +49,7 @@ class GradleBuildCacheServerCli : GbcsCommand() {
|
|||||||
addSubcommand(BenchmarkCommand())
|
addSubcommand(BenchmarkCommand())
|
||||||
addSubcommand(PutCommand())
|
addSubcommand(PutCommand())
|
||||||
addSubcommand(GetCommand())
|
addSubcommand(GetCommand())
|
||||||
|
addSubcommand(HealthCheckCommand())
|
||||||
})
|
})
|
||||||
System.exit(commandLine.execute(*args))
|
System.exit(commandLine.execute(*args))
|
||||||
}
|
}
|
||||||
|
@@ -46,90 +46,97 @@ class BenchmarkCommand : GbcsCommand() {
|
|||||||
clientCommand.configuration.profiles[profileName]
|
clientCommand.configuration.profiles[profileName]
|
||||||
?: throw IllegalArgumentException("Profile $profileName does not exist in configuration")
|
?: throw IllegalArgumentException("Profile $profileName does not exist in configuration")
|
||||||
}
|
}
|
||||||
val client = GradleBuildCacheClient(profile)
|
GradleBuildCacheClient(profile).use { client ->
|
||||||
|
|
||||||
val entryGenerator = sequence {
|
val entryGenerator = sequence {
|
||||||
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
|
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
|
||||||
while (true) {
|
while (true) {
|
||||||
val key = JWO.bytesToHex(random.nextBytes(16))
|
val key = JWO.bytesToHex(random.nextBytes(16))
|
||||||
val content = random.nextInt().toByte()
|
val content = random.nextInt().toByte()
|
||||||
val value = ByteArray(size, { _ -> content })
|
val value = ByteArray(size, { _ -> content })
|
||||||
yield(key to value)
|
yield(key to value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
log.info {
|
log.info {
|
||||||
"Starting insertion"
|
"Starting insertion"
|
||||||
}
|
}
|
||||||
val entries = let {
|
val entries = let {
|
||||||
val completionCounter = AtomicLong(0)
|
val completionCounter = AtomicLong(0)
|
||||||
val completionQueue = LinkedBlockingQueue<Pair<String, ByteArray>>(numberOfEntries)
|
val completionQueue = LinkedBlockingQueue<Pair<String, ByteArray>>(numberOfEntries)
|
||||||
val start = Instant.now()
|
val start = Instant.now()
|
||||||
val semaphore = Semaphore(profile.maxConnections * 3)
|
val semaphore = Semaphore(profile.maxConnections * 3)
|
||||||
val iterator = entryGenerator.take(numberOfEntries).iterator()
|
val iterator = entryGenerator.take(numberOfEntries).iterator()
|
||||||
while(completionCounter.get() < numberOfEntries) {
|
while (completionCounter.get() < numberOfEntries) {
|
||||||
if(iterator.hasNext()) {
|
if (iterator.hasNext()) {
|
||||||
val entry = iterator.next()
|
val entry = iterator.next()
|
||||||
semaphore.acquire()
|
semaphore.acquire()
|
||||||
val future = client.put(entry.first, entry.second).thenApply { entry }
|
val future = client.put(entry.first, entry.second).thenApply { entry }
|
||||||
future.whenComplete { result, ex ->
|
future.whenComplete { result, ex ->
|
||||||
if (ex != null) {
|
if (ex != null) {
|
||||||
log.error(ex.message, ex)
|
log.error(ex.message, ex)
|
||||||
} else {
|
} else {
|
||||||
completionQueue.put(result)
|
completionQueue.put(result)
|
||||||
|
}
|
||||||
|
semaphore.release()
|
||||||
|
completionCounter.incrementAndGet()
|
||||||
}
|
}
|
||||||
semaphore.release()
|
} else {
|
||||||
completionCounter.incrementAndGet()
|
Thread.sleep(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val inserted = completionQueue.toList()
|
val inserted = completionQueue.toList()
|
||||||
val end = Instant.now()
|
val end = Instant.now()
|
||||||
|
log.info {
|
||||||
|
val elapsed = Duration.between(start, end).toMillis()
|
||||||
|
val opsPerSecond = String.format("%.2f", numberOfEntries.toDouble() / elapsed * 1000)
|
||||||
|
"Insertion rate: $opsPerSecond ops/s"
|
||||||
|
}
|
||||||
|
inserted
|
||||||
|
}
|
||||||
log.info {
|
log.info {
|
||||||
val elapsed = Duration.between(start, end).toMillis()
|
"Inserted ${entries.size} entries"
|
||||||
val opsPerSecond = String.format("%.2f", numberOfEntries.toDouble() / elapsed * 1000)
|
|
||||||
"Insertion rate: $opsPerSecond ops/s"
|
|
||||||
}
|
}
|
||||||
inserted
|
log.info {
|
||||||
}
|
"Starting retrieval"
|
||||||
log.info {
|
}
|
||||||
"Inserted ${entries.size} entries"
|
if (entries.isNotEmpty()) {
|
||||||
}
|
val completionCounter = AtomicLong(0)
|
||||||
log.info {
|
val semaphore = Semaphore(profile.maxConnections * 3)
|
||||||
"Starting retrieval"
|
val start = Instant.now()
|
||||||
}
|
val it = entries.iterator()
|
||||||
if (entries.isNotEmpty()) {
|
while (completionCounter.get() < entries.size) {
|
||||||
val completionCounter = AtomicLong(0)
|
if (it.hasNext()) {
|
||||||
val semaphore = Semaphore(profile.maxConnections * 3)
|
val entry = it.next()
|
||||||
val start = Instant.now()
|
val future = client.get(entry.first).thenApply {
|
||||||
entries.forEach { entry ->
|
if (it == null) {
|
||||||
semaphore.acquire()
|
log.error {
|
||||||
|
"Missing entry for key '${entry.first}'"
|
||||||
val future = client.get(entry.first).thenApply {
|
}
|
||||||
if (it == null) {
|
} else if (!entry.second.contentEquals(it)) {
|
||||||
log.error {
|
log.error {
|
||||||
"Missing entry for key '${entry.first}'"
|
"Retrieved a value different from what was inserted for key '${entry.first}'"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (!entry.second.contentEquals(it)) {
|
future.whenComplete { _, _ ->
|
||||||
log.error {
|
completionCounter.incrementAndGet()
|
||||||
"Retrieved a value different from what was inserted for key '${entry.first}'"
|
semaphore.release()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Thread.sleep(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
future.whenComplete { _, _ ->
|
val end = Instant.now()
|
||||||
completionCounter.incrementAndGet()
|
log.info {
|
||||||
semaphore.release()
|
val elapsed = Duration.between(start, end).toMillis()
|
||||||
|
val opsPerSecond = String.format("%.2f", entries.size.toDouble() / elapsed * 1000)
|
||||||
|
"Retrieval rate: $opsPerSecond ops/s"
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.error("Skipping retrieval benchmark as it was not possible to insert any entry in the cache")
|
||||||
}
|
}
|
||||||
val end = Instant.now()
|
|
||||||
log.info {
|
|
||||||
val elapsed = Duration.between(start, end).toMillis()
|
|
||||||
val opsPerSecond = String.format("%.2f", entries.size.toDouble() / elapsed * 1000)
|
|
||||||
"Retrieval rate: $opsPerSecond ops/s"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.error("Skipping retrieval benchmark as it was not possible to insert any entry in the cache")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
package net.woggioni.gbcs.cli.impl.commands
|
||||||
|
|
||||||
|
import net.woggioni.gbcs.cli.impl.GbcsCommand
|
||||||
|
import net.woggioni.gbcs.client.GradleBuildCacheClient
|
||||||
|
import net.woggioni.gbcs.common.contextLogger
|
||||||
|
import picocli.CommandLine
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
@CommandLine.Command(
|
||||||
|
name = "health",
|
||||||
|
description = ["Check server health"],
|
||||||
|
showDefaultValues = true
|
||||||
|
)
|
||||||
|
class HealthCheckCommand : GbcsCommand() {
|
||||||
|
private val log = contextLogger()
|
||||||
|
|
||||||
|
@CommandLine.Spec
|
||||||
|
private lateinit var spec: CommandLine.Model.CommandSpec
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
val clientCommand = spec.parent().userObject() as ClientCommand
|
||||||
|
val profile = clientCommand.profileName.let { profileName ->
|
||||||
|
clientCommand.configuration.profiles[profileName]
|
||||||
|
?: throw IllegalArgumentException("Profile $profileName does not exist in configuration")
|
||||||
|
}
|
||||||
|
GradleBuildCacheClient(profile).use { client ->
|
||||||
|
val random = Random(SecureRandom.getInstance("NativePRNGNonBlocking").nextLong())
|
||||||
|
val nonce = ByteArray(0xa0)
|
||||||
|
random.nextBytes(nonce)
|
||||||
|
client.healthCheck(nonce).thenApply { value ->
|
||||||
|
if(value == null) {
|
||||||
|
throw IllegalStateException("Empty response from server")
|
||||||
|
}
|
||||||
|
for(i in 0 until nonce.size) {
|
||||||
|
for(j in value.size - nonce.size until nonce.size) {
|
||||||
|
if(nonce[i] != value[j]) {
|
||||||
|
throw IllegalStateException("Server nonce does not match")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -2,6 +2,7 @@ package net.woggioni.gbcs.cli.impl.commands
|
|||||||
|
|
||||||
import net.woggioni.gbcs.api.Configuration
|
import net.woggioni.gbcs.api.Configuration
|
||||||
import net.woggioni.gbcs.cli.impl.GbcsCommand
|
import net.woggioni.gbcs.cli.impl.GbcsCommand
|
||||||
|
import net.woggioni.gbcs.cli.impl.converters.DurationConverter
|
||||||
import net.woggioni.gbcs.common.contextLogger
|
import net.woggioni.gbcs.common.contextLogger
|
||||||
import net.woggioni.gbcs.common.debug
|
import net.woggioni.gbcs.common.debug
|
||||||
import net.woggioni.gbcs.common.info
|
import net.woggioni.gbcs.common.info
|
||||||
@@ -13,6 +14,7 @@ import picocli.CommandLine
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
name = "server",
|
name = "server",
|
||||||
@@ -35,6 +37,14 @@ class ServerCommand(app : Application) : GbcsCommand() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@CommandLine.Option(
|
||||||
|
names = ["-t", "--timeout"],
|
||||||
|
description = ["Exit after the specified time"],
|
||||||
|
paramLabel = "TIMEOUT",
|
||||||
|
converter = [DurationConverter::class]
|
||||||
|
)
|
||||||
|
private var timeout: Duration? = null
|
||||||
|
|
||||||
@CommandLine.Option(
|
@CommandLine.Option(
|
||||||
names = ["-c", "--config-file"],
|
names = ["-c", "--config-file"],
|
||||||
description = ["Read the application configuration from this file"],
|
description = ["Read the application configuration from this file"],
|
||||||
@@ -42,10 +52,6 @@ class ServerCommand(app : Application) : GbcsCommand() {
|
|||||||
)
|
)
|
||||||
private var configurationFile: Path = findConfigurationFile(app, "gbcs-server.xml")
|
private var configurationFile: Path = findConfigurationFile(app, "gbcs-server.xml")
|
||||||
|
|
||||||
val configuration : Configuration by lazy {
|
|
||||||
GradleBuildCacheServer.loadConfiguration(configurationFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (!Files.exists(configurationFile)) {
|
if (!Files.exists(configurationFile)) {
|
||||||
Files.createDirectories(configurationFile.parent)
|
Files.createDirectories(configurationFile.parent)
|
||||||
@@ -61,7 +67,11 @@ class ServerCommand(app : Application) : GbcsCommand() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val server = GradleBuildCacheServer(configuration)
|
val server = GradleBuildCacheServer(configuration)
|
||||||
server.run().use {
|
server.run().use { server ->
|
||||||
|
timeout?.let {
|
||||||
|
Thread.sleep(it)
|
||||||
|
server.shutdown()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -0,0 +1,11 @@
|
|||||||
|
package net.woggioni.gbcs.cli.impl.converters
|
||||||
|
|
||||||
|
import picocli.CommandLine
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
|
|
||||||
|
class DurationConverter : CommandLine.ITypeConverter<Duration> {
|
||||||
|
override fun convert(value: String): Duration {
|
||||||
|
return Duration.parse(value)
|
||||||
|
}
|
||||||
|
}
|
@@ -15,6 +15,4 @@
|
|||||||
<root level="info">
|
<root level="info">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="com.google.code.yanf4j" level="warn"/>
|
|
||||||
<logger name="net.rubyeye.xmemcached" level="warn"/>
|
|
||||||
</configuration>
|
</configuration>
|
@@ -213,6 +213,25 @@ class GradleBuildCacheClient(private val profile: Configuration.Profile) : AutoC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun healthCheck(nonce: ByteArray): CompletableFuture<ByteArray?> {
|
||||||
|
return executeWithRetry {
|
||||||
|
sendRequest(profile.serverURI, HttpMethod.TRACE, nonce)
|
||||||
|
}.thenApply {
|
||||||
|
val status = it.status()
|
||||||
|
if (it.status() != HttpResponseStatus.OK) {
|
||||||
|
throw HttpException(status)
|
||||||
|
} else {
|
||||||
|
it.content()
|
||||||
|
}
|
||||||
|
}.thenApply { maybeByteBuf ->
|
||||||
|
maybeByteBuf?.let {
|
||||||
|
val result = ByteArray(it.readableBytes())
|
||||||
|
it.getBytes(0, result)
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun get(key: String): CompletableFuture<ByteArray?> {
|
fun get(key: String): CompletableFuture<ByteArray?> {
|
||||||
return executeWithRetry {
|
return executeWithRetry {
|
||||||
sendRequest(profile.serverURI.resolve(key), HttpMethod.GET, null)
|
sendRequest(profile.serverURI.resolve(key), HttpMethod.GET, null)
|
||||||
|
@@ -0,0 +1,19 @@
|
|||||||
|
package net.woggioni.gbcs.common
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
class ByteBufOutputStream(private val buf : ByteBuf) : OutputStream() {
|
||||||
|
override fun write(b: Int) {
|
||||||
|
buf.writeByte(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(b: ByteArray, off: Int, len: Int) {
|
||||||
|
buf.writeBytes(b, off, len)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
buf.release()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,7 @@
|
|||||||
|
package net.woggioni.gbcs.common
|
||||||
|
|
||||||
|
class ResourceNotFoundException(msg : String? = null, cause: Throwable? = null) : RuntimeException(msg, cause) {
|
||||||
|
}
|
||||||
|
|
||||||
|
class ModuleNotFoundException(msg : String? = null, cause: Throwable? = null) : RuntimeException(msg, cause) {
|
||||||
|
}
|
@@ -36,13 +36,17 @@ class GbcsUrlStreamHandlerFactory : URLStreamHandlerProvider() {
|
|||||||
private class JpmsHandler : URLStreamHandler() {
|
private class JpmsHandler : URLStreamHandler() {
|
||||||
|
|
||||||
override fun openConnection(u: URL): URLConnection {
|
override fun openConnection(u: URL): URLConnection {
|
||||||
|
val moduleName = u.host
|
||||||
val thisModule = javaClass.module
|
val thisModule = javaClass.module
|
||||||
val sourceModule = Optional.ofNullable(thisModule)
|
val sourceModule =
|
||||||
.map { obj: Module -> obj.layer }
|
thisModule
|
||||||
.flatMap { layer: ModuleLayer ->
|
?.let(Module::getLayer)
|
||||||
val moduleName = u.host
|
?.let { layer: ModuleLayer ->
|
||||||
layer.findModule(moduleName)
|
layer.findModule(moduleName).orElse(null)
|
||||||
}.orElse(thisModule)
|
} ?: if(thisModule.layer == null) {
|
||||||
|
thisModule
|
||||||
|
} else throw ModuleNotFoundException("Module '$moduleName' not found")
|
||||||
|
|
||||||
return JpmsResourceURLConnection(u, sourceModule)
|
return JpmsResourceURLConnection(u, sourceModule)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +57,9 @@ class GbcsUrlStreamHandlerFactory : URLStreamHandlerProvider() {
|
|||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun getInputStream(): InputStream {
|
override fun getInputStream(): InputStream {
|
||||||
return module.getResourceAsStream(getURL().path)
|
val resource = getURL().path
|
||||||
|
return module.getResourceAsStream(resource)
|
||||||
|
?: throw ResourceNotFoundException("Resource '$resource' not found in module '${module.name}'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
56
gbcs-native/build.gradle
Normal file
56
gbcs-native/build.gradle
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java-library'
|
||||||
|
alias catalog.plugins.sambal
|
||||||
|
alias catalog.plugins.graalvm.native.image
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
import net.woggioni.gradle.graalvm.*
|
||||||
|
|
||||||
|
Property<String> mainModuleName = objects.property(String.class)
|
||||||
|
mainModuleName.set('net.woggioni.gbcs.cli')
|
||||||
|
Property<String> mainClassName = objects.property(String.class)
|
||||||
|
mainClassName.set('net.woggioni.gbcs.graal.NativeServer')
|
||||||
|
|
||||||
|
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
||||||
|
options.javaModuleMainClass = mainClassName
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
release {
|
||||||
|
transitive = false
|
||||||
|
canBeConsumed = true
|
||||||
|
canBeResolved = true
|
||||||
|
visible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation catalog.jwo
|
||||||
|
implementation catalog.netty.transport
|
||||||
|
implementation project(':gbcs-server')
|
||||||
|
implementation project(':gbcs-server-memcache')
|
||||||
|
|
||||||
|
// runtimeOnly catalog.slf4j.jdk14
|
||||||
|
runtimeOnly catalog.logback.classic
|
||||||
|
// runtimeOnly catalog.slf4j.simple
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask.class) {
|
||||||
|
mainClass = 'net.woggioni.gbcs.graal.ConfigureNativeServer'
|
||||||
|
// mainModule = mainModuleName
|
||||||
|
systemProperty('logback.configurationFile', 'classpath:net/woggioni/gbcs/graal/logback.xml')
|
||||||
|
systemProperty('io.netty.leakDetectionLevel', 'DISABLED')
|
||||||
|
modularity.inferModulePath = false
|
||||||
|
mergeConfiguration = true
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named(NativeImagePlugin.NATIVE_IMAGE_TASK_NAME, NativeImageTask) {
|
||||||
|
mainClass = mainClassName
|
||||||
|
// mainModule = mainModuleName
|
||||||
|
useMusl = true
|
||||||
|
buildStaticImage = true
|
||||||
|
linkAtBuildTime = false
|
||||||
|
}
|
||||||
|
|
6
gbcs-native/native-image/jni-config.json
Normal file
6
gbcs-native/native-image/jni-config.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name":"java.lang.Boolean",
|
||||||
|
"methods":[{"name":"getBoolean","parameterTypes":["java.lang.String"] }]
|
||||||
|
}
|
||||||
|
]
|
2
gbcs-native/native-image/native-image.properties
Normal file
2
gbcs-native/native-image/native-image.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Args=-O3 --gc=G1 --enable-url-protocols=jpms --initialize-at-run-time=io.netty --initialize-at-build-time=net.woggioni.gbcs.common.GbcsUrlStreamHandlerFactory,net.woggioni.gbcs.common.GbcsUrlStreamHandlerFactory$JpmsHandler,org.apache.logging.slf4j.SLF4JLogger
|
||||||
|
#-H:TraceClassInitialization=io.netty.handler.ssl.BouncyCastleAlpnSslUtils
|
8
gbcs-native/native-image/predefined-classes-config.json
Normal file
8
gbcs-native/native-image/predefined-classes-config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type":"agent-extracted",
|
||||||
|
"classes":[
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
2
gbcs-native/native-image/proxy-config.json
Normal file
2
gbcs-native/native-image/proxy-config.json
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[
|
||||||
|
]
|
1278
gbcs-native/native-image/reachability-metadata.json
Normal file
1278
gbcs-native/native-image/reachability-metadata.json
Normal file
File diff suppressed because it is too large
Load Diff
330
gbcs-native/native-image/reflect-config.json
Normal file
330
gbcs-native/native-image/reflect-config.json
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.classic.encoder.PatternLayoutEncoder",
|
||||||
|
"queryAllPublicMethods":true,
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.classic.joran.SerializedModelConfigurator",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.classic.util.DefaultJoranConfigurator",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.core.ConsoleAppender",
|
||||||
|
"queryAllPublicMethods":true,
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setTarget","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.core.OutputStreamAppender",
|
||||||
|
"methods":[{"name":"setEncoder","parameterTypes":["ch.qos.logback.core.encoder.Encoder"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.core.encoder.Encoder",
|
||||||
|
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.core.encoder.LayoutWrappingEncoder",
|
||||||
|
"methods":[{"name":"setParent","parameterTypes":["ch.qos.logback.core.spi.ContextAware"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.core.pattern.PatternLayoutEncoderBase",
|
||||||
|
"methods":[{"name":"setPattern","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"ch.qos.logback.core.spi.ContextAware",
|
||||||
|
"methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.crypto.provider.AESCipher$General",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.crypto.provider.ARCFOURCipher",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.crypto.provider.DESCipher",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.crypto.provider.DESedeCipher",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.org.apache.xerces.internal.impl.dv.xs.ExtendedSchemaDVFactoryImpl",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.org.apache.xerces.internal.impl.dv.xs.SchemaDVFactoryImpl",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.bootstrap.ServerBootstrap$1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor",
|
||||||
|
"methods":[{"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.buffer.AbstractByteBufAllocator",
|
||||||
|
"queryAllDeclaredMethods":true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.AbstractChannelHandlerContext",
|
||||||
|
"fields":[{"name":"handlerState"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.ChannelInboundHandlerAdapter",
|
||||||
|
"methods":[{"name":"channelActive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelUnregistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelWritabilityChanged","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.ChannelInitializer",
|
||||||
|
"methods":[{"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.ChannelOutboundBuffer",
|
||||||
|
"fields":[{"name":"totalPendingSize"}, {"name":"unwritable"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.DefaultChannelConfig",
|
||||||
|
"fields":[{"name":"autoRead"}, {"name":"writeBufferWaterMark"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.DefaultChannelPipeline",
|
||||||
|
"fields":[{"name":"estimatorHandle"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.DefaultChannelPipeline$HeadContext",
|
||||||
|
"methods":[{"name":"bind","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"channelActive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelUnregistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelWritabilityChanged","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"close","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"connect","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.net.SocketAddress","java.net.SocketAddress","io.netty.channel.ChannelPromise"] }, {"name":"deregister","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"disconnect","parameterTypes":["io.netty.channel.ChannelHandlerContext","io.netty.channel.ChannelPromise"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"flush","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"read","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"write","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object","io.netty.channel.ChannelPromise"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.DefaultChannelPipeline$TailContext",
|
||||||
|
"methods":[{"name":"channelActive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelInactive","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRead","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }, {"name":"channelReadComplete","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelRegistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelUnregistered","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"channelWritabilityChanged","parameterTypes":["io.netty.channel.ChannelHandlerContext"] }, {"name":"exceptionCaught","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Throwable"] }, {"name":"userEventTriggered","parameterTypes":["io.netty.channel.ChannelHandlerContext","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.channel.socket.nio.NioServerSocketChannel",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.util.DefaultAttributeMap",
|
||||||
|
"fields":[{"name":"attributes"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.util.concurrent.DefaultPromise",
|
||||||
|
"fields":[{"name":"result"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.util.concurrent.SingleThreadEventExecutor",
|
||||||
|
"fields":[{"name":"state"}, {"name":"threadProperties"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields",
|
||||||
|
"fields":[{"name":"producerLimit"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueConsumerFields",
|
||||||
|
"fields":[{"name":"consumerIndex"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"io.netty.util.internal.shaded.org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields",
|
||||||
|
"fields":[{"name":"producerIndex"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.io.FilePermission"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.lang.ProcessHandle",
|
||||||
|
"methods":[{"name":"current","parameterTypes":[] }, {"name":"pid","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.lang.RuntimePermission"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.lang.Thread",
|
||||||
|
"fields":[{"name":"threadLocalRandomProbe"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.net.NetPermission"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.net.SocketPermission"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.net.URLPermission",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.String","java.lang.String"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.nio.Bits",
|
||||||
|
"fields":[{"name":"MAX_MEMORY"}, {"name":"UNALIGNED"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.nio.Buffer",
|
||||||
|
"fields":[{"name":"address"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.nio.ByteBuffer",
|
||||||
|
"methods":[{"name":"alignedSlice","parameterTypes":["int"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.nio.DirectByteBuffer",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["long","long"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.nio.channels.spi.SelectorProvider",
|
||||||
|
"methods":[{"name":"openServerSocketChannel","parameterTypes":["java.net.ProtocolFamily"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.security.AlgorithmParametersSpi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.security.AllPermission"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.security.KeyStoreSpi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.security.SecureRandomParameters"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.security.SecurityPermission"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.util.PropertyPermission"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.util.concurrent.ForkJoinTask",
|
||||||
|
"fields":[{"name":"aux"}, {"name":"status"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.util.concurrent.atomic.AtomicBoolean",
|
||||||
|
"fields":[{"name":"value"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.util.concurrent.atomic.AtomicReference",
|
||||||
|
"fields":[{"name":"value"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"java.util.concurrent.atomic.Striped64",
|
||||||
|
"fields":[{"name":"base"}, {"name":"cellsBusy"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"javax.security.auth.x500.X500Principal",
|
||||||
|
"fields":[{"name":"thisX500Name"}],
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"javax.smartcardio.CardPermission"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"jdk.internal.misc.Unsafe",
|
||||||
|
"methods":[{"name":"getUnsafe","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.misc.Unsafe",
|
||||||
|
"fields":[{"name":"theUnsafe"}],
|
||||||
|
"methods":[{"name":"copyMemory","parameterTypes":["java.lang.Object","long","java.lang.Object","long","long"] }, {"name":"getAndAddLong","parameterTypes":["java.lang.Object","long","long"] }, {"name":"getAndSetObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] }, {"name":"invokeCleaner","parameterTypes":["java.nio.ByteBuffer"] }, {"name":"storeFence","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.nio.ch.SelectorImpl",
|
||||||
|
"fields":[{"name":"publicSelectedKeys"}, {"name":"selectedKeys"}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.pkcs12.PKCS12KeyStore",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.provider.JavaKeyStore$JKS",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.provider.NativePRNG",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"<init>","parameterTypes":["java.security.SecureRandomParameters"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.provider.SHA",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.provider.X509Factory",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.rsa.RSAKeyFactory$Legacy",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.ssl.SSLContextImpl$DefaultSSLContext",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":[] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.AuthorityInfoAccessExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.AuthorityKeyIdentifierExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.BasicConstraintsExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.CRLDistributionPointsExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.CertificatePoliciesExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.KeyUsageExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.NetscapeCertTypeExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.PrivateKeyUsageExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.SubjectAlternativeNameExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name":"sun.security.x509.SubjectKeyIdentifierExtension",
|
||||||
|
"methods":[{"name":"<init>","parameterTypes":["java.lang.Boolean","java.lang.Object"] }]
|
||||||
|
}
|
||||||
|
]
|
40
gbcs-native/native-image/resource-config.json
Normal file
40
gbcs-native/native-image/resource-config.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"resources":{
|
||||||
|
"includes":[{
|
||||||
|
"pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\QMETA-INF/services/java.lang.System$LoggerFinder\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\QMETA-INF/services/javax.xml.parsers.DocumentBuilderFactory\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\QMETA-INF/services/net.woggioni.gbcs.api.CacheProvider\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\Qclasspath:net/woggioni/gbcs/graal/logback.xml\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\Qlogback-test.scmo\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\Qlogback.scmo\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\Qnet/woggioni/gbcs/graal/logback.xml\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\Qnet/woggioni/gbcs/server/memcache/schema/gbcs-memcache.xsd\\E"
|
||||||
|
}, {
|
||||||
|
"pattern":"\\Qnet/woggioni/gbcs/server/schema/gbcs.xsd\\E"
|
||||||
|
}]},
|
||||||
|
"bundles":[{
|
||||||
|
"name":"com.sun.org.apache.xerces.internal.impl.xpath.regex.message",
|
||||||
|
"locales":[""]
|
||||||
|
}, {
|
||||||
|
"name": "com.sun.org.apache.xerces.internal.impl.msg.XMLSchemaMessages"
|
||||||
|
}]
|
||||||
|
}
|
8
gbcs-native/native-image/serialization-config.json
Normal file
8
gbcs-native/native-image/serialization-config.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"types":[
|
||||||
|
],
|
||||||
|
"lambdaCapturingTypes":[
|
||||||
|
],
|
||||||
|
"proxies":[
|
||||||
|
]
|
||||||
|
}
|
@@ -0,0 +1,10 @@
|
|||||||
|
package net.woggioni.gbcs.graal;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class ConfigureNativeServer {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
NativeServer.run(Duration.ofSeconds(60));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,45 @@
|
|||||||
|
package net.woggioni.gbcs.graal;
|
||||||
|
|
||||||
|
import net.woggioni.gbcs.server.GradleBuildCacheServer;
|
||||||
|
import net.woggioni.jwo.Application;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
public class NativeServer {
|
||||||
|
|
||||||
|
private static Path findConfigurationFile(Application app, String fileName) {
|
||||||
|
final var confDir = app.computeConfigurationDirectory();
|
||||||
|
final var configurationFile = confDir.resolve(fileName);
|
||||||
|
return configurationFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void run(Duration timeout) throws Exception {
|
||||||
|
final var app = Application.builder("gbcs")
|
||||||
|
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
||||||
|
.configurationDirectoryPropertyKey("net.woggioni.gbcs.conf.dir")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final var configurationFile = findConfigurationFile(app, "gbcs-server.xml");
|
||||||
|
final var cfg = GradleBuildCacheServer.Companion.loadConfiguration(configurationFile);
|
||||||
|
try(final var handle = new GradleBuildCacheServer(cfg).run()) {
|
||||||
|
if(timeout != null) {
|
||||||
|
Thread.sleep(timeout);
|
||||||
|
handle.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setPropertyIfNotPresent(String key, String value) {
|
||||||
|
final var previousValue = System.getProperty(key);
|
||||||
|
if(previousValue == null) {
|
||||||
|
System.setProperty(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
setPropertyIfNotPresent("logback.configurationFile", "net/woggioni/gbcs/graal/logback.xml");
|
||||||
|
setPropertyIfNotPresent("io.netty.leakDetectionLevel", "DISABLED");
|
||||||
|
run(null);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE configuration>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
|
||||||
|
<import class="ch.qos.logback.core.ConsoleAppender"/>
|
||||||
|
|
||||||
|
<appender name="console" class="ConsoleAppender">
|
||||||
|
<target>System.err</target>
|
||||||
|
<encoder class="PatternLayoutEncoder">
|
||||||
|
<pattern>%d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="info">
|
||||||
|
<appender-ref ref="console"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
@@ -24,6 +24,7 @@ import io.netty.handler.codec.memcache.binary.FullBinaryMemcacheRequest
|
|||||||
import io.netty.handler.codec.memcache.binary.FullBinaryMemcacheResponse
|
import io.netty.handler.codec.memcache.binary.FullBinaryMemcacheResponse
|
||||||
import io.netty.util.concurrent.GenericFutureListener
|
import io.netty.util.concurrent.GenericFutureListener
|
||||||
import net.woggioni.gbcs.common.ByteBufInputStream
|
import net.woggioni.gbcs.common.ByteBufInputStream
|
||||||
|
import net.woggioni.gbcs.common.ByteBufOutputStream
|
||||||
import net.woggioni.gbcs.common.GBCS.digest
|
import net.woggioni.gbcs.common.GBCS.digest
|
||||||
import net.woggioni.gbcs.common.HostAndPort
|
import net.woggioni.gbcs.common.HostAndPort
|
||||||
import net.woggioni.gbcs.common.contextLogger
|
import net.woggioni.gbcs.common.contextLogger
|
||||||
@@ -114,13 +115,14 @@ class MemcacheClient(private val cfg: MemcacheCacheConfiguration) : AutoCloseabl
|
|||||||
val channel = channelFuture.now
|
val channel = channelFuture.now
|
||||||
val pipeline = channel.pipeline()
|
val pipeline = channel.pipeline()
|
||||||
channel.pipeline()
|
channel.pipeline()
|
||||||
.addLast("handler", object : SimpleChannelInboundHandler<FullBinaryMemcacheResponse>() {
|
.addLast("client-handler", object : SimpleChannelInboundHandler<FullBinaryMemcacheResponse>() {
|
||||||
override fun channelRead0(
|
override fun channelRead0(
|
||||||
ctx: ChannelHandlerContext,
|
ctx: ChannelHandlerContext,
|
||||||
msg: FullBinaryMemcacheResponse
|
msg: FullBinaryMemcacheResponse
|
||||||
) {
|
) {
|
||||||
pipeline.removeLast()
|
pipeline.removeLast()
|
||||||
pool.release(channel)
|
pool.release(channel)
|
||||||
|
msg.touch("The method's caller must remember to release this")
|
||||||
response.complete(msg.retain())
|
response.complete(msg.retain())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +137,7 @@ class MemcacheClient(private val cfg: MemcacheCacheConfiguration) : AutoCloseabl
|
|||||||
response.completeExceptionally(ex)
|
response.completeExceptionally(ex)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
request.touch()
|
||||||
channel.writeAndFlush(request)
|
channel.writeAndFlush(request)
|
||||||
} else {
|
} else {
|
||||||
response.completeExceptionally(channelFuture.cause())
|
response.completeExceptionally(channelFuture.cause())
|
||||||
@@ -161,28 +164,35 @@ class MemcacheClient(private val cfg: MemcacheCacheConfiguration) : AutoCloseabl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sendRequest(request).thenApply { response ->
|
return sendRequest(request).thenApply { response ->
|
||||||
when (val status = response.status()) {
|
try {
|
||||||
BinaryMemcacheResponseStatus.SUCCESS -> {
|
when (val status = response.status()) {
|
||||||
val compressionMode = cfg.compressionMode
|
BinaryMemcacheResponseStatus.SUCCESS -> {
|
||||||
val content = response.content()
|
val compressionMode = cfg.compressionMode
|
||||||
if (compressionMode != null) {
|
val content = response.content().retain()
|
||||||
when (compressionMode) {
|
content.touch()
|
||||||
MemcacheCacheConfiguration.CompressionMode.GZIP -> {
|
if (compressionMode != null) {
|
||||||
GZIPInputStream(ByteBufInputStream(content))
|
when (compressionMode) {
|
||||||
}
|
MemcacheCacheConfiguration.CompressionMode.GZIP -> {
|
||||||
|
GZIPInputStream(ByteBufInputStream(content))
|
||||||
|
}
|
||||||
|
|
||||||
MemcacheCacheConfiguration.CompressionMode.DEFLATE -> {
|
MemcacheCacheConfiguration.CompressionMode.DEFLATE -> {
|
||||||
InflaterInputStream(ByteBufInputStream(content))
|
InflaterInputStream(ByteBufInputStream(content))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
} else {
|
ByteBufInputStream(content)
|
||||||
ByteBufInputStream(content)
|
}.let(Channels::newChannel)
|
||||||
}.let(Channels::newChannel)
|
}
|
||||||
|
|
||||||
|
BinaryMemcacheResponseStatus.KEY_ENOENT -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw MemcacheException(status)
|
||||||
}
|
}
|
||||||
BinaryMemcacheResponseStatus.KEY_ENOENT -> {
|
} finally {
|
||||||
null
|
response.release()
|
||||||
}
|
|
||||||
else -> throw MemcacheException(status)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -197,16 +207,18 @@ class MemcacheClient(private val cfg: MemcacheCacheConfiguration) : AutoCloseabl
|
|||||||
extras.writeInt(0)
|
extras.writeInt(0)
|
||||||
extras.writeInt(encodeExpiry(expiry))
|
extras.writeInt(encodeExpiry(expiry))
|
||||||
val compressionMode = cfg.compressionMode
|
val compressionMode = cfg.compressionMode
|
||||||
|
content.retain()
|
||||||
val payload = if (compressionMode != null) {
|
val payload = if (compressionMode != null) {
|
||||||
val inputStream = ByteBufInputStream(Unpooled.wrappedBuffer(content))
|
val inputStream = ByteBufInputStream(content)
|
||||||
val baos = ByteArrayOutputStream()
|
val buf = content.alloc().buffer()
|
||||||
|
buf.retain()
|
||||||
val outputStream = when (compressionMode) {
|
val outputStream = when (compressionMode) {
|
||||||
MemcacheCacheConfiguration.CompressionMode.GZIP -> {
|
MemcacheCacheConfiguration.CompressionMode.GZIP -> {
|
||||||
GZIPOutputStream(baos)
|
GZIPOutputStream(ByteBufOutputStream(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
MemcacheCacheConfiguration.CompressionMode.DEFLATE -> {
|
MemcacheCacheConfiguration.CompressionMode.DEFLATE -> {
|
||||||
DeflaterOutputStream(baos, Deflater(Deflater.DEFAULT_COMPRESSION, false))
|
DeflaterOutputStream(ByteBufOutputStream(buf), Deflater(Deflater.DEFAULT_COMPRESSION, false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inputStream.use { i ->
|
inputStream.use { i ->
|
||||||
@@ -214,7 +226,7 @@ class MemcacheClient(private val cfg: MemcacheCacheConfiguration) : AutoCloseabl
|
|||||||
JWO.copy(i, o)
|
JWO.copy(i, o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Unpooled.wrappedBuffer(baos.toByteArray())
|
buf
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
@@ -224,9 +236,13 @@ class MemcacheClient(private val cfg: MemcacheCacheConfiguration) : AutoCloseabl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sendRequest(request).thenApply { response ->
|
return sendRequest(request).thenApply { response ->
|
||||||
when(val status = response.status()) {
|
try {
|
||||||
BinaryMemcacheResponseStatus.SUCCESS -> null
|
when (val status = response.status()) {
|
||||||
else -> throw MemcacheException(status)
|
BinaryMemcacheResponseStatus.SUCCESS -> null
|
||||||
|
else -> throw MemcacheException(status)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
response.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
package net.woggioni.gbcs.server.cache
|
package net.woggioni.gbcs.server.cache
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf
|
import io.netty.buffer.ByteBuf
|
||||||
import io.netty.buffer.Unpooled
|
|
||||||
import net.woggioni.gbcs.api.Cache
|
import net.woggioni.gbcs.api.Cache
|
||||||
import net.woggioni.gbcs.common.ByteBufInputStream
|
import net.woggioni.gbcs.common.ByteBufInputStream
|
||||||
|
import net.woggioni.gbcs.common.ByteBufOutputStream
|
||||||
import net.woggioni.gbcs.common.GBCS.digestString
|
import net.woggioni.gbcs.common.GBCS.digestString
|
||||||
|
import net.woggioni.gbcs.common.contextLogger
|
||||||
import net.woggioni.jwo.JWO
|
import net.woggioni.jwo.JWO
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.nio.channels.Channels
|
import java.nio.channels.Channels
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@@ -14,6 +14,7 @@ import java.time.Instant
|
|||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.PriorityBlockingQueue
|
import java.util.concurrent.PriorityBlockingQueue
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import java.util.zip.Deflater
|
import java.util.zip.Deflater
|
||||||
import java.util.zip.DeflaterOutputStream
|
import java.util.zip.DeflaterOutputStream
|
||||||
import java.util.zip.Inflater
|
import java.util.zip.Inflater
|
||||||
@@ -21,11 +22,18 @@ import java.util.zip.InflaterInputStream
|
|||||||
|
|
||||||
class InMemoryCache(
|
class InMemoryCache(
|
||||||
val maxAge: Duration,
|
val maxAge: Duration,
|
||||||
|
val maxSize: Long,
|
||||||
val digestAlgorithm: String?,
|
val digestAlgorithm: String?,
|
||||||
val compressionEnabled: Boolean,
|
val compressionEnabled: Boolean,
|
||||||
val compressionLevel: Int
|
val compressionLevel: Int
|
||||||
) : Cache {
|
) : Cache {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
private val log = contextLogger()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val size = AtomicLong()
|
||||||
private val map = ConcurrentHashMap<String, ByteBuf>()
|
private val map = ConcurrentHashMap<String, ByteBuf>()
|
||||||
|
|
||||||
private class RemovalQueueElement(val key: String, val value : ByteBuf, val expiry : Instant) : Comparable<RemovalQueueElement> {
|
private class RemovalQueueElement(val key: String, val value : ByteBuf, val expiry : Instant) : Comparable<RemovalQueueElement> {
|
||||||
@@ -38,9 +46,17 @@ class InMemoryCache(
|
|||||||
private val garbageCollector = Thread {
|
private val garbageCollector = Thread {
|
||||||
while(true) {
|
while(true) {
|
||||||
val el = removalQueue.take()
|
val el = removalQueue.take()
|
||||||
|
val buf = el.value
|
||||||
val now = Instant.now()
|
val now = Instant.now()
|
||||||
if(now > el.expiry) {
|
if(now > el.expiry) {
|
||||||
map.remove(el.key, el.value)
|
val removed = map.remove(el.key, buf)
|
||||||
|
if(removed) {
|
||||||
|
updateSizeAfterRemoval(buf)
|
||||||
|
//Decrease the reference count for map
|
||||||
|
buf.release()
|
||||||
|
}
|
||||||
|
//Decrease the reference count for removalQueue
|
||||||
|
buf.release()
|
||||||
} else {
|
} else {
|
||||||
removalQueue.put(el)
|
removalQueue.put(el)
|
||||||
Thread.sleep(minOf(Duration.between(now, el.expiry), Duration.ofSeconds(1)))
|
Thread.sleep(minOf(Duration.between(now, el.expiry), Duration.ofSeconds(1)))
|
||||||
@@ -50,6 +66,28 @@ class InMemoryCache(
|
|||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeEldest() : Long {
|
||||||
|
while(true) {
|
||||||
|
val el = removalQueue.take()
|
||||||
|
val buf = el.value
|
||||||
|
val removed = map.remove(el.key, buf)
|
||||||
|
//Decrease the reference count for removalQueue
|
||||||
|
buf.release()
|
||||||
|
if(removed) {
|
||||||
|
val newSize = updateSizeAfterRemoval(buf)
|
||||||
|
//Decrease the reference count for map
|
||||||
|
buf.release()
|
||||||
|
return newSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateSizeAfterRemoval(removed: ByteBuf) : Long {
|
||||||
|
return size.updateAndGet { currentSize : Long ->
|
||||||
|
currentSize - removed.readableBytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
running = false
|
running = false
|
||||||
garbageCollector.join()
|
garbageCollector.join()
|
||||||
@@ -64,11 +102,13 @@ class InMemoryCache(
|
|||||||
).let { digest ->
|
).let { digest ->
|
||||||
map[digest]
|
map[digest]
|
||||||
?.let { value ->
|
?.let { value ->
|
||||||
|
val copy = value.retainedDuplicate()
|
||||||
|
copy.touch("This has to be released by the caller of the cache")
|
||||||
if (compressionEnabled) {
|
if (compressionEnabled) {
|
||||||
val inflater = Inflater()
|
val inflater = Inflater()
|
||||||
Channels.newChannel(InflaterInputStream(ByteBufInputStream(value), inflater))
|
Channels.newChannel(InflaterInputStream(ByteBufInputStream(copy), inflater))
|
||||||
} else {
|
} else {
|
||||||
Channels.newChannel(ByteBufInputStream(value))
|
Channels.newChannel(ByteBufInputStream(copy))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.let {
|
}.let {
|
||||||
@@ -81,18 +121,29 @@ class InMemoryCache(
|
|||||||
?.let { md ->
|
?.let { md ->
|
||||||
digestString(key.toByteArray(), md)
|
digestString(key.toByteArray(), md)
|
||||||
} ?: key).let { digest ->
|
} ?: key).let { digest ->
|
||||||
|
content.retain()
|
||||||
val value = if (compressionEnabled) {
|
val value = if (compressionEnabled) {
|
||||||
val deflater = Deflater(compressionLevel)
|
val deflater = Deflater(compressionLevel)
|
||||||
val baos = ByteArrayOutputStream()
|
val buf = content.alloc().buffer()
|
||||||
DeflaterOutputStream(baos, deflater).use { stream ->
|
buf.retain()
|
||||||
JWO.copy(ByteBufInputStream(content), stream)
|
DeflaterOutputStream(ByteBufOutputStream(buf), deflater).use { outputStream ->
|
||||||
|
ByteBufInputStream(content).use { inputStream ->
|
||||||
|
JWO.copy(inputStream, outputStream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Unpooled.wrappedBuffer(baos.toByteArray())
|
buf
|
||||||
} else {
|
} else {
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
map[digest] = value
|
val old = map.put(digest, value)
|
||||||
removalQueue.put(RemovalQueueElement(digest, value, Instant.now().plus(maxAge)))
|
val delta = value.readableBytes() - (old?.readableBytes() ?: 0)
|
||||||
|
var newSize = size.updateAndGet { currentSize : Long ->
|
||||||
|
currentSize + delta
|
||||||
|
}
|
||||||
|
removalQueue.put(RemovalQueueElement(digest, value.retain(), Instant.now().plus(maxAge)))
|
||||||
|
while(newSize > maxSize) {
|
||||||
|
newSize = removeEldest()
|
||||||
|
}
|
||||||
}.let {
|
}.let {
|
||||||
CompletableFuture.completedFuture<Void>(null)
|
CompletableFuture.completedFuture<Void>(null)
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,14 @@ import java.time.Duration
|
|||||||
|
|
||||||
data class InMemoryCacheConfiguration(
|
data class InMemoryCacheConfiguration(
|
||||||
val maxAge: Duration,
|
val maxAge: Duration,
|
||||||
|
val maxSize: Long,
|
||||||
val digestAlgorithm : String?,
|
val digestAlgorithm : String?,
|
||||||
val compressionEnabled: Boolean,
|
val compressionEnabled: Boolean,
|
||||||
val compressionLevel: Int,
|
val compressionLevel: Int,
|
||||||
) : Configuration.Cache {
|
) : Configuration.Cache {
|
||||||
override fun materialize() = InMemoryCache(
|
override fun materialize() = InMemoryCache(
|
||||||
maxAge,
|
maxAge,
|
||||||
|
maxSize,
|
||||||
digestAlgorithm,
|
digestAlgorithm,
|
||||||
compressionEnabled,
|
compressionEnabled,
|
||||||
compressionLevel
|
compressionLevel
|
||||||
|
@@ -21,6 +21,9 @@ class InMemoryCacheProvider : CacheProvider<InMemoryCacheConfiguration> {
|
|||||||
val maxAge = el.renderAttribute("max-age")
|
val maxAge = el.renderAttribute("max-age")
|
||||||
?.let(Duration::parse)
|
?.let(Duration::parse)
|
||||||
?: Duration.ofDays(1)
|
?: Duration.ofDays(1)
|
||||||
|
val maxSize = el.renderAttribute("max-size")
|
||||||
|
?.let(java.lang.Long::decode)
|
||||||
|
?: 0x1000000
|
||||||
val enableCompression = el.renderAttribute("enable-compression")
|
val enableCompression = el.renderAttribute("enable-compression")
|
||||||
?.let(String::toBoolean)
|
?.let(String::toBoolean)
|
||||||
?: true
|
?: true
|
||||||
@@ -31,6 +34,7 @@ class InMemoryCacheProvider : CacheProvider<InMemoryCacheConfiguration> {
|
|||||||
|
|
||||||
return InMemoryCacheConfiguration(
|
return InMemoryCacheConfiguration(
|
||||||
maxAge,
|
maxAge,
|
||||||
|
maxSize,
|
||||||
digestAlgorithm,
|
digestAlgorithm,
|
||||||
enableCompression,
|
enableCompression,
|
||||||
compressionLevel
|
compressionLevel
|
||||||
@@ -43,6 +47,7 @@ class InMemoryCacheProvider : CacheProvider<InMemoryCacheConfiguration> {
|
|||||||
val prefix = doc.lookupPrefix(GBCS.GBCS_NAMESPACE_URI)
|
val prefix = doc.lookupPrefix(GBCS.GBCS_NAMESPACE_URI)
|
||||||
attr("xs:type", "${prefix}:inMemoryCacheType", GBCS.XML_SCHEMA_NAMESPACE_URI)
|
attr("xs:type", "${prefix}:inMemoryCacheType", GBCS.XML_SCHEMA_NAMESPACE_URI)
|
||||||
attr("max-age", maxAge.toString())
|
attr("max-age", maxAge.toString())
|
||||||
|
attr("max-size", maxSize.toString())
|
||||||
digestAlgorithm?.let { digestAlgorithm ->
|
digestAlgorithm?.let { digestAlgorithm ->
|
||||||
attr("digest", digestAlgorithm)
|
attr("digest", digestAlgorithm)
|
||||||
}
|
}
|
||||||
|
@@ -57,16 +57,29 @@ class ServerHandler(private val cache: Cache, private val serverPrefix: Path) :
|
|||||||
response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED)
|
response.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED)
|
||||||
}
|
}
|
||||||
ctx.write(response)
|
ctx.write(response)
|
||||||
val content : Any = when (channel) {
|
when (channel) {
|
||||||
is FileChannel -> DefaultFileRegion(channel, 0, channel.size())
|
is FileChannel -> {
|
||||||
else -> ChunkedNioStream(channel)
|
val content = DefaultFileRegion(channel, 0, channel.size())
|
||||||
}
|
if (keepAlive) {
|
||||||
if (keepAlive) {
|
ctx.write(content)
|
||||||
ctx.write(content)
|
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT.retainedDuplicate())
|
||||||
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT.retainedDuplicate())
|
} else {
|
||||||
} else {
|
ctx.writeAndFlush(content)
|
||||||
ctx.writeAndFlush(content)
|
.addListener(ChannelFutureListener.CLOSE)
|
||||||
.addListener(ChannelFutureListener.CLOSE)
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val content = ChunkedNioStream(channel)
|
||||||
|
if (keepAlive) {
|
||||||
|
ctx.write(content).addListener {
|
||||||
|
content.close()
|
||||||
|
}
|
||||||
|
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT.retainedDuplicate())
|
||||||
|
} else {
|
||||||
|
ctx.writeAndFlush(content)
|
||||||
|
.addListener(ChannelFutureListener.CLOSE)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
@@ -94,7 +107,7 @@ class ServerHandler(private val cache: Cache, private val serverPrefix: Path) :
|
|||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Added value for key '$key' to build cache"
|
"Added value for key '$key' to build cache"
|
||||||
}
|
}
|
||||||
cache.put(key, msg.content().retain()).thenRun {
|
cache.put(key, msg.content()).thenRun {
|
||||||
val response = DefaultFullHttpResponse(
|
val response = DefaultFullHttpResponse(
|
||||||
msg.protocolVersion(), HttpResponseStatus.CREATED,
|
msg.protocolVersion(), HttpResponseStatus.CREATED,
|
||||||
Unpooled.copiedBuffer(key.toByteArray())
|
Unpooled.copiedBuffer(key.toByteArray())
|
||||||
|
@@ -52,6 +52,7 @@
|
|||||||
<xs:complexContent>
|
<xs:complexContent>
|
||||||
<xs:extension base="gbcs:cacheType">
|
<xs:extension base="gbcs:cacheType">
|
||||||
<xs:attribute name="max-age" type="xs:duration" default="P1D"/>
|
<xs:attribute name="max-age" type="xs:duration" default="P1D"/>
|
||||||
|
<xs:attribute name="max-size" type="xs:token" default="0x1000000"/>
|
||||||
<xs:attribute name="digest" type="xs:token" default="MD5"/>
|
<xs:attribute name="digest" type="xs:token" default="MD5"/>
|
||||||
<xs:attribute name="enable-compression" type="xs:boolean" default="true"/>
|
<xs:attribute name="enable-compression" type="xs:boolean" default="true"/>
|
||||||
<xs:attribute name="compression-level" type="xs:byte" default="-1"/>
|
<xs:attribute name="compression-level" type="xs:byte" default="-1"/>
|
||||||
|
@@ -51,7 +51,8 @@ class NoAuthServerTest : AbstractServerTest() {
|
|||||||
maxAge = Duration.ofSeconds(3600 * 24),
|
maxAge = Duration.ofSeconds(3600 * 24),
|
||||||
compressionEnabled = true,
|
compressionEnabled = true,
|
||||||
digestAlgorithm = "MD5",
|
digestAlgorithm = "MD5",
|
||||||
compressionLevel = Deflater.DEFAULT_COMPRESSION
|
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
||||||
|
maxSize = 0x1000000
|
||||||
),
|
),
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@@ -2,9 +2,9 @@ org.gradle.configuration-cache=false
|
|||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
|
||||||
gbcs.version = 0.1.2
|
gbcs.version = 0.1.3
|
||||||
|
|
||||||
lys.version = 2025.01.31
|
lys.version = 2025.02.05
|
||||||
|
|
||||||
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
|
||||||
docker.registry.url=gitea.woggioni.net
|
docker.registry.url=gitea.woggioni.net
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenLocal {
|
||||||
|
content {
|
||||||
|
includeGroup 'net.woggioni.gradle'
|
||||||
|
}
|
||||||
|
}
|
||||||
maven {
|
maven {
|
||||||
url = getProperty('gitea.maven.url')
|
url = getProperty('gitea.maven.url')
|
||||||
}
|
}
|
||||||
@@ -32,3 +37,4 @@ include 'gbcs-cli'
|
|||||||
include 'docker'
|
include 'docker'
|
||||||
include 'gbcs-client'
|
include 'gbcs-client'
|
||||||
include 'gbcs-server'
|
include 'gbcs-server'
|
||||||
|
include 'gbcs-native'
|
||||||
|
Reference in New Issue
Block a user