Skip to content

Commit 30b776a

Browse files
committed
feat: implement idle session pruning in TaskManager
- Add `session_idle_timeout` to `TaskManager` (default 60s). - Implement `prune_sessions` to automatically close and remove idle sessions. - Add test case to verify session cleanup after inactivity.
1 parent f15ad3f commit 30b776a

3 files changed

Lines changed: 78 additions & 9 deletions

File tree

R/languageserver.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ LanguageServer <- R6::R6Class("LanguageServer",
5555

5656
self$parse_task_manager <- TaskManager$new(
5757
"parse",
58-
use_session = TRUE, process_recent_first = TRUE,
58+
use_session = TRUE, process_recent_first = TRUE
5959
)
6060
self$diagnostics_task_manager <- TaskManager$new(
6161
"diagnostics",

R/task.R

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,15 @@ Task <- R6::R6Class("Task",
4949
return(TRUE)
5050
}
5151
}
52-
return(private$session$get_state() == "finished")
52+
state <- private$session$get_state()
53+
if (identical(state, "finished")) {
54+
if (!is.null(private$error)) {
55+
err <- simpleError("Session finished unexpectedly while task was running")
56+
private$error(err)
57+
}
58+
return(TRUE)
59+
}
60+
return(FALSE)
5361
}
5462

5563
if (is.null(private$process)) {
@@ -98,6 +106,7 @@ TaskManager <- R6::R6Class("TaskManager",
98106
sessions = NULL,
99107
process_recent_first = NULL,
100108
max_running_tasks = NULL,
109+
session_idle_timeout = NULL,
101110
find_or_create_session = function() {
102111
if (!isTRUE(private$use_session)) {
103112
return(NULL)
@@ -127,20 +136,41 @@ TaskManager <- R6::R6Class("TaskManager",
127136
}
128137

129138
NULL
139+
},
140+
prune_sessions = function() {
141+
for (i in rev(seq_along(private$sessions))) {
142+
session <- private$sessions[[i]]
143+
state <- session$get_state()
144+
if (state == "finished") {
145+
private$sessions[[i]] <- NULL
146+
} else if (state == "idle") {
147+
idle_start <- attr(session, "idle_start")
148+
if (is.null(idle_start)) {
149+
attr(session, "idle_start") <- Sys.time()
150+
} else if (as.numeric(difftime(Sys.time(), idle_start, units = "secs")) > private$session_idle_timeout) {
151+
session$close()
152+
private$sessions[[i]] <- NULL
153+
}
154+
} else {
155+
attr(session, "idle_start") <- NULL
156+
}
157+
}
130158
}
131159
),
132160
public = list(
133161
initialize = function(name,
134162
use_session = FALSE,
135163
process_recent_first = FALSE,
136164
cpu_load = 0.5,
137-
max_running_tasks = 8) {
165+
max_running_tasks = 8,
166+
session_idle_timeout = 60) {
138167
private$pending_tasks <- collections::ordered_dict()
139168
private$running_tasks <- collections::ordered_dict()
140169
private$name <- name
141170
private$use_session <- use_session
142171
private$process_recent_first <- process_recent_first
143172

173+
private$session_idle_timeout <- session_idle_timeout
144174
cpus <- min(parallel::detectCores())
145175
max_running_tasks <- min(cpus, max_running_tasks)
146176
private$max_running_tasks <- max(min(max_running_tasks, round(cpus * cpu_load)), 1)
@@ -167,11 +197,6 @@ TaskManager <- R6::R6Class("TaskManager",
167197
pending_ids <- pending_ids[seq_len(n)]
168198
}
169199

170-
if (isTRUE(private$use_session)) {
171-
states <- vapply(private$sessions, function(s) s$get_state(), character(1))
172-
private$sessions <- private$sessions[states != "finished"]
173-
}
174-
175200
for (id in pending_ids) {
176201
task <- private$pending_tasks$get(id)
177202
if (Sys.time() - task$time >= task$delay) {
@@ -205,6 +230,9 @@ TaskManager <- R6::R6Class("TaskManager",
205230
running_tasks$remove(key)
206231
}
207232
}
233+
if (isTRUE(private$use_session)) {
234+
private$prune_sessions()
235+
}
208236
},
209237
stop = function() {
210238
for (id in private$running_tasks$keys()) {

tests/testthat/test-task.R

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ for (covr in c("false", "true")) {
1717
tm$add_task("t1", task)
1818

1919
tm$run_tasks()
20-
20+
2121
# Wait for the task to finish
2222
for (i in 1:10) {
2323
Sys.sleep(0.5)
@@ -100,3 +100,44 @@ for (covr in c("false", "true")) {
100100
})
101101
})
102102
}
103+
104+
test_that("TaskManager prunes idle sessions", {
105+
skip_on_cran()
106+
107+
# Initialize TaskManager with a short timeout
108+
tm <- TaskManager$new("test", use_session = TRUE, session_idle_timeout = 2)
109+
110+
# Create a dummy task
111+
task <- create_task(function() 1, list())
112+
tm$add_task("1", task)
113+
114+
# Run the task
115+
tm$run_tasks()
116+
117+
# Wait for task completion
118+
start_time <- Sys.time()
119+
while (length(tm$.__enclos_env__$private$running_tasks$keys()) > 0 ||
120+
length(tm$.__enclos_env__$private$pending_tasks$keys()) > 0) {
121+
tm$check_tasks()
122+
tm$run_tasks()
123+
if (Sys.time() - start_time > 10) stop("Task timed out")
124+
Sys.sleep(0.1)
125+
}
126+
127+
# Verify session is idle
128+
sessions <- tm$.__enclos_env__$private$sessions
129+
expect_length(sessions, 1)
130+
expect_equal(sessions[[1]]$get_state(), "idle")
131+
132+
# Wait for timeout
133+
Sys.sleep(5)
134+
135+
# Trigger pruning
136+
tm$check_tasks()
137+
138+
# Verify session is removed
139+
sessions <- tm$.__enclos_env__$private$sessions
140+
expect_length(sessions, 0)
141+
142+
tm$stop()
143+
})

0 commit comments

Comments
 (0)