|
42 | 42 | ) |
43 | 43 | from dstack._internal.core.models.projects import Project |
44 | 44 | from dstack._internal.core.models.resources import ResourcesSpec |
45 | | -from dstack._internal.core.models.runs import JobProvisioningData, Requirements, get_policy_map |
| 45 | +from dstack._internal.core.models.runs import ( |
| 46 | + JobProvisioningData, |
| 47 | + Requirements, |
| 48 | + RunStatus, |
| 49 | + get_policy_map, |
| 50 | +) |
46 | 51 | from dstack._internal.core.models.users import GlobalRole |
47 | 52 | from dstack._internal.core.services import validate_dstack_resource_name |
48 | 53 | from dstack._internal.core.services.diff import ModelDiff, copy_model, diff_models |
|
53 | 58 | JobModel, |
54 | 59 | MemberModel, |
55 | 60 | ProjectModel, |
| 61 | + RunModel, |
56 | 62 | UserModel, |
57 | 63 | ) |
58 | 64 | from dstack._internal.server.services import events |
@@ -613,48 +619,56 @@ async def delete_fleets( |
613 | 619 | instance_nums: Optional[List[int]] = None, |
614 | 620 | ): |
615 | 621 | res = await session.execute( |
616 | | - select(FleetModel) |
| 622 | + select(FleetModel.id) |
617 | 623 | .where( |
618 | 624 | FleetModel.project_id == project.id, |
619 | 625 | FleetModel.name.in_(names), |
620 | 626 | FleetModel.deleted == False, |
621 | 627 | ) |
622 | | - .options(joinedload(FleetModel.instances)) |
| 628 | + .order_by(FleetModel.id) # take locks in order |
| 629 | + .with_for_update(key_share=True) |
623 | 630 | ) |
624 | | - fleet_models = res.scalars().unique().all() |
625 | | - fleets_ids = sorted([f.id for f in fleet_models]) |
626 | | - instances_ids = sorted([i.id for f in fleet_models for i in f.instances]) |
627 | | - await session.commit() |
628 | | - logger.info("Deleting fleets: %s", [v.name for v in fleet_models]) |
| 631 | + fleets_ids = list(res.scalars().unique().all()) |
| 632 | + res = await session.execute( |
| 633 | + select(InstanceModel.id) |
| 634 | + .where( |
| 635 | + InstanceModel.fleet_id.in_(fleets_ids), |
| 636 | + InstanceModel.deleted == False, |
| 637 | + ) |
| 638 | + .order_by(InstanceModel.id) # take locks in order |
| 639 | + .with_for_update(key_share=True) |
| 640 | + ) |
| 641 | + instances_ids = list(res.scalars().unique().all()) |
| 642 | + if is_db_sqlite(): |
| 643 | + # Start new transaction to see committed changes after lock |
| 644 | + await session.commit() |
629 | 645 | async with ( |
630 | 646 | get_locker(get_db().dialect_name).lock_ctx(FleetModel.__tablename__, fleets_ids), |
631 | 647 | get_locker(get_db().dialect_name).lock_ctx(InstanceModel.__tablename__, instances_ids), |
632 | 648 | ): |
633 | | - # Refetch after lock |
634 | | - # TODO: Lock instances with FOR UPDATE? |
635 | | - # TODO: Do not lock fleet when deleting only instances |
| 649 | + # Refetch after lock. |
| 650 | + # TODO: Do not lock fleet when deleting only instances. |
636 | 651 | res = await session.execute( |
637 | 652 | select(FleetModel) |
638 | | - .where( |
639 | | - FleetModel.project_id == project.id, |
640 | | - FleetModel.name.in_(names), |
641 | | - FleetModel.deleted == False, |
642 | | - ) |
| 653 | + .where(FleetModel.id.in_(fleets_ids)) |
643 | 654 | .options( |
644 | | - selectinload(FleetModel.instances) |
| 655 | + joinedload(FleetModel.instances.and_(InstanceModel.id.in_(instances_ids))) |
645 | 656 | .joinedload(InstanceModel.jobs) |
646 | 657 | .load_only(JobModel.id) |
647 | 658 | ) |
648 | | - .options(selectinload(FleetModel.runs)) |
| 659 | + .options( |
| 660 | + joinedload( |
| 661 | + FleetModel.runs.and_(RunModel.status.not_in(RunStatus.finished_statuses())) |
| 662 | + ) |
| 663 | + ) |
649 | 664 | .execution_options(populate_existing=True) |
650 | | - .order_by(FleetModel.id) # take locks in order |
651 | | - .with_for_update(key_share=True) |
652 | 665 | ) |
653 | 666 | fleet_models = res.scalars().unique().all() |
654 | 667 | fleets = [fleet_model_to_fleet(m) for m in fleet_models] |
655 | 668 | for fleet in fleets: |
656 | 669 | if fleet.spec.configuration.ssh_config is not None: |
657 | 670 | _check_can_manage_ssh_fleets(user=user, project=project) |
| 671 | + logger.info("Deleting fleets: %s", [f.name for f in fleet_models]) |
658 | 672 | for fleet_model in fleet_models: |
659 | 673 | _terminate_fleet_instances(fleet_model=fleet_model, instance_nums=instance_nums) |
660 | 674 | # TERMINATING fleets are deleted by process_fleets after instances are terminated |
|
0 commit comments