shared event executor group between server and clients
All checks were successful
CI / build (push) Successful in 3m44s

- improved documentation
- closed memcache client's thread pools
This commit is contained in:
2025-02-24 13:52:20 +08:00
parent c7d2b89d82
commit 23f2a351a6
20 changed files with 286 additions and 140 deletions

View File

@@ -66,11 +66,18 @@ buildCache {
url = 'https://rbcs.example.com/'
push = true
allowInsecureProtocol = false
// The credentials block is only required if you enable
// HTTP basic authentication on RBCS
credentials {
username = 'build-cache-user'
password = 'some-complicated-password'
}
}
}
```
alternatively you can add this to `${GRADLE_HOME}/init.gradle`
alternatively you can add this to `${GRADLE_HOME}/init.gradle` to configure the remote cache
at the system level
```groovy
gradle.settingsEvaluated { settings ->
@@ -79,14 +86,51 @@ gradle.settingsEvaluated { settings ->
url = 'https://rbcs.example.com/'
push = true
allowInsecureProtocol = false
// The credentials block is only required if you enable
// HTTP basic authentication on RBCS
credentials {
username = 'build-cache-user'
password = 'some-complicated-password'
}
}
}
}
```
add `org.gradle.caching=true` to your `<project>/gradle.properties` or run gradle with `--build-cache`.
Read [Gradle documentation](https://docs.gradle.org/current/userguide/build_cache.html) for more detailed information.
### Using RBCS with Maven
1. Create an `extensions.xml` in `<project>/.mvn/extensions.xml` with the following content
```xml
<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.1.0 https://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
<extension>
<groupId>org.apache.maven.extensions</groupId>
<artifactId>maven-build-cache-extension</artifactId>
<version>1.2.0</version>
</extension>
</extensions>
```
2. Copy [maven-build-cache-config.xml](https://maven.apache.org/extensions/maven-build-cache-extension/maven-build-cache-config.xml) into `<project>/.mvn/` folder
3. Edit the `cache/configuration/remote` element
```xml
<remote enabled="true" id="rbcs">
<url>https://rbcs.example.com/</url>
</remote>
```
4. Run maven with
```bash
mvn -Dmaven.build.cache.enabled=true -Dmaven.build.cache.debugOutput=true -Dmaven.build.cache.remote.save.enabled=true package
```
Alternatively you can set those properties in your `<project>/pom.xml`
Read [here](https://maven.apache.org/extensions/maven-build-cache-extension/remote-cache.html)
for more informations
## FAQ
### Why should I use a build cache?

View File

@@ -14,9 +14,7 @@ allprojects { subproject ->
if(project.currentTag.isPresent()) {
version = project.currentTag.map { it[0] }.get()
} else {
version = project.gitRevision.map { gitRevision ->
"${getProperty('rbcs.version')}.${gitRevision[0..10]}"
}.get()
version = "${getProperty('rbcs.version')}-SNAPSHOT"
}
repositories {
@@ -24,7 +22,6 @@ allprojects { subproject ->
url = getProperty('gitea.maven.url')
content {
includeModule 'net.woggioni', 'jwo'
includeModule 'net.woggioni', 'xmemcached'
includeGroup 'com.lys'
}
}

View File

@@ -5,6 +5,7 @@ plugins {
}
dependencies {
api catalog.netty.common
api catalog.netty.buffer
api catalog.netty.handler
}

View File

@@ -4,6 +4,7 @@ module net.woggioni.rbcs.api {
requires io.netty.buffer;
requires io.netty.handler;
requires io.netty.transport;
requires io.netty.common;
exports net.woggioni.rbcs.api;
exports net.woggioni.rbcs.api.exception;
exports net.woggioni.rbcs.api.message;

View File

@@ -0,0 +1,13 @@
package net.woggioni.rbcs.api;
import java.util.concurrent.CompletableFuture;
public interface AsyncCloseable extends AutoCloseable {
CompletableFuture<Void> asyncClose();
@Override
default void close() throws Exception {
asyncClose().get();
}
}

View File

@@ -1,7 +1,15 @@
package net.woggioni.rbcs.api;
import io.netty.channel.ChannelFactory;
import io.netty.channel.ChannelHandler;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.SocketChannel;
public interface CacheHandlerFactory extends AutoCloseable {
ChannelHandler newHandler();
public interface CacheHandlerFactory extends AsyncCloseable {
ChannelHandler newHandler(
EventLoopGroup eventLoopGroup,
ChannelFactory<SocketChannel> socketChannelFactory,
ChannelFactory<DatagramChannel> datagramChannelFactory
);
}

View File

@@ -83,17 +83,6 @@ public class Configuration {
Group extract(X509Certificate cert);
}
@Value
public static class Throttling {
KeyStore keyStore;
TrustStore trustStore;
boolean verifyClients;
}
public enum ClientCertificate {
REQUIRED, OPTIONAL
}
@Value
public static class Tls {
KeyStore keyStore;

View File

@@ -9,6 +9,7 @@ plugins {
id 'maven-publish'
}
import net.woggioni.gradle.envelope.EnvelopePlugin
import net.woggioni.gradle.envelope.EnvelopeJarTask
import net.woggioni.gradle.graalvm.NativeImageConfigurationTask
import net.woggioni.gradle.graalvm.NativeImagePlugin
@@ -16,15 +17,6 @@ import net.woggioni.gradle.graalvm.NativeImageTask
import net.woggioni.gradle.graalvm.JlinkPlugin
import net.woggioni.gradle.graalvm.JlinkTask
Property<String> mainModuleName = objects.property(String.class)
mainModuleName.set('net.woggioni.rbcs.cli')
Property<String> mainClassName = objects.property(String.class)
mainClassName.set('net.woggioni.rbcs.cli.RemoteBuildCacheServerCli')
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
options.javaModuleMainClass = mainClassName
}
configurations {
release {
transitive = false
@@ -34,13 +26,6 @@ configurations {
}
}
envelopeJar {
mainModule = mainModuleName
mainClass = mainClassName
extraClasspath = ["plugins"]
}
dependencies {
implementation catalog.jwo
implementation catalog.slf4j.api
@@ -54,18 +39,24 @@ dependencies {
// runtimeOnly catalog.slf4j.simple
}
Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named('envelopeJar', EnvelopeJarTask.class) {
// systemProperties['java.util.logging.config.class'] = 'net.woggioni.rbcs.LoggingConfig'
// systemProperties['log.config.source'] = 'net/woggioni/rbcs/cli/logging.properties'
// systemProperties['java.util.logging.config.file'] = 'classpath:net/woggioni/rbcs/cli/logging.properties'
Property<String> mainModuleName = objects.property(String.class)
mainModuleName.set('net.woggioni.rbcs.cli')
Property<String> mainClassName = objects.property(String.class)
mainClassName.set('net.woggioni.rbcs.cli.RemoteBuildCacheServerCli')
tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
options.javaModuleMainClass = mainClassName
}
Provider<EnvelopeJarTask> envelopeJarTaskProvider = tasks.named(EnvelopePlugin.ENVELOPE_JAR_TASK_NAME, EnvelopeJarTask.class) {
mainModule = mainModuleName
mainClass = mainClassName
extraClasspath = ["plugins"]
systemProperties['logback.configurationFile'] = 'classpath:net/woggioni/rbcs/cli/logback.xml'
systemProperties['io.netty.leakDetectionLevel'] = 'DISABLED'
// systemProperties['org.slf4j.simpleLogger.showDateTime'] = 'true'
// systemProperties['org.slf4j.simpleLogger.defaultLogLevel'] = 'debug'
// systemProperties['org.slf4j.simpleLogger.log.com.google.code.yanf4j'] = 'warn'
// systemProperties['org.slf4j.simpleLogger.log.net.rubyeye.xmemcached'] = 'warn'
// systemProperties['org.slf4j.simpleLogger.dateTimeFormat'] = 'yyyy-MM-dd\'T\'HH:mm:ss.SSSZ'
}
tasks.named(NativeImagePlugin.CONFIGURE_NATIVE_IMAGE_TASK_NAME, NativeImageConfigurationTask) {

View File

@@ -32,7 +32,7 @@ object RBCS {
fun digest(
data: ByteArray,
md: MessageDigest = MessageDigest.getInstance("MD5")
md: MessageDigest
): ByteArray {
md.update(data)
return md.digest()
@@ -40,7 +40,7 @@ object RBCS {
fun digestString(
data: ByteArray,
md: MessageDigest = MessageDigest.getInstance("MD5")
md: MessageDigest
): String {
return JWO.bytesToHex(digest(data, md))
}

View File

@@ -1,10 +1,20 @@
package net.woggioni.rbcs.server.memcache
import io.netty.channel.ChannelFactory
import io.netty.channel.ChannelHandler
import io.netty.channel.EventLoopGroup
import io.netty.channel.pool.FixedChannelPool
import io.netty.channel.socket.DatagramChannel
import io.netty.channel.socket.SocketChannel
import net.woggioni.rbcs.api.CacheHandlerFactory
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.common.HostAndPort
import net.woggioni.rbcs.server.memcache.client.MemcacheClient
import java.time.Duration
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
data class MemcacheCacheConfiguration(
val servers: List<Server>,
@@ -12,7 +22,7 @@ data class MemcacheCacheConfiguration(
val digestAlgorithm: String? = null,
val compressionMode: CompressionMode? = null,
val compressionLevel: Int,
val chunkSize : Int
val chunkSize: Int
) : Configuration.Cache {
enum class CompressionMode {
@@ -23,19 +33,58 @@ data class MemcacheCacheConfiguration(
}
data class Server(
val endpoint : HostAndPort,
val connectionTimeoutMillis : Int?,
val maxConnections : Int
val endpoint: HostAndPort,
val connectionTimeoutMillis: Int?,
val maxConnections: Int
)
override fun materialize() = object : CacheHandlerFactory {
private val client = MemcacheClient(this@MemcacheCacheConfiguration.servers, chunkSize)
override fun close() {
client.close()
private val connectionPoolMap = ConcurrentHashMap<HostAndPort, FixedChannelPool>()
override fun newHandler(
eventLoop: EventLoopGroup,
socketChannelFactory: ChannelFactory<SocketChannel>,
datagramChannelFactory: ChannelFactory<DatagramChannel>
): ChannelHandler {
return MemcacheCacheHandler(
MemcacheClient(
this@MemcacheCacheConfiguration.servers,
chunkSize,
eventLoop,
socketChannelFactory,
connectionPoolMap
),
digestAlgorithm,
compressionMode != null,
compressionLevel,
chunkSize,
maxAge
)
}
override fun asyncClose() = object : CompletableFuture<Void>() {
init {
val failure = AtomicReference<Throwable>(null)
val pools = connectionPoolMap.values.toList()
val npools = pools.size
val finished = AtomicInteger(0)
pools.forEach { pool ->
pool.closeAsync().addListener {
if (!it.isSuccess) {
failure.compareAndSet(null, it.cause())
}
if(finished.incrementAndGet() == npools) {
when(val ex = failure.get()) {
null -> complete(null)
else -> completeExceptionally(ex)
}
}
}
}
}
}
override fun newHandler() = MemcacheCacheHandler(client, digestAlgorithm, compressionMode != null, compressionLevel, chunkSize, maxAge)
}
override fun getNamespaceURI() = "urn:net.woggioni.rbcs.server.memcache"

View File

@@ -4,16 +4,17 @@ package net.woggioni.rbcs.server.memcache.client
import io.netty.bootstrap.Bootstrap
import io.netty.buffer.ByteBuf
import io.netty.channel.Channel
import io.netty.channel.ChannelFactory
import io.netty.channel.ChannelFutureListener
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelOption
import io.netty.channel.ChannelPipeline
import io.netty.channel.EventLoopGroup
import io.netty.channel.SimpleChannelInboundHandler
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.pool.AbstractChannelPoolHandler
import io.netty.channel.pool.ChannelPool
import io.netty.channel.pool.FixedChannelPool
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.channel.socket.SocketChannel
import io.netty.handler.codec.memcache.LastMemcacheContent
import io.netty.handler.codec.memcache.MemcacheContent
import io.netty.handler.codec.memcache.MemcacheObject
@@ -33,23 +34,22 @@ import java.util.concurrent.ConcurrentHashMap
import io.netty.util.concurrent.Future as NettyFuture
class MemcacheClient(private val servers: List<MemcacheCacheConfiguration.Server>, private val chunkSize : Int) : AutoCloseable {
class MemcacheClient(
private val servers: List<MemcacheCacheConfiguration.Server>,
private val chunkSize : Int,
private val group: EventLoopGroup,
private val channelFactory: ChannelFactory<SocketChannel>,
private val connectionPool: ConcurrentHashMap<HostAndPort, FixedChannelPool>
) : AutoCloseable {
private companion object {
private val log = createLogger<MemcacheCacheHandler>()
}
private val group: NioEventLoopGroup
private val connectionPool: MutableMap<HostAndPort, ChannelPool> = ConcurrentHashMap()
init {
group = NioEventLoopGroup()
}
private fun newConnectionPool(server: MemcacheCacheConfiguration.Server): FixedChannelPool {
val bootstrap = Bootstrap().apply {
group(group)
channel(NioSocketChannel::class.java)
channelFactory(channelFactory)
option(ChannelOption.SO_KEEPALIVE, true)
remoteAddress(InetSocketAddress(server.endpoint.host, server.endpoint.port))
server.connectionTimeoutMillis?.let {

View File

@@ -21,7 +21,7 @@
</xs:sequence>
<xs:attribute name="max-age" type="xs:duration" default="P1D"/>
<xs:attribute name="chunk-size" type="rbcs:byteSizeType" default="0x10000"/>
<xs:attribute name="digest" type="xs:token" />
<xs:attribute name="digest" type="xs:token"/>
<xs:attribute name="compression-mode" type="rbcs-memcache:compressionType"/>
<xs:attribute name="compression-level" type="rbcs:compressionLevelType" default="-1"/>
</xs:extension>

View File

@@ -3,6 +3,7 @@ package net.woggioni.rbcs.server
import io.netty.bootstrap.ServerBootstrap
import io.netty.buffer.ByteBuf
import io.netty.channel.Channel
import io.netty.channel.ChannelFactory
import io.netty.channel.ChannelFuture
import io.netty.channel.ChannelHandler.Sharable
import io.netty.channel.ChannelHandlerContext
@@ -11,7 +12,12 @@ import io.netty.channel.ChannelInitializer
import io.netty.channel.ChannelOption
import io.netty.channel.ChannelPromise
import io.netty.channel.nio.NioEventLoopGroup
import io.netty.channel.socket.DatagramChannel
import io.netty.channel.socket.ServerSocketChannel
import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioDatagramChannel
import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.channel.socket.nio.NioSocketChannel
import io.netty.handler.codec.compression.CompressionOptions
import io.netty.handler.codec.http.DefaultHttpContent
import io.netty.handler.codec.http.HttpContentCompressor
@@ -31,6 +37,7 @@ import io.netty.util.concurrent.DefaultEventExecutorGroup
import io.netty.util.concurrent.EventExecutorGroup
import net.woggioni.jwo.JWO
import net.woggioni.jwo.Tuple2
import net.woggioni.rbcs.api.AsyncCloseable
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.api.exception.ConfigurationException
import net.woggioni.rbcs.common.PasswordSecurity.decodePasswordHash
@@ -200,8 +207,10 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
private class ServerInitializer(
private val cfg: Configuration,
private val channelFactory : ChannelFactory<SocketChannel>,
private val datagramChannelFactory : ChannelFactory<DatagramChannel>,
private val eventExecutorGroup: EventExecutorGroup
) : ChannelInitializer<Channel>(), AutoCloseable {
) : ChannelInitializer<Channel>(), AsyncCloseable {
companion object {
private fun createSslCtx(tls: Configuration.Tls): SslContext {
@@ -368,21 +377,20 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
ServerHandler(prefix)
}
pipeline.addLast(eventExecutorGroup, ServerHandler.NAME, serverHandler)
pipeline.addLast(cacheHandlerFactory.newHandler())
pipeline.addLast(cacheHandlerFactory.newHandler(ch.eventLoop(), channelFactory, datagramChannelFactory))
pipeline.addLast(TraceHandler)
pipeline.addLast(ExceptionHandler)
}
override fun close() {
cacheHandlerFactory.close()
}
override fun asyncClose() = cacheHandlerFactory.asyncClose()
}
class ServerHandle(
closeFuture: ChannelFuture,
private val bossGroup: EventExecutorGroup,
private val executorGroups: Iterable<EventExecutorGroup>,
private val serverInitializer: AutoCloseable,
private val serverInitializer: AsyncCloseable,
) : Future<Void> by from(closeFuture, executorGroups, serverInitializer) {
companion object {
@@ -391,13 +399,28 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
private fun from(
closeFuture: ChannelFuture,
executorGroups: Iterable<EventExecutorGroup>,
serverInitializer: AutoCloseable
serverInitializer: AsyncCloseable
): CompletableFuture<Void> {
val result = CompletableFuture<Void>()
closeFuture.addListener {
val errors = mutableListOf<Throwable>()
val deadline = Instant.now().plusSeconds(20)
try {
serverInitializer.close()
} catch (ex: Throwable) {
log.error(ex.message, ex)
errors.addLast(ex)
}
serverInitializer.asyncClose().whenComplete { _, ex ->
if(ex != null) {
log.error(ex.message, ex)
errors.addLast(ex)
}
executorGroups.map {
it.shutdownGracefully()
}
for (executorGroup in executorGroups) {
val future = executorGroup.terminationFuture()
@@ -417,18 +440,14 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
errors.addLast(ex)
}
}
try {
serverInitializer.close()
} catch (ex: Throwable) {
log.error(ex.message, ex)
errors.addLast(ex)
}
if(errors.isEmpty()) {
result.complete(null)
} else {
result.completeExceptionally(errors.first())
}
}
}
return result.thenAccept {
log.info {
@@ -441,16 +460,15 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
fun sendShutdownSignal() {
bossGroup.shutdownGracefully()
executorGroups.map {
it.shutdownGracefully()
}
}
}
fun run(): ServerHandle {
// Create the multithreaded event loops for the server
val bossGroup = NioEventLoopGroup(1)
val serverSocketChannel = NioServerSocketChannel::class.java
val channelFactory = ChannelFactory<SocketChannel> { NioSocketChannel() }
val datagramChannelFactory = ChannelFactory<DatagramChannel> { NioDatagramChannel() }
val serverChannelFactory = ChannelFactory<ServerSocketChannel> { NioServerSocketChannel() }
val workerGroup = NioEventLoopGroup(0)
val eventExecutorGroup = run {
val threadFactory = if (cfg.eventExecutor.isUseVirtualThreads) {
@@ -460,11 +478,11 @@ class RemoteBuildCacheServer(private val cfg: Configuration) {
}
DefaultEventExecutorGroup(Runtime.getRuntime().availableProcessors(), threadFactory)
}
val serverInitializer = ServerInitializer(cfg, eventExecutorGroup)
val serverInitializer = ServerInitializer(cfg, channelFactory, datagramChannelFactory, workerGroup)
val bootstrap = ServerBootstrap().apply {
// Configure the server
group(bossGroup, workerGroup)
channel(serverSocketChannel)
channelFactory(serverChannelFactory)
childHandler(serverInitializer)
option(ChannelOption.SO_BACKLOG, cfg.incomingConnectionsBacklogSize)
childOption(ChannelOption.SO_KEEPALIVE, true)

View File

@@ -1,6 +1,7 @@
package net.woggioni.rbcs.server.cache
import net.woggioni.jwo.JWO
import net.woggioni.rbcs.api.AsyncCloseable
import net.woggioni.rbcs.api.CacheValueMetadata
import net.woggioni.rbcs.common.createLogger
import java.io.ByteArrayOutputStream
@@ -18,11 +19,12 @@ import java.nio.file.StandardOpenOption
import java.nio.file.attribute.BasicFileAttributes
import java.time.Duration
import java.time.Instant
import java.util.concurrent.CompletableFuture
class FileSystemCache(
val root: Path,
val maxAge: Duration
) : AutoCloseable {
) : AsyncCloseable {
class EntryValue(val metadata: CacheValueMetadata, val channel : FileChannel, val offset : Long, val size : Long) : Serializable
@@ -112,10 +114,19 @@ class FileSystemCache(
return FileSink(metadata, file, tmpFile)
}
private val garbageCollector = Thread.ofVirtual().name("file-system-cache-gc").start {
private val closeFuture = object : CompletableFuture<Void>() {
init {
Thread.ofVirtual().name("file-system-cache-gc").start {
try {
while (running) {
gc()
}
complete(null)
} catch (ex : Throwable) {
completeExceptionally(ex)
}
}
}
}
private fun gc() {
@@ -151,8 +162,8 @@ class FileSystemCache(
return result
}
override fun close() {
override fun asyncClose() : CompletableFuture<Void> {
running = false
garbageCollector.join()
return closeFuture
}
}

View File

@@ -1,5 +1,9 @@
package net.woggioni.rbcs.server.cache
import io.netty.channel.ChannelFactory
import io.netty.channel.EventLoopGroup
import io.netty.channel.socket.DatagramChannel
import io.netty.channel.socket.SocketChannel
import net.woggioni.jwo.Application
import net.woggioni.rbcs.api.CacheHandlerFactory
import net.woggioni.rbcs.api.Configuration
@@ -19,11 +23,13 @@ data class FileSystemCacheConfiguration(
override fun materialize() = object : CacheHandlerFactory {
private val cache = FileSystemCache(root ?: Application.builder("rbcs").build().computeCacheDirectory(), maxAge)
override fun close() {
cache.close()
}
override fun asyncClose() = cache.asyncClose()
override fun newHandler() = FileSystemCacheHandler(cache, digestAlgorithm, compressionEnabled, compressionLevel, chunkSize)
override fun newHandler(
eventLoop: EventLoopGroup,
socketChannelFactory: ChannelFactory<SocketChannel>,
datagramChannelFactory: ChannelFactory<DatagramChannel>
) = FileSystemCacheHandler(cache, digestAlgorithm, compressionEnabled, compressionLevel, chunkSize)
}
override fun getNamespaceURI() = RBCS.RBCS_NAMESPACE_URI

View File

@@ -30,7 +30,7 @@ class FileSystemCacheProvider : CacheProvider<FileSystemCacheConfiguration> {
val compressionLevel = el.renderAttribute("compression-level")
?.let(String::toInt)
?: Deflater.DEFAULT_COMPRESSION
val digestAlgorithm = el.renderAttribute("digest") ?: "MD5"
val digestAlgorithm = el.renderAttribute("digest")
val chunkSize = el.renderAttribute("chunk-size")
?.let(Integer::decode)
?: 0x10000

View File

@@ -1,10 +1,12 @@
package net.woggioni.rbcs.server.cache
import io.netty.buffer.ByteBuf
import net.woggioni.rbcs.api.AsyncCloseable
import net.woggioni.rbcs.api.CacheValueMetadata
import net.woggioni.rbcs.common.createLogger
import java.time.Duration
import java.time.Instant
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.PriorityBlockingQueue
import java.util.concurrent.TimeUnit
@@ -26,7 +28,7 @@ class CacheEntry(
class InMemoryCache(
private val maxAge: Duration,
private val maxSize: Long
) : AutoCloseable {
) : AsyncCloseable {
companion object {
private val log = createLogger<InMemoryCache>()
@@ -45,7 +47,10 @@ class InMemoryCache(
@Volatile
private var running = true
private val garbageCollector = Thread.ofVirtual().name("in-memory-cache-gc").start {
private val closeFuture = object : CompletableFuture<Void>() {
init {
Thread.ofVirtual().name("in-memory-cache-gc").start {
try {
while (running) {
val el = removalQueue.poll(1, TimeUnit.SECONDS) ?: continue
val value = el.value
@@ -62,9 +67,15 @@ class InMemoryCache(
Thread.sleep(minOf(Duration.between(now, el.expiry), Duration.ofSeconds(1)))
}
}
complete(null)
} catch (ex: Throwable) {
completeExceptionally(ex)
}
}
}
}
private fun removeEldest(): Long {
fun removeEldest(): Long {
while (true) {
val el = removalQueue.take()
val value = el.value
@@ -84,9 +95,9 @@ class InMemoryCache(
}
}
override fun close() {
override fun asyncClose() : CompletableFuture<Void> {
running = false
garbageCollector.join()
return closeFuture
}
fun get(key: ByteArray) = map[CacheKey(key)]?.run {

View File

@@ -1,5 +1,10 @@
package net.woggioni.rbcs.server.cache
import io.netty.channel.ChannelFactory
import io.netty.channel.EventLoopGroup
import io.netty.channel.socket.DatagramChannel
import io.netty.channel.socket.SocketChannel
import io.netty.util.concurrent.Future
import net.woggioni.rbcs.api.CacheHandlerFactory
import net.woggioni.rbcs.api.Configuration
import net.woggioni.rbcs.common.RBCS
@@ -16,11 +21,13 @@ data class InMemoryCacheConfiguration(
override fun materialize() = object : CacheHandlerFactory {
private val cache = InMemoryCache(maxAge, maxSize)
override fun close() {
cache.close()
}
override fun asyncClose() = cache.asyncClose()
override fun newHandler() = InMemoryCacheHandler(cache, digestAlgorithm, compressionEnabled, compressionLevel)
override fun newHandler(
eventLoop: EventLoopGroup,
socketChannelFactory: ChannelFactory<SocketChannel>,
datagramChannelFactory: ChannelFactory<DatagramChannel>
) = InMemoryCacheHandler(cache, digestAlgorithm, compressionEnabled, compressionLevel)
}
override fun getNamespaceURI() = RBCS.RBCS_NAMESPACE_URI

View File

@@ -30,7 +30,7 @@ class InMemoryCacheProvider : CacheProvider<InMemoryCacheConfiguration> {
val compressionLevel = el.renderAttribute("compression-level")
?.let(String::toInt)
?: Deflater.DEFAULT_COMPRESSION
val digestAlgorithm = el.renderAttribute("digest") ?: "MD5"
val digestAlgorithm = el.renderAttribute("digest")
val chunkSize = el.renderAttribute("chunk-size")
?.let(Integer::decode)
?: 0x10000

View File

@@ -153,7 +153,7 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="digest" type="xs:token" default="MD5">
<xs:attribute name="digest" type="xs:token">
<xs:annotation>
<xs:documentation>
Hashing algorithm to apply to the key. If omitted, no hashing is performed.
@@ -209,7 +209,7 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="digest" type="xs:token" default="MD5">
<xs:attribute name="digest" type="xs:token" default="SHA3-224">
<xs:annotation>
<xs:documentation>
Hashing algorithm to apply to the key. If omitted, no hashing is performed.