Skip to content

Commit

Permalink
Fix memory leak
Browse files Browse the repository at this point in the history
  • Loading branch information
JPercival committed Dec 16, 2024
1 parent 3d54bb6 commit 1cc9d73
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,33 @@ import org.hl7.elm.r1.Element
* In Java use Trackable.INSTANCE to access the same functionality.
*/
object Trackable {
private fun Any.refHash(): Int = System.identityHashCode(this)

private val trackerIdsByElement = HashMap<Int, UUID>()
private val trackerIdsByElement = WeakIdentityHashMap<Element, UUID>()
var Element.trackerId: UUID
get() = trackerIdsByElement.getOrPut(this.refHash()) { UUID.randomUUID() }
get() = trackerIdsByElement.getOrPut(this) { UUID.randomUUID() }
set(value) {
trackerIdsByElement[this.refHash()] = value
trackerIdsByElement[this] = value
}

private val trackbacksByElement = HashMap<Int, MutableList<TrackBack>>()
private val trackbacksByElement = WeakIdentityHashMap<Element, MutableList<TrackBack>>()
var Element.trackbacks: MutableList<TrackBack>
get() = trackbacksByElement.getOrPut(this.refHash()) { ArrayList() }
get() = trackbacksByElement.getOrPut(this) { ArrayList() }
set(value) {
trackbacksByElement[this.refHash()] = value
trackbacksByElement[this] = value
}

private val resultTypesByElement = HashMap<Int, DataType?>()
private val resultTypesByElement = WeakIdentityHashMap<Element, DataType?>()
var Element.resultType: DataType?
get() = resultTypesByElement[this.refHash()]
get() = resultTypesByElement[this]
set(value) {
resultTypesByElement[this.refHash()] = value
if (value == null) {
resultTypesByElement.remove(this)
} else {
resultTypesByElement[this] = value
}
}

fun <T : Element> T.withResultType(resultType: DataType?): T {
this.resultType = resultType
return this
}

fun clear() {
trackerIdsByElement.clear()
trackbacksByElement.clear()
resultTypesByElement.clear()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.cqframework.cql.cql2elm.tracking

import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap

const val DEFAULT_CLEANUP_THRESHOLD = 1000

/**
* This is a map that uses weak references for keys. This means that if the key is no longer
* strongly referenced anywhere in the program, it will be garbage collected and the entry will be
* removed from the map. This is useful for extension properties. The lifetime of the property is
* tied to the lifetime of the object
*/
internal class WeakIdentityHashMap<K : Any, V> {

private val backingMap = ConcurrentHashMap<WeakKey<K>, V>()
private var operationCount = 0
private val cleanupThreshold = DEFAULT_CLEANUP_THRESHOLD

val size: Int
get() = backingMap.size

operator fun get(key: K): V? {
incrementAndCleanUp()
return backingMap[WeakKey(key)]
}

operator fun set(key: K, value: V) {
incrementAndCleanUp()
backingMap[WeakKey(key)] = value
}

fun remove(key: K): V? {
incrementAndCleanUp()
return backingMap.remove(WeakKey(key))
}

fun getOrPut(key: K, defaultValue: () -> V): V {
incrementAndCleanUp()
return backingMap.getOrPut(WeakKey(key), defaultValue)
}

private fun incrementAndCleanUp() {
operationCount++
if (operationCount >= cleanupThreshold) {
cleanUp()
operationCount = 0
}
}

private fun cleanUp() {
val keys = backingMap.keys.filter { it.get() == null }
keys.forEach { backingMap.remove(it) }
}

private class WeakKey<K : Any>(key: K) {
private val ref = WeakReference(key)
private val hashCode = System.identityHashCode(key)

fun get(): K? = ref.get()

override fun equals(other: Any?): Boolean {
if (other !is WeakKey<*>) return false
if (this.hashCode != other.hashCode) return false
return this.ref.get() === other.ref.get()
}

override fun hashCode(): Int = hashCode
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.*;
import org.cqframework.cql.cql2elm.tracking.Trackable;
import org.fhir.ucum.UcumException;
import org.hl7.elm.r1.Library;
import org.hl7.elm.r1.VersionedIdentifier;
Expand Down Expand Up @@ -122,7 +121,6 @@ protected void runTest(
CompositeDataProvider provider,
FhirModelResolver<?, ?, ?, ?, ?, ?, ?, ?> resolver)
throws UcumException {
Trackable.INSTANCE.clear();
String cql = null;
IBaseResource resource = null;
if (test.getInputfile() != null) {
Expand Down

0 comments on commit 1cc9d73

Please sign in to comment.