diff --git a/pageserver/src/http/routes.rs b/pageserver/src/http/routes.rs index 4f4c397abe..1c5eacd362 100644 --- a/pageserver/src/http/routes.rs +++ b/pageserver/src/http/routes.rs @@ -738,17 +738,17 @@ async fn timeline_compact_handler(request: Request) -> Result = result_receiver + .await + .context("receive compaction result") + .map_err(ApiError::InternalServerError)?; + result.map_err(ApiError::InternalServerError)?; + json_response(StatusCode::OK, ()) } diff --git a/pageserver/src/tenant/mgr.rs b/pageserver/src/tenant/mgr.rs index af7794490a..dce7cd8bae 100644 --- a/pageserver/src/tenant/mgr.rs +++ b/pageserver/src/tenant/mgr.rs @@ -492,3 +492,53 @@ pub async fn immediate_gc( Ok(wait_task_done) } + +#[cfg(feature = "testing")] +pub async fn immediate_compact( + tenant_id: TenantId, + timeline_id: TimelineId, +) -> Result>, ApiError> { + let guard = TENANTS.read().await; + + let tenant = guard + .get(&tenant_id) + .map(Arc::clone) + .with_context(|| format!("Tenant {tenant_id} not found")) + .map_err(ApiError::NotFound)?; + + let timeline = tenant + .get_timeline(timeline_id, true) + .map_err(ApiError::NotFound)?; + + // Run in task_mgr to avoid race with detach operation + let (task_done, wait_task_done) = tokio::sync::oneshot::channel(); + task_mgr::spawn( + &tokio::runtime::Handle::current(), + TaskKind::Compaction, + Some(tenant_id), + Some(timeline_id), + &format!( + "timeline_compact_handler compaction run for tenant {tenant_id} timeline {timeline_id}" + ), + false, + async move { + let result = timeline + .compact() + .instrument( + info_span!("manual_compact", tenant = %tenant_id, timeline = %timeline_id), + ) + .await; + + match task_done.send(result) { + Ok(_) => (), + Err(result) => error!("failed to send compaction result: {result:?}"), + } + Ok(()) + }, + ); + + // drop the guard until after we've spawned the task so that timeline shutdown will wait for the task + drop(guard); + + Ok(wait_task_done) +}