Fix memory leak in sealed builder. Fine-grained Meta builders.

This commit is contained in:
Alexander Nozik 2023-10-08 18:21:04 +03:00
parent cbbcd18df3
commit 851fdda311
8 changed files with 98 additions and 38 deletions

View File

@ -7,11 +7,11 @@ import kotlin.test.assertEquals
fun Meta.toByteArray(format: MetaFormat = JsonMetaFormat) = ByteArray {
format.writeObject(this@ByteArray, this@toByteArray)
format.writeTo(this@ByteArray, this@toByteArray)
}
fun MetaFormat.fromByteArray(packet: ByteArray): Meta {
return packet.asBinary().read { readObject(this) }
return packet.asBinary().read { readFrom(this) }
}
class MetaFormatTest {

View File

@ -4,8 +4,8 @@ import kotlinx.io.buffered
fun <T : Any> IOFormat<T>.writeToByteArray(obj: T): ByteArray = ByteArray {
writeObject(this, obj)
writeTo(this, obj)
}
fun <T : Any> IOFormat<T>.readFromByteArray(array: ByteArray): T = ByteArraySource(array).buffered().use {
readObject(it)
readFrom(it)
}

View File

@ -50,7 +50,9 @@ public interface Meta : MetaRepr, MetaProvider {
override fun toMeta(): Meta = this
override fun toString(): String
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
public companion object {

View File

@ -11,7 +11,7 @@ import kotlin.js.JsName
* Mark a meta builder
*/
@DslMarker
public annotation class MetaBuilder
public annotation class MetaBuilderMarker
/**
* A generic interface that gives access to getting and setting meta notes and values
@ -27,7 +27,7 @@ public interface MutableMetaProvider : MetaProvider, MutableValueProvider {
* TODO documentation
*/
@Serializable(MutableMetaSerializer::class)
@MetaBuilder
@MetaBuilderMarker
public interface MutableMeta : Meta, MutableMetaProvider {
override val items: Map<NameToken, MutableMeta>
@ -90,8 +90,8 @@ public interface MutableMeta : Meta, MutableMetaProvider {
setMeta(this, repr.toMeta())
}
public infix fun Name.put(mutableMeta: MutableMeta.() -> Unit) {
setMeta(this, Meta(mutableMeta))
public infix fun Name.put(builder: MutableMeta.() -> Unit) {
getOrCreate(this).apply(builder)
}
public infix fun String.put(meta: Meta) {
@ -131,7 +131,7 @@ public interface MutableMeta : Meta, MutableMetaProvider {
}
public infix fun String.put(builder: MutableMeta.() -> Unit) {
setMeta(Name.parse(this), MutableMeta(builder))
getOrCreate(parseAsName()).apply(builder)
}
}
@ -381,16 +381,14 @@ public fun Meta.toMutableMeta(): ObservableMutableMeta = MutableMetaImpl(value,
public fun Meta.asMutableMeta(): MutableMeta = (this as? MutableMeta) ?: toMutableMeta()
@Suppress("FunctionName")
@JsName("newMutableMeta")
public fun MutableMeta(): ObservableMutableMeta = MutableMetaImpl(null)
@JsName("newObservableMutableMeta")
public fun ObservableMutableMeta(): ObservableMutableMeta = MutableMetaImpl(null)
/**
* Build a [MutableMeta] using given transformation
*/
@Suppress("FunctionName")
public inline fun MutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
MutableMeta().apply(builder)
public inline fun ObservableMutableMeta(builder: MutableMeta.() -> Unit = {}): ObservableMutableMeta =
ObservableMutableMeta().apply(builder)
/**

View File

@ -51,6 +51,8 @@ private class ObservableMetaWrapper(
override fun setMeta(name: Name, node: Meta?) {
val oldMeta = get(name)
//don't forget to remove listener
oldMeta?.removeListener(this)
root.setMeta(absoluteName + name, node)
if (oldMeta != node) {
invalidate(name)

View File

@ -1,16 +1,17 @@
package space.kscience.dataforge.meta
import kotlinx.serialization.Serializable
import space.kscience.dataforge.names.NameToken
import space.kscience.dataforge.names.*
import kotlin.js.JsName
/**
* The meta implementation which is guaranteed to be immutable.
*
*/
@Serializable
public class SealedMeta internal constructor(
public class SealedMeta(
override val value: Value?,
override val items: Map<NameToken, SealedMeta>
override val items: Map<NameToken, SealedMeta>,
) : TypedMeta<SealedMeta> {
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
@ -26,7 +27,7 @@ public class SealedMeta internal constructor(
}
/**
* Generate sealed node from [this]. If it is already sealed return it as is.
* Generate sealed node from [this]. If it is already sealed, return it as is.
*/
public fun Meta.seal(): SealedMeta = this as? SealedMeta ?: SealedMeta(
value,
@ -47,7 +48,79 @@ public fun Meta(value: String): SealedMeta = Meta(value.asValue())
@Suppress("FunctionName")
public fun Meta(value: Boolean): SealedMeta = Meta(value.asValue())
@Suppress("FunctionName")
public inline fun Meta(builder: MutableMeta.() -> Unit): SealedMeta =
MutableMeta(builder).seal()
/**
* A lightweight mutable meta used to create [SealedMeta] instances without bothering with
*/
@PublishedApi
internal class MetaBuilder(
override var value: Value? = null,
override val items: MutableMap<NameToken, MetaBuilder> = hashMapOf(),
) : MutableMeta {
override fun getOrCreate(name: Name): MetaBuilder {
val existing = get(name) as? MetaBuilder
return if (existing == null) {
val newItem = MetaBuilder()
setMeta(name, newItem)
newItem
} else {
existing
}
}
private fun wrap(meta: Meta): MetaBuilder = meta as? MetaBuilder ?: MetaBuilder(
meta.value,
meta.items.mapValuesTo(hashMapOf()) { wrap(it.value) }
)
override fun setMeta(name: Name, node: Meta?) {
when (name.length) {
0 -> error("Can't set a meta with empty name")
1 -> {
val token = name.first()
//remove child and invalidate if argument is null
if (node == null) {
items.remove(token)
} else {
items[token] = wrap(node)
}
}
else -> {
getOrCreate(name.first().asName()).setMeta(name.cutFirst(), node)
}
}
}
override fun toString(): String = Meta.toString(this)
override fun equals(other: Any?): Boolean = Meta.equals(this, other as? Meta)
override fun hashCode(): Int = Meta.hashCode(this)
}
/**
* Create a read-only meta.
*/
public inline fun Meta(builder: MutableMeta.() -> Unit): Meta =
MetaBuilder().apply(builder).seal()
/**
* Create an immutable meta.
*/
public inline fun SealedMeta(builder: MutableMeta.() -> Unit): SealedMeta =
MetaBuilder().apply(builder).seal()
/**
* Create an empty meta mutable meta.
*/
@JsName("newMutableMeta")
public fun MutableMeta(): MutableMeta = MetaBuilder()
/**
* Create a mutable meta with given builder.
*/
public inline fun MutableMeta(builder: MutableMeta.() -> Unit = {}): MutableMeta =
MutableMeta().apply(builder)

View File

@ -105,7 +105,7 @@ public value class MetaTransformation(private val transformations: Collection<Tr
* Generate an observable configuration that contains only elements defined by transformation rules and changes with the source
*/
@DFExperimental
public fun generate(source: ObservableMeta): ObservableMeta = MutableMeta().apply {
public fun generate(source: ObservableMeta): ObservableMeta = ObservableMutableMeta{
transformations.forEach { rule ->
rule.selectItems(source).forEach { name ->
rule.transformItem(name, source[name], this)

View File

@ -1,15 +0,0 @@
package space.kscience.dataforge.meta
import org.junit.jupiter.api.Test
import kotlin.test.assertFails
class JvmMutableMetaTest {
@Test
fun recursiveMeta(){
val meta = MutableMeta {
"a" put 2
}
assertFails { meta["child.a"] = meta }
}
}