Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions idl/chromadb/proto/fn_consumer.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
syntax = "proto3";

package chroma;

message ListFnConsumerInProgressJobsRequest {}

message FnConsumerInProgressJobInfo {
string fn_id = 1;
int64 expires_at_epoch_secs = 2;
}

message ListFnConsumerInProgressJobsResponse {
repeated FnConsumerInProgressJobInfo jobs = 1;
}

service FnConsumer {
rpc ListInProgressJobs(ListFnConsumerInProgressJobsRequest) returns (ListFnConsumerInProgressJobsResponse) {}
}
1 change: 1 addition & 0 deletions rust/types/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
"idl/chromadb/proto/query_executor.proto",
"idl/chromadb/proto/garbage_collector.proto",
"idl/chromadb/proto/fault_injection.proto",
"idl/chromadb/proto/fn_consumer.proto",
"idl/chromadb/proto/workqueue.proto",
];

Expand Down
84 changes: 83 additions & 1 deletion rust/worker/src/fn_consumer/fn_consumer_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::panic::AssertUnwindSafe;
use std::pin::Pin;
use std::time::{Duration, SystemTime};
use thiserror::Error;
use tokio::sync::mpsc;
use tokio::sync::{mpsc, oneshot};
use tracing::{instrument, span};

use crate::compactor::config::CompactorConfig;
Expand Down Expand Up @@ -46,6 +46,35 @@ impl InProgressFn {
}
}

#[derive(Debug, PartialEq, Eq)]
pub struct InProgressFnEntry {
pub fn_id: AttachedFunctionUuid,
pub expires_at_epoch_secs: i64,
}

#[derive(Debug)]
pub struct ListInProgressJobsMessage {
pub response_tx: oneshot::Sender<Vec<InProgressFnEntry>>,
}

fn snapshot_in_progress_jobs(
in_progress: &HashMap<AttachedFunctionUuid, InProgressFn>,
) -> Vec<InProgressFnEntry> {
let mut entries: Vec<_> = in_progress
.iter()
.map(|(fn_id, job)| InProgressFnEntry {
fn_id: *fn_id,
expires_at_epoch_secs: job
.expires_at
.duration_since(std::time::UNIX_EPOCH)
.map(|duration| duration.as_secs() as i64)
.unwrap_or(0),
})
.collect();
entries.sort_unstable_by_key(|entry| entry.fn_id.to_string());
entries
}

#[derive(Error, Debug)]
pub enum DispatchError {
#[error("Dispatcher not initialized")]
Expand Down Expand Up @@ -592,12 +621,65 @@ impl Handler<ScheduledPollMessage> for FnConsumerManager {
}
}

#[async_trait]
impl Handler<ListInProgressJobsMessage> for FnConsumerManager {
type Result = ();

async fn handle(&mut self, message: ListInProgressJobsMessage, _ctx: &ComponentContext<Self>) {
let entries = snapshot_in_progress_jobs(&self.in_progress);
Comment thread
tanujnay112 marked this conversation as resolved.
if let Err(entries) = message.response_tx.send(entries) {
tracing::warn!(
job_count = entries.len(),
"Failed to send fn-consumer in-progress jobs response"
);
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use tokio::sync::oneshot;
use tokio::time::{timeout, Duration};

#[test]
fn snapshots_in_progress_jobs() {
let first_fn_id = AttachedFunctionUuid::new();
let second_fn_id = AttachedFunctionUuid::new();
let mut in_progress = HashMap::new();
in_progress.insert(
first_fn_id,
InProgressFn {
expires_at: std::time::UNIX_EPOCH + Duration::from_secs(20),
expiry_logged: false,
},
);
in_progress.insert(
second_fn_id,
InProgressFn {
expires_at: std::time::UNIX_EPOCH + Duration::from_secs(10),
expiry_logged: false,
},
);

let entries = snapshot_in_progress_jobs(&in_progress);
assert_eq!(entries.len(), 2);
assert!(entries
.windows(2)
.all(|pair| pair[0].fn_id.to_string() < pair[1].fn_id.to_string()));
assert!(entries
.iter()
.any(|entry| { entry.fn_id == first_fn_id && entry.expires_at_epoch_secs == 20 }));
assert!(entries
.iter()
.any(|entry| { entry.fn_id == second_fn_id && entry.expires_at_epoch_secs == 10 }));
}

#[test]
fn snapshots_empty_in_progress_jobs() {
assert!(snapshot_in_progress_jobs(&HashMap::new()).is_empty());
}

#[tokio::test]
async fn dispatch_awaiter_completes_later_tasks_while_one_is_running() {
let (task_tx, task_rx) = mpsc::channel(2);
Expand Down
51 changes: 51 additions & 0 deletions rust/worker/src/fn_consumer/grpc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use async_trait::async_trait;
use chroma_system::ComponentHandle;
use chroma_types::chroma_proto::{
fn_consumer_server::{FnConsumer, FnConsumerServer},
FnConsumerInProgressJobInfo, ListFnConsumerInProgressJobsRequest,
ListFnConsumerInProgressJobsResponse,
};
use tonic::{Request, Response, Status};

use super::fn_consumer_manager::{FnConsumerManager, ListInProgressJobsMessage};

pub struct FnConsumerGrpcServer {
manager: ComponentHandle<FnConsumerManager>,
}

impl FnConsumerGrpcServer {
pub fn new(manager: ComponentHandle<FnConsumerManager>) -> Self {
Self { manager }
}

pub fn into_service(self) -> FnConsumerServer<Self> {
FnConsumerServer::new(self)
}
}

#[async_trait]
impl FnConsumer for FnConsumerGrpcServer {
async fn list_in_progress_jobs(
&self,
_request: Request<ListFnConsumerInProgressJobsRequest>,
) -> Result<Response<ListFnConsumerInProgressJobsResponse>, Status> {
let (response_tx, response_rx) = tokio::sync::oneshot::channel();
self.manager
.receiver()
.send(ListInProgressJobsMessage { response_tx }, None)
.await
.map_err(|error| Status::internal(error.to_string()))?;

let jobs = response_rx
.await
.map_err(|error| Status::internal(format!("Failed to receive response: {error}")))?
.into_iter()
.map(|entry| FnConsumerInProgressJobInfo {
fn_id: entry.fn_id.to_string(),
expires_at_epoch_secs: entry.expires_at_epoch_secs,
})
.collect();

Ok(Response::new(ListFnConsumerInProgressJobsResponse { jobs }))
}
}
1 change: 1 addition & 0 deletions rust/worker/src/fn_consumer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod config;
pub mod fn_consumer_manager;
mod grpc;
pub mod server;

pub use server::fn_consumer_service_entrypoint;
12 changes: 10 additions & 2 deletions rust/worker/src/fn_consumer/server.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::config::RootConfig;
use crate::fn_consumer::fn_consumer_manager::FnConsumerManager;
use crate::fn_consumer::grpc::FnConsumerGrpcServer;
use crate::work_queue::work_queue_client::WorkQueueClient;
use chroma_blockstore::provider::BlockfileProvider;
use chroma_config::registry::Registry;
Expand Down Expand Up @@ -172,10 +173,16 @@ pub async fn fn_consumer_service_entrypoint() {
spann_provider,
);
manager.set_dispatcher(dispatcher_handle);
let _manager_handle = system.start_component(manager);
let manager_handle = system.start_component(manager);

// Create health service for readiness probe
let (_health_reporter, health_service) = tonic_health::server::health_reporter();
let (health_reporter, health_service) = tonic_health::server::health_reporter();
health_reporter
.set_serving::<chroma_types::chroma_proto::fn_consumer_server::FnConsumerServer<
FnConsumerGrpcServer,
>>()
.await;
let fn_consumer_service = FnConsumerGrpcServer::new(manager_handle).into_service();

let addr = format!("0.0.0.0:{}", service_config.my_port)
.parse()
Expand All @@ -186,6 +193,7 @@ pub async fn fn_consumer_service_entrypoint() {
// Start server (this blocks forever)
Server::builder()
.add_service(health_service)
.add_service(fn_consumer_service)
.serve(addr)
.await
.expect("Failed to start fn-consumer service");
Expand Down
Loading