mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-07-05 04:01:31 +02:00
Make connector pause and delete fast (#3646)
* first cut * refresh on delete --------- Co-authored-by: Richard Kuo (Danswer) <rkuo@onyx.app>
This commit is contained in:
@ -14,6 +14,7 @@ from onyx.connectors.interfaces import PollConnector
|
|||||||
from onyx.connectors.interfaces import SlimConnector
|
from onyx.connectors.interfaces import SlimConnector
|
||||||
from onyx.connectors.models import Document
|
from onyx.connectors.models import Document
|
||||||
from onyx.db.connector_credential_pair import get_connector_credential_pair
|
from onyx.db.connector_credential_pair import get_connector_credential_pair
|
||||||
|
from onyx.db.enums import ConnectorCredentialPairStatus
|
||||||
from onyx.db.enums import TaskStatus
|
from onyx.db.enums import TaskStatus
|
||||||
from onyx.db.models import TaskQueueState
|
from onyx.db.models import TaskQueueState
|
||||||
from onyx.indexing.indexing_heartbeat import IndexingHeartbeatInterface
|
from onyx.indexing.indexing_heartbeat import IndexingHeartbeatInterface
|
||||||
@ -41,15 +42,22 @@ def _get_deletion_status(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
redis_connector = RedisConnector(tenant_id, cc_pair.id)
|
redis_connector = RedisConnector(tenant_id, cc_pair.id)
|
||||||
if not redis_connector.delete.fenced:
|
if redis_connector.delete.fenced:
|
||||||
return None
|
|
||||||
|
|
||||||
return TaskQueueState(
|
return TaskQueueState(
|
||||||
task_id="",
|
task_id="",
|
||||||
task_name=redis_connector.delete.fence_key,
|
task_name=redis_connector.delete.fence_key,
|
||||||
status=TaskStatus.STARTED,
|
status=TaskStatus.STARTED,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if cc_pair.status == ConnectorCredentialPairStatus.DELETING:
|
||||||
|
return TaskQueueState(
|
||||||
|
task_id="",
|
||||||
|
task_name=redis_connector.delete.fence_key,
|
||||||
|
status=TaskStatus.PENDING,
|
||||||
|
)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_deletion_attempt_snapshot(
|
def get_deletion_attempt_snapshot(
|
||||||
connector_id: int,
|
connector_id: int,
|
||||||
|
@ -164,17 +164,12 @@ def update_cc_pair_status(
|
|||||||
db_session: Session = Depends(get_session),
|
db_session: Session = Depends(get_session),
|
||||||
tenant_id: str | None = Depends(get_current_tenant_id),
|
tenant_id: str | None = Depends(get_current_tenant_id),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
"""This method may wait up to 30 seconds if pausing the connector due to the need to
|
"""This method returns nearly immediately. It simply sets some signals and
|
||||||
terminate tasks in progress. Tasks are not guaranteed to terminate within the
|
optimistically assumes any running background processes will clean themselves up.
|
||||||
timeout.
|
This is done to improve the perceived end user experience.
|
||||||
|
|
||||||
Returns HTTPStatus.OK if everything finished.
|
Returns HTTPStatus.OK if everything finished.
|
||||||
Returns HTTPStatus.ACCEPTED if the connector is being paused, but background tasks
|
|
||||||
did not finish within the timeout.
|
|
||||||
"""
|
"""
|
||||||
WAIT_TIMEOUT = 15.0
|
|
||||||
still_terminating = False
|
|
||||||
|
|
||||||
cc_pair = get_connector_credential_pair_from_id(
|
cc_pair = get_connector_credential_pair_from_id(
|
||||||
cc_pair_id=cc_pair_id,
|
cc_pair_id=cc_pair_id,
|
||||||
db_session=db_session,
|
db_session=db_session,
|
||||||
@ -188,37 +183,17 @@ def update_cc_pair_status(
|
|||||||
detail="Connection not found for current user's permissions",
|
detail="Connection not found for current user's permissions",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
redis_connector = RedisConnector(tenant_id, cc_pair_id)
|
||||||
if status_update_request.status == ConnectorCredentialPairStatus.PAUSED:
|
if status_update_request.status == ConnectorCredentialPairStatus.PAUSED:
|
||||||
|
redis_connector.stop.set_fence(True)
|
||||||
|
|
||||||
search_settings_list: list[SearchSettings] = get_active_search_settings(
|
search_settings_list: list[SearchSettings] = get_active_search_settings(
|
||||||
db_session
|
db_session
|
||||||
)
|
)
|
||||||
|
|
||||||
redis_connector = RedisConnector(tenant_id, cc_pair_id)
|
|
||||||
|
|
||||||
try:
|
|
||||||
redis_connector.stop.set_fence(True)
|
|
||||||
while True:
|
while True:
|
||||||
logger.debug(
|
|
||||||
f"Wait for indexing soft termination starting: cc_pair={cc_pair_id}"
|
|
||||||
)
|
|
||||||
wait_succeeded = redis_connector.wait_for_indexing_termination(
|
|
||||||
search_settings_list, WAIT_TIMEOUT
|
|
||||||
)
|
|
||||||
if wait_succeeded:
|
|
||||||
logger.debug(
|
|
||||||
f"Wait for indexing soft termination succeeded: cc_pair={cc_pair_id}"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
"Wait for indexing soft termination timed out. "
|
|
||||||
f"Moving to hard termination: cc_pair={cc_pair_id} timeout={WAIT_TIMEOUT:.2f}"
|
|
||||||
)
|
|
||||||
|
|
||||||
for search_settings in search_settings_list:
|
for search_settings in search_settings_list:
|
||||||
redis_connector_index = redis_connector.new_index(
|
redis_connector_index = redis_connector.new_index(search_settings.id)
|
||||||
search_settings.id
|
|
||||||
)
|
|
||||||
if not redis_connector_index.fenced:
|
if not redis_connector_index.fenced:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -236,24 +211,8 @@ def update_cc_pair_status(
|
|||||||
# watchdog thread to kill the spawned task
|
# watchdog thread to kill the spawned task
|
||||||
redis_connector_index.set_terminate(index_payload.celery_task_id)
|
redis_connector_index.set_terminate(index_payload.celery_task_id)
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
f"Wait for indexing hard termination starting: cc_pair={cc_pair_id}"
|
|
||||||
)
|
|
||||||
wait_succeeded = redis_connector.wait_for_indexing_termination(
|
|
||||||
search_settings_list, WAIT_TIMEOUT
|
|
||||||
)
|
|
||||||
if wait_succeeded:
|
|
||||||
logger.debug(
|
|
||||||
f"Wait for indexing hard termination succeeded: cc_pair={cc_pair_id}"
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
logger.debug(
|
|
||||||
f"Wait for indexing hard termination timed out: cc_pair={cc_pair_id}"
|
|
||||||
)
|
|
||||||
still_terminating = True
|
|
||||||
break
|
|
||||||
finally:
|
|
||||||
redis_connector.stop.set_fence(False)
|
redis_connector.stop.set_fence(False)
|
||||||
|
|
||||||
update_connector_credential_pair_from_id(
|
update_connector_credential_pair_from_id(
|
||||||
@ -264,14 +223,6 @@ def update_cc_pair_status(
|
|||||||
|
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|
||||||
if still_terminating:
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=HTTPStatus.ACCEPTED,
|
|
||||||
content={
|
|
||||||
"message": "Request accepted, background task termination still in progress"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
return JSONResponse(
|
return JSONResponse(
|
||||||
status_code=HTTPStatus.OK, content={"message": str(HTTPStatus.OK)}
|
status_code=HTTPStatus.OK, content={"message": str(HTTPStatus.OK)}
|
||||||
)
|
)
|
||||||
|
@ -8,7 +8,13 @@ import { deleteCCPair } from "@/lib/documentDeletion";
|
|||||||
import { mutate } from "swr";
|
import { mutate } from "swr";
|
||||||
import { buildCCPairInfoUrl } from "./lib";
|
import { buildCCPairInfoUrl } from "./lib";
|
||||||
|
|
||||||
export function DeletionButton({ ccPair }: { ccPair: CCPairFullInfo }) {
|
export function DeletionButton({
|
||||||
|
ccPair,
|
||||||
|
refresh,
|
||||||
|
}: {
|
||||||
|
ccPair: CCPairFullInfo;
|
||||||
|
refresh: () => void;
|
||||||
|
}) {
|
||||||
const { popup, setPopup } = usePopup();
|
const { popup, setPopup } = usePopup();
|
||||||
|
|
||||||
const isDeleting =
|
const isDeleting =
|
||||||
@ -31,14 +37,22 @@ export function DeletionButton({ ccPair }: { ccPair: CCPairFullInfo }) {
|
|||||||
{popup}
|
{popup}
|
||||||
<Button
|
<Button
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() =>
|
onClick={async () => {
|
||||||
deleteCCPair(
|
try {
|
||||||
|
// Await the delete operation to ensure it completes
|
||||||
|
await deleteCCPair(
|
||||||
ccPair.connector.id,
|
ccPair.connector.id,
|
||||||
ccPair.credential.id,
|
ccPair.credential.id,
|
||||||
setPopup,
|
setPopup,
|
||||||
() => mutate(buildCCPairInfoUrl(ccPair.id))
|
() => mutate(buildCCPairInfoUrl(ccPair.id))
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// Call refresh to update the state after deletion
|
||||||
|
refresh();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting connector:", error);
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
icon={FiTrash}
|
icon={FiTrash}
|
||||||
disabled={
|
disabled={
|
||||||
ccPair.status === ConnectorCredentialPairStatus.ACTIVE || isDeleting
|
ccPair.status === ConnectorCredentialPairStatus.ACTIVE || isDeleting
|
||||||
|
@ -362,7 +362,7 @@ function Main({ ccPairId }: { ccPairId: number }) {
|
|||||||
<div className="flex mt-4">
|
<div className="flex mt-4">
|
||||||
<div className="mx-auto">
|
<div className="mx-auto">
|
||||||
{ccPair.is_editable_for_current_user && (
|
{ccPair.is_editable_for_current_user && (
|
||||||
<DeletionButton ccPair={ccPair} />
|
<DeletionButton ccPair={ccPair} refresh={refresh} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user