feat: support loading the source code index whenever it's ready in file system (#530)

* feat: support loading index whenever it's ready

* fix test
r0.3
Meng Zhang 2023-10-10 21:35:20 -07:00 committed by GitHub
parent 5be9a6ae1b
commit 75d2944fb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 62 additions and 28 deletions

View File

@ -128,12 +128,12 @@ pub struct CompletionState {
impl CompletionState { impl CompletionState {
pub fn new( pub fn new(
engine: Arc<Box<dyn TextGeneration>>, engine: Arc<Box<dyn TextGeneration>>,
index_server: Option<Arc<IndexServer>>, index_server: Arc<IndexServer>,
prompt_template: Option<String>, prompt_template: Option<String>,
) -> Self { ) -> Self {
Self { Self {
engine, engine,
prompt_builder: prompt::PromptBuilder::new(prompt_template, index_server), prompt_builder: prompt::PromptBuilder::new(prompt_template, Some(index_server)),
} }
} }
} }

View File

@ -22,7 +22,7 @@ use tabby_common::{
use tabby_download::Downloader; use tabby_download::Downloader;
use tokio::time::sleep; use tokio::time::sleep;
use tower_http::{cors::CorsLayer, timeout::TimeoutLayer}; use tower_http::{cors::CorsLayer, timeout::TimeoutLayer};
use tracing::{debug, info, warn}; use tracing::{info, warn};
use utoipa::{openapi::ServerBuilder, OpenApi}; use utoipa::{openapi::ServerBuilder, OpenApi};
use utoipa_swagger_ui::SwaggerUi; use utoipa_swagger_ui::SwaggerUi;
@ -165,14 +165,7 @@ pub async fn main(config: &Config, args: &ServeArgs) {
} }
fn api_router(args: &ServeArgs) -> Router { fn api_router(args: &ServeArgs) -> Router {
let index_server = match IndexServer::load() { let index_server = Arc::new(IndexServer::new());
Ok(index_server) => Some(Arc::new(index_server)),
Err(err) => {
debug!("Load index failed due to `{}`", err);
None
}
};
let completion_state = { let completion_state = {
let ( let (
engine, engine,
@ -235,15 +228,12 @@ fn api_router(args: &ServeArgs) -> Router {
}) })
} }
if let Some(index_server) = index_server {
info!("Index is ready, enabling /v1beta/search API route");
routers.push({ routers.push({
Router::new().route( Router::new().route(
"/v1beta/search", "/v1beta/search",
routing::get(search::search).with_state(index_server), routing::get(search::search).with_state(index_server),
) )
}) });
}
let mut root = Router::new(); let mut root = Router::new();
for router in routers { for router in routers {

View File

@ -1,4 +1,4 @@
use std::sync::Arc; use std::{sync::Arc, time::Duration};
use anyhow::Result; use anyhow::Result;
use axum::{ use axum::{
@ -14,7 +14,8 @@ use tantivy::{
schema::Field, schema::Field,
DocAddress, Document, Index, IndexReader, DocAddress, Document, Index, IndexReader,
}; };
use tracing::instrument; use tokio::{sync::OnceCell, task, time::sleep};
use tracing::{debug, instrument, log::info};
use utoipa::{IntoParams, ToSchema}; use utoipa::{IntoParams, ToSchema};
#[derive(Deserialize, IntoParams)] #[derive(Deserialize, IntoParams)]
@ -60,9 +61,9 @@ pub struct HitDocument {
tag = "v1beta", tag = "v1beta",
responses( responses(
(status = 200, description = "Success" , body = SearchResponse, content_type = "application/json"), (status = 200, description = "Success" , body = SearchResponse, content_type = "application/json"),
(status = 405, description = "When code search is not enabled, the endpoint will returns 405 Method Not Allowed"), (status = 501, description = "When code search is not enabled, the endpoint will returns 501 Not Implemented"),
) )
)] )]
#[instrument(skip(state, query))] #[instrument(skip(state, query))]
pub async fn search( pub async fn search(
State(state): State<Arc<IndexServer>>, State(state): State<Arc<IndexServer>>,
@ -73,13 +74,13 @@ pub async fn search(
query.limit.unwrap_or(20), query.limit.unwrap_or(20),
query.offset.unwrap_or(0), query.offset.unwrap_or(0),
) else { ) else {
return Err(StatusCode::INTERNAL_SERVER_ERROR); return Err(StatusCode::NOT_IMPLEMENTED);
}; };
Ok(Json(serp)) Ok(Json(serp))
} }
pub struct IndexServer { struct IndexServerImpl {
reader: IndexReader, reader: IndexReader,
query_parser: QueryParser, query_parser: QueryParser,
@ -91,7 +92,7 @@ pub struct IndexServer {
field_name: Field, field_name: Field,
} }
impl IndexServer { impl IndexServerImpl {
pub fn load() -> Result<Self> { pub fn load() -> Result<Self> {
let index = Index::open_in_dir(path::index_dir())?; let index = Index::open_in_dir(path::index_dir())?;
index.register_tokenizer(); index.register_tokenizer();
@ -162,3 +163,46 @@ fn get_field(doc: &Document, field: Field) -> String {
.unwrap() .unwrap()
.to_owned() .to_owned()
} }
static IMPL: OnceCell<IndexServerImpl> = OnceCell::const_new();
pub struct IndexServer {}
impl IndexServer {
pub fn new() -> Self {
task::spawn(IMPL.get_or_init(|| async {
task::spawn(IndexServer::worker())
.await
.expect("Failed to create IndexServerImpl")
}));
Self {}
}
fn get_cell(&self) -> Option<&IndexServerImpl> {
IMPL.get()
}
async fn worker() -> IndexServerImpl {
loop {
match IndexServerImpl::load() {
Ok(index_server) => {
info!("Index is ready, enabling server...");
return index_server;
}
Err(err) => {
debug!("Source code index is not ready `{}`", err);
}
};
sleep(Duration::from_secs(60)).await;
}
}
pub fn search(&self, q: &str, limit: usize, offset: usize) -> tantivy::Result<SearchResponse> {
if let Some(imp) = self.get_cell() {
imp.search(q, limit, offset)
} else {
Err(tantivy::TantivyError::InternalError("Not Ready".to_owned()))
}
}
}