Skip to content
This repository was archived by the owner on Aug 10, 2024. It is now read-only.

Commit 1455775

Browse files
committed
clean up DOMs when client state expires, hopefully prevent resource leaks
1 parent 2d1eac3 commit 1455775

File tree

4 files changed

+61
-2
lines changed

4 files changed

+61
-2
lines changed

src/main/kotlin/kweb/Kweb.kt

+6
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,9 @@ class Kweb private constructor(
140140
val clientState: Cache<String, RemoteClientState> = CacheBuilder.newBuilder()
141141
.expireAfterAccess(kwebConfig.clientStateTimeout)
142142
.apply { if (kwebConfig.clientStateStatsEnabled) recordStats() }
143+
.removalListener<String, RemoteClientState> { rl ->
144+
rl.value?.triggerCloseListeners()
145+
}
143146
.build()
144147

145148
//: ConcurrentHashMap<String, RemoteClientState> = ConcurrentHashMap()
@@ -339,6 +342,9 @@ class Kweb private constructor(
339342

340343
try {
341344
val webBrowser = WebBrowser(kwebSessionId, httpRequestInfo, this)
345+
346+
remoteClientState.addCloseHandler { webBrowser.close() }
347+
342348
webBrowser.htmlDocument.set(htmlDocument)
343349
if (debug) {
344350
warnIfBlocking(maxTimeMs = kwebConfig.buildpageTimeout.toMillis(), onBlock = { thread ->

src/main/kotlin/kweb/WebBrowser.kt

+20
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class WebBrowser(val sessionId: String, val httpRequestInfo: HttpRequestInfo, va
3737

3838
private val idCounter = AtomicInteger(0)
3939

40+
private val closeListeners : ConcurrentHashMap<Int, () -> Unit> = ConcurrentHashMap()
41+
4042
/**
4143
* During page render, the initial HTML document will be available for modification as a
4244
* [JSoup Document](https://jsoup.org/) in this [AtomicReference].
@@ -229,6 +231,24 @@ class WebBrowser(val sessionId: String, val httpRequestInfo: HttpRequestInfo, va
229231
kweb.addCallback(sessionId, functionCall.callbackId!!, callback)
230232
}
231233

234+
fun addCloseListener(listener: () -> Unit) : Int {
235+
val id = random.nextInt()
236+
this.closeListeners[id] = listener
237+
return id
238+
}
239+
240+
fun removeCloseListener(id: Int) {
241+
synchronized(this) {
242+
this.closeListeners.remove(id)
243+
}
244+
}
245+
246+
fun close() {
247+
synchronized(this) {
248+
this.closeListeners.values.forEach { it() }
249+
}
250+
}
251+
232252
private fun createCacheFunctionJs(cacheId: Int, functionBody: String, params: String? = null) : String {
233253
params?.let {
234254
//language=JavaScript

src/main/kotlin/kweb/client/RemoteClientState.kt

+24
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,38 @@ import kotlinx.serialization.encodeToString
44
import kotlinx.serialization.json.Json
55
import kotlinx.serialization.json.JsonElement
66
import kweb.DebugInfo
7+
import kweb.util.random
78
import java.time.Instant
9+
import java.util.concurrent.ConcurrentHashMap
810

911
data class RemoteClientState(val id: String, @Volatile var clientConnection: ClientConnection,
1012
val handlers: MutableMap<Int, (JsonElement) -> Unit> = HashMap(),
13+
val onCloseHandlers : ConcurrentHashMap<Int, () -> Unit> = ConcurrentHashMap(),
1114
val debugTokens: MutableMap<String, DebugInfo> = HashMap(), var lastModified: Instant = Instant.now()) {
1215
fun send(message: Server2ClientMessage) {
1316
clientConnection.send(Json.encodeToString(message))
1417
}
1518

19+
fun addCloseHandler(handler: () -> Unit) : Int {
20+
synchronized(this) {
21+
val id = random.nextInt()
22+
onCloseHandlers[id] = handler
23+
return id
24+
}
25+
}
26+
27+
fun removeCloseHandler(id: Int) {
28+
synchronized(this) {
29+
onCloseHandlers.remove(id)
30+
}
31+
}
32+
33+
fun triggerCloseListeners() {
34+
synchronized(this) {
35+
onCloseHandlers.values.forEach { it() }
36+
onCloseHandlers.clear()
37+
}
38+
}
39+
1640
override fun toString() = "Remote2ClientState(id = $id)"
1741
}

src/main/kotlin/kweb/html/Document.kt

+11-2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import kweb.util.random
1818
* Passed in as `doc` to the `buildPage` lambda of the [Kweb] constructor.
1919
*/
2020
class Document(val receiver: WebBrowser) : EventGenerator<Document> {
21+
2122
fun getElementById(id: String) = Element(receiver, null, "return document.getElementById(\"$id\")", id = id)
2223

2324
val cookie = CookieReceiver(receiver)
@@ -26,7 +27,11 @@ class Document(val receiver: WebBrowser) : EventGenerator<Document> {
2627

2728
fun body(new: (ElementCreator<BodyElement>.() -> Unit)? = null) : BodyElement {
2829
if (new != null) {
29-
new(ElementCreator(parent = body, insertBefore = null))
30+
val ec = ElementCreator(parent = body, insertBefore = null)
31+
new(ec)
32+
receiver.addCloseListener {
33+
ec.cleanup()
34+
}
3035
}
3136
return body
3237
}
@@ -35,7 +40,11 @@ class Document(val receiver: WebBrowser) : EventGenerator<Document> {
3540

3641
fun head(new: (ElementCreator<HeadElement>.() -> Unit)? = null) : HeadElement {
3742
if (new != null) {
38-
new(ElementCreator(parent = head, insertBefore = null))
43+
val ec = ElementCreator(parent = head, insertBefore = null)
44+
new(ec)
45+
receiver.addCloseListener {
46+
ec.cleanup()
47+
}
3948
}
4049
return head
4150
}

0 commit comments

Comments
 (0)