<commit_before>// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use borrow_check::nll::region_infer::Cause;
use borrow_check::nll::type_check::AtLocation;
use dataflow::move_paths::{HasMoveData, MoveData};
use dataflow::MaybeInitializedPlaces;
use dataflow::{FlowAtLocation, FlowsAtLocation};
use rustc::infer::region_constraints::RegionConstraintData;
use rustc::mir::Local;
use rustc::mir::{BasicBlock, Location, Mir};
use rustc::ty::subst::Kind;
use rustc::ty::{Ty, TypeFoldable};
use rustc_data_structures::fx::FxHashMap;
use std::rc::Rc;
use syntax::codemap::DUMMY_SP;
use util::liveness::LivenessResults;

use super::TypeChecker;

/// Combines liveness analysis with initialization analysis to
/// determine which variables are live at which points, both due to
/// ordinary uses and drops. Returns a set of (ty, location) pairs
/// that indicate which types must be live at which point in the CFG.
/// This vector is consumed by `constraint_generation`.
///
/// NB. This computation requires normalization; therefore, it must be
/// performed before
pub(super) fn generate<'gcx, 'tcx>(
    cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
    mir: &Mir<'tcx>,
    liveness: &LivenessResults,
    flow_inits: &mut FlowAtLocation<MaybeInitializedPlaces<'_, 'gcx, 'tcx>>,
    move_data: &MoveData<'tcx>,
) {
    let mut generator = TypeLivenessGenerator {
        cx,
        mir,
        liveness,
        flow_inits,
        move_data,
        drop_data: FxHashMap(),
    };

    for bb in mir.basic_blocks().indices() {
        generator.add_liveness_constraints(bb);
    }
}

struct TypeLivenessGenerator<'gen, 'typeck, 'flow, 'gcx, 'tcx>
where
    'typeck: 'gen,
    'flow: 'gen,
    'tcx: 'typeck + 'flow,
    'gcx: 'tcx,
{
    cx: &'gen mut TypeChecker<'typeck, 'gcx, 'tcx>,
    mir: &'gen Mir<'tcx>,
    liveness: &'gen LivenessResults,
    flow_inits: &'gen mut FlowAtLocation<MaybeInitializedPlaces<'flow, 'gcx, 'tcx>>,
    move_data: &'gen MoveData<'tcx>,
    drop_data: FxHashMap<Local, DropData<'tcx>>,
}

struct DropData<'tcx> {
    dropped_kinds: Vec<Kind<'tcx>>,
    region_constraint_data: Option<Rc<RegionConstraintData<'tcx>>>,
}

impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flow, 'gcx, 'tcx> {
    /// Liveness constraints:
    ///
    /// > If a variable V is live at point P, then all regions R in the type of V
    /// > must include the point P.
    fn add_liveness_constraints(&mut self, bb: BasicBlock) {
        debug!("add_liveness_constraints(bb={:?})", bb);

        self.liveness
            .regular
            .simulate_block(self.mir, bb, |location, live_locals| {
                for live_local in live_locals.iter() {
                    let live_local_ty = self.mir.local_decls[live_local].ty;
                    let cause = Cause::LiveVar(live_local, location);
                    Self::push_type_live_constraint(&mut self.cx, live_local_ty, location, cause);
                }
            });

        let mut all_live_locals: Vec<(Location, Vec<Local>)> = vec![];
        self.liveness
            .drop
            .simulate_block(self.mir, bb, |location, live_locals| {
                all_live_locals.push((location, live_locals.iter().collect()));
            });
        debug!(
            "add_liveness_constraints: all_live_locals={:#?}",
            all_live_locals
        );

        let terminator_index = self.mir.basic_blocks()[bb].statements.len();
        self.flow_inits.reset_to_entry_of(bb);
        while let Some((location, live_locals)) = all_live_locals.pop() {
            for live_local in live_locals {
                debug!(
                    "add_liveness_constraints: location={:?} live_local={:?}",
                    location, live_local
                );

                if log_enabled!(::log::Level::Debug) {
                    self.flow_inits.each_state_bit(|mpi_init| {
                        debug!(
                            "add_liveness_constraints: location={:?} initialized={:?}",
                            location,
                            &self.flow_inits.operator().move_data().move_paths[mpi_init]
                        );
                    });
                }

                let mpi = self.move_data.rev_lookup.find_local(live_local);
                if let Some(initialized_child) = self.flow_inits.has_any_child_of(mpi) {
                    debug!(
                        "add_liveness_constraints: mpi={:?} has initialized child {:?}",
                        self.move_data.move_paths[mpi],
                        self.move_data.move_paths[initialized_child]
                    );

                    let live_local_ty = self.mir.local_decls[live_local].ty;
                    self.add_drop_live_constraint(live_local, live_local_ty, location);
                }
            }

            if location.statement_index == terminator_index {
                debug!(
                    "add_liveness_constraints: reconstruct_terminator_effect from {:#?}",
                    location
                );
                self.flow_inits.reconstruct_terminator_effect(location);
            } else {
                debug!(
                    "add_liveness_constraints: reconstruct_statement_effect from {:#?}",
                    location
                );
                self.flow_inits.reconstruct_statement_effect(location);
            }
            self.flow_inits.apply_local_effect(location);
        }
    }

    /// Some variable with type `live_ty` is "regular live" at
    /// `location` -- i.e., it may be used later. This means that all
    /// regions appearing in the type `live_ty` must be live at
    /// `location`.
    fn push_type_live_constraint<T>(
        cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
        value: T,
        location: Location,
        cause: Cause,
    ) where
        T: TypeFoldable<'tcx>,
    {
        debug!(
            "push_type_live_constraint(live_ty={:?}, location={:?})",
            value, location
        );

        cx.tcx().for_each_free_region(&value, |live_region| {
            cx
                .constraints
                .liveness_set
                .push((live_region, location, cause.clone()));
        });
    }

    /// Some variable with type `live_ty` is "drop live" at `location`
    /// -- i.e., it may be dropped later. This means that *some* of
    /// the regions in its type must be live at `location`. The
    /// precise set will depend on the dropck constraints, and in
    /// particular this takes `#[may_dangle]` into account.
    #[inline(never)]
    fn add_drop_live_constraint(
        &mut self,
        dropped_local: Local,
        dropped_ty: Ty<'tcx>,
        location: Location,
    ) {
        debug!(
            "add_drop_live_constraint(dropped_local={:?}, dropped_ty={:?}, location={:?})",
            dropped_local, dropped_ty, location
        );

        let drop_data = self.drop_data.entry(dropped_local).or_insert_with({
            let cx = &mut self.cx;
            move || Self::compute_drop_data(cx, dropped_ty)
        });

        if let Some(data) = &drop_data.region_constraint_data {
            self.cx
                .push_region_constraints(location.at_self(), data.clone());
        }

        // All things in the `outlives` array may be touched by
        // the destructor and must be live at this point.
        let cause = Cause::DropVar(dropped_local, location);
        for &kind in &drop_data.dropped_kinds {
            Self::push_type_live_constraint(&mut self.cx, kind, location, cause);
        }
    }

    #[inline(never)]
    fn compute_drop_data(
        cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
        dropped_ty: Ty<'tcx>,
    ) -> DropData<'tcx> {
        debug!("compute_drop_data(dropped_ty={:?})", dropped_ty,);

        let (dropped_kinds, region_constraint_data) =
            cx.fully_perform_op_and_get_region_constraint_data(
                || format!("compute_drop_data(dropped_ty={:?})", dropped_ty),
                |cx| {
                    // crappy span, but I don't think it really matters
                    let span = DUMMY_SP;
                    Ok(cx
                        .infcx
                        .at(&cx.misc(span), cx.param_env)
                        .dropck_outlives(dropped_ty))
                },
            ).unwrap();

        DropData {
            dropped_kinds,
            region_constraint_data,
        }
    }
}
<commit_msg>key drop-data computation by ty, not var<commit_after>// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use borrow_check::nll::region_infer::Cause;
use borrow_check::nll::type_check::AtLocation;
use dataflow::move_paths::{HasMoveData, MoveData};
use dataflow::MaybeInitializedPlaces;
use dataflow::{FlowAtLocation, FlowsAtLocation};
use rustc::infer::region_constraints::RegionConstraintData;
use rustc::mir::Local;
use rustc::mir::{BasicBlock, Location, Mir};
use rustc::ty::subst::Kind;
use rustc::ty::{Ty, TypeFoldable};
use rustc_data_structures::fx::FxHashMap;
use std::rc::Rc;
use syntax::codemap::DUMMY_SP;
use util::liveness::LivenessResults;

use super::TypeChecker;

/// Combines liveness analysis with initialization analysis to
/// determine which variables are live at which points, both due to
/// ordinary uses and drops. Returns a set of (ty, location) pairs
/// that indicate which types must be live at which point in the CFG.
/// This vector is consumed by `constraint_generation`.
///
/// NB. This computation requires normalization; therefore, it must be
/// performed before
pub(super) fn generate<'gcx, 'tcx>(
    cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
    mir: &Mir<'tcx>,
    liveness: &LivenessResults,
    flow_inits: &mut FlowAtLocation<MaybeInitializedPlaces<'_, 'gcx, 'tcx>>,
    move_data: &MoveData<'tcx>,
) {
    let mut generator = TypeLivenessGenerator {
        cx,
        mir,
        liveness,
        flow_inits,
        move_data,
        drop_data: FxHashMap(),
    };

    for bb in mir.basic_blocks().indices() {
        generator.add_liveness_constraints(bb);
    }
}

struct TypeLivenessGenerator<'gen, 'typeck, 'flow, 'gcx, 'tcx>
where
    'typeck: 'gen,
    'flow: 'gen,
    'tcx: 'typeck + 'flow,
    'gcx: 'tcx,
{
    cx: &'gen mut TypeChecker<'typeck, 'gcx, 'tcx>,
    mir: &'gen Mir<'tcx>,
    liveness: &'gen LivenessResults,
    flow_inits: &'gen mut FlowAtLocation<MaybeInitializedPlaces<'flow, 'gcx, 'tcx>>,
    move_data: &'gen MoveData<'tcx>,
    drop_data: FxHashMap<Ty<'tcx>, DropData<'tcx>>,
}

struct DropData<'tcx> {
    dropped_kinds: Vec<Kind<'tcx>>,
    region_constraint_data: Option<Rc<RegionConstraintData<'tcx>>>,
}

impl<'gen, 'typeck, 'flow, 'gcx, 'tcx> TypeLivenessGenerator<'gen, 'typeck, 'flow, 'gcx, 'tcx> {
    /// Liveness constraints:
    ///
    /// > If a variable V is live at point P, then all regions R in the type of V
    /// > must include the point P.
    fn add_liveness_constraints(&mut self, bb: BasicBlock) {
        debug!("add_liveness_constraints(bb={:?})", bb);

        self.liveness
            .regular
            .simulate_block(self.mir, bb, |location, live_locals| {
                for live_local in live_locals.iter() {
                    let live_local_ty = self.mir.local_decls[live_local].ty;
                    let cause = Cause::LiveVar(live_local, location);
                    Self::push_type_live_constraint(&mut self.cx, live_local_ty, location, cause);
                }
            });

        let mut all_live_locals: Vec<(Location, Vec<Local>)> = vec![];
        self.liveness
            .drop
            .simulate_block(self.mir, bb, |location, live_locals| {
                all_live_locals.push((location, live_locals.iter().collect()));
            });
        debug!(
            "add_liveness_constraints: all_live_locals={:#?}",
            all_live_locals
        );

        let terminator_index = self.mir.basic_blocks()[bb].statements.len();
        self.flow_inits.reset_to_entry_of(bb);
        while let Some((location, live_locals)) = all_live_locals.pop() {
            for live_local in live_locals {
                debug!(
                    "add_liveness_constraints: location={:?} live_local={:?}",
                    location, live_local
                );

                if log_enabled!(::log::Level::Debug) {
                    self.flow_inits.each_state_bit(|mpi_init| {
                        debug!(
                            "add_liveness_constraints: location={:?} initialized={:?}",
                            location,
                            &self.flow_inits.operator().move_data().move_paths[mpi_init]
                        );
                    });
                }

                let mpi = self.move_data.rev_lookup.find_local(live_local);
                if let Some(initialized_child) = self.flow_inits.has_any_child_of(mpi) {
                    debug!(
                        "add_liveness_constraints: mpi={:?} has initialized child {:?}",
                        self.move_data.move_paths[mpi],
                        self.move_data.move_paths[initialized_child]
                    );

                    let live_local_ty = self.mir.local_decls[live_local].ty;
                    self.add_drop_live_constraint(live_local, live_local_ty, location);
                }
            }

            if location.statement_index == terminator_index {
                debug!(
                    "add_liveness_constraints: reconstruct_terminator_effect from {:#?}",
                    location
                );
                self.flow_inits.reconstruct_terminator_effect(location);
            } else {
                debug!(
                    "add_liveness_constraints: reconstruct_statement_effect from {:#?}",
                    location
                );
                self.flow_inits.reconstruct_statement_effect(location);
            }
            self.flow_inits.apply_local_effect(location);
        }
    }

    /// Some variable with type `live_ty` is "regular live" at
    /// `location` -- i.e., it may be used later. This means that all
    /// regions appearing in the type `live_ty` must be live at
    /// `location`.
    fn push_type_live_constraint<T>(
        cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
        value: T,
        location: Location,
        cause: Cause,
    ) where
        T: TypeFoldable<'tcx>,
    {
        debug!(
            "push_type_live_constraint(live_ty={:?}, location={:?})",
            value, location
        );

        cx.tcx().for_each_free_region(&value, |live_region| {
            cx
                .constraints
                .liveness_set
                .push((live_region, location, cause.clone()));
        });
    }

    /// Some variable with type `live_ty` is "drop live" at `location`
    /// -- i.e., it may be dropped later. This means that *some* of
    /// the regions in its type must be live at `location`. The
    /// precise set will depend on the dropck constraints, and in
    /// particular this takes `#[may_dangle]` into account.
    #[inline(never)]
    fn add_drop_live_constraint(
        &mut self,
        dropped_local: Local,
        dropped_ty: Ty<'tcx>,
        location: Location,
    ) {
        debug!(
            "add_drop_live_constraint(dropped_local={:?}, dropped_ty={:?}, location={:?})",
            dropped_local, dropped_ty, location
        );

        let drop_data = self.drop_data.entry(dropped_ty).or_insert_with({
            let cx = &mut self.cx;
            move || Self::compute_drop_data(cx, dropped_ty)
        });

        if let Some(data) = &drop_data.region_constraint_data {
            self.cx
                .push_region_constraints(location.at_self(), data.clone());
        }

        // All things in the `outlives` array may be touched by
        // the destructor and must be live at this point.
        let cause = Cause::DropVar(dropped_local, location);
        for &kind in &drop_data.dropped_kinds {
            Self::push_type_live_constraint(&mut self.cx, kind, location, cause);
        }
    }

    #[inline(never)]
    fn compute_drop_data(
        cx: &mut TypeChecker<'_, 'gcx, 'tcx>,
        dropped_ty: Ty<'tcx>,
    ) -> DropData<'tcx> {
        debug!("compute_drop_data(dropped_ty={:?})", dropped_ty,);

        let (dropped_kinds, region_constraint_data) =
            cx.fully_perform_op_and_get_region_constraint_data(
                || format!("compute_drop_data(dropped_ty={:?})", dropped_ty),
                |cx| {
                    // crappy span, but I don't think it really matters
                    let span = DUMMY_SP;
                    Ok(cx
                        .infcx
                        .at(&cx.misc(span), cx.param_env)
                        .dropck_outlives(dropped_ty))
                },
            ).unwrap();

        DropData {
            dropped_kinds,
            region_constraint_data,
        }
    }
}
<|endoftext|>


<commit_before>
extern crate amqp_client0 as amqp;

use amqp::ConfigBuilder;

fn main() {
    let mut session = ConfigBuilder::without_tls()
        .port(5672)
        .plain_auth("", "")
        .channel_max(10)
        .virtual_host("/")
        .into_blocking();

    print!("{:#?}", session);
    {
        let start1 = session.connect().unwrap();
        print!("Start1: {:?}", start1);
    }

    let start2 = session.connect().unwrap();
    print!("Start2: {:?}", start2);
}<commit_msg>[client] Temporarily disable connect example<commit_after>
extern crate amqp_client0 as amqp;

use amqp::ConfigBuilder;

fn main() {
    let mut session = ConfigBuilder::without_tls()
        .port(5672)
        .plain_auth("", "")
        .channel_max(10)
        .virtual_host("/")
        .into_blocking();

    /*
    print!("{:#?}", session);
    {
        let start1 = session.connect().unwrap();
        print!("Start1: {:?}", start1);
    }

    let start2 = session.connect().unwrap();
    print!("Start2: {:?}", start2);
    */
}<|endoftext|>


<commit_before>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::db_metadata::DbMetadataSchema;
use crate::pruner::state_store::generics::StaleNodeIndexSchemaTrait;
use crate::schema::db_metadata::DbMetadataValue;
use crate::{
    jellyfish_merkle_node::JellyfishMerkleNodeSchema, metrics::PRUNER_LEAST_READABLE_VERSION,
    pruner::db_pruner::DBPruner, utils, StaleNodeIndexCrossEpochSchema, OTHER_TIMERS_SECONDS,
};
use anyhow::Result;
use aptos_infallible::Mutex;
use aptos_jellyfish_merkle::node_type::NodeKey;
use aptos_jellyfish_merkle::StaleNodeIndex;
use aptos_logger::error;
use aptos_types::transaction::{AtomicVersion, Version};
use schemadb::schema::KeyCodec;
use schemadb::{ReadOptions, SchemaBatch, DB};
use std::sync::{atomic::Ordering, Arc};

pub mod generics;
pub(crate) mod state_value_pruner;

#[cfg(test)]
mod test;

pub const STATE_MERKLE_PRUNER_NAME: &str = "state_merkle_pruner";

/// Responsible for pruning the state tree.
#[derive(Debug)]
pub struct StateMerklePruner<S> {
    /// State DB.
    state_merkle_db: Arc<DB>,
    /// Keeps track of the target version that the pruner needs to achieve.
    target_version: AtomicVersion,
    /// 1. min readable version
    /// 2. if things before that version fully cleaned
    progress: Mutex<(Version, bool)>,
    _phantom: std::marker::PhantomData<S>,
}

impl<S: StaleNodeIndexSchemaTrait> DBPruner for StateMerklePruner<S>
where
    StaleNodeIndex: KeyCodec<S>,
{
    fn name(&self) -> &'static str {
        STATE_MERKLE_PRUNER_NAME
    }

    fn prune(&self, batch_size: usize) -> Result<Version> {
        if !self.is_pruning_pending() {
            return Ok(self.min_readable_version());
        }
        let min_readable_version = self.min_readable_version();
        let target_version = self.target_version();

        match self.prune_state_merkle(min_readable_version, target_version, batch_size, None) {
            Ok(new_min_readable_version) => Ok(new_min_readable_version),
            Err(e) => {
                error!(
                    error = ?e,
                    "Error pruning stale states.",
                );
                Err(e)
                // On error, stop retrying vigorously by making next recv() blocking.
            }
        }
    }

    fn initialize_min_readable_version(&self) -> Result<Version> {
        Ok(self
            .state_merkle_db
            .get::<DbMetadataSchema>(&S::tag())?
            .map_or(0, |v| v.expect_version()))
    }

    fn min_readable_version(&self) -> Version {
        let (version, _) = *self.progress.lock();
        version
    }

    fn set_target_version(&self, target_version: Version) {
        self.target_version.store(target_version, Ordering::Relaxed);
    }

    fn target_version(&self) -> Version {
        self.target_version.load(Ordering::Relaxed)
    }

    // used only by blanket `initialize()`, use the underlying implementation instead elsewhere.
    fn record_progress(&self, min_readable_version: Version) {
        self.record_progress_impl(min_readable_version, false /* is_fully_pruned */);
    }

    fn is_pruning_pending(&self) -> bool {
        let (min_readable_version, fully_pruned) = *self.progress.lock();
        self.target_version() > min_readable_version || !fully_pruned
    }

    /// (For tests only.) Updates the minimal readable version kept by pruner.
    fn testonly_update_min_version(&self, version: Version) {
        self.record_progress_impl(version, true /* is_fully_pruned */);
    }
}

impl<S: StaleNodeIndexSchemaTrait> StateMerklePruner<S>
where
    StaleNodeIndex: KeyCodec<S>,
{
    pub fn new(state_merkle_db: Arc<DB>) -> Self {
        let pruner = StateMerklePruner {
            state_merkle_db,
            target_version: AtomicVersion::new(0),
            progress: Mutex::new((0, true)),
            _phantom: std::marker::PhantomData,
        };
        pruner.initialize();
        pruner
    }

    // If the existing schema batch is not none, this function only adds items need to be
    // deleted to the schema batch and the caller is responsible for committing the schema batches
    // to the DB.
    pub fn prune_state_merkle(
        &self,
        min_readable_version: Version,
        target_version: Version,
        batch_size: usize,
        existing_schema_batch: Option<&mut SchemaBatch>,
    ) -> anyhow::Result<Version> {
        assert_ne!(batch_size, 0);
        let (indices, is_end_of_target_version) =
            self.get_stale_node_indices(min_readable_version, target_version, batch_size)?;
        if indices.is_empty() {
            self.record_progress_impl(target_version, is_end_of_target_version);
            Ok(target_version)
        } else {
            let _timer = OTHER_TIMERS_SECONDS
                .with_label_values(&["state_pruner_commit"])
                .start_timer();
            let new_min_readable_version =
                indices.last().expect("Should exist.").stale_since_version;

            // Delete stale nodes.
            if let Some(existing_schema_batch) = existing_schema_batch {
                indices.into_iter().try_for_each(|index| {
                    existing_schema_batch.delete::<JellyfishMerkleNodeSchema>(&index.node_key)?;
                    existing_schema_batch.delete::<S>(&index)
                })?;
            } else {
                let batch = SchemaBatch::new();
                indices.into_iter().try_for_each(|index| {
                    batch.delete::<JellyfishMerkleNodeSchema>(&index.node_key)?;
                    batch.delete::<S>(&index)
                })?;

                batch.put::<DbMetadataSchema>(
                    &S::tag(),
                    &DbMetadataValue::Version(new_min_readable_version),
                )?;

                // Commit to DB.
                self.state_merkle_db.write_schemas(batch)?;
            }

            // TODO(zcc): recording progress after writing schemas might provide wrong answers to
            // API calls when they query min_readable_version while the write_schemas are still in
            // progress.
            self.record_progress_impl(new_min_readable_version, is_end_of_target_version);
            Ok(new_min_readable_version)
        }
    }

    fn record_progress_impl(&self, min_readable_version: Version, is_fully_pruned: bool) {
        *self.progress.lock() = (min_readable_version, is_fully_pruned);
        PRUNER_LEAST_READABLE_VERSION
            .with_label_values(&["state_store"])
            .set(min_readable_version as i64);
    }

    fn get_stale_node_indices(
        &self,
        start_version: Version,
        target_version: Version,
        batch_size: usize,
    ) -> Result<(Vec<StaleNodeIndex>, bool)> {
        let mut indices = Vec::new();
        let mut iter = self.state_merkle_db.iter::<S>(ReadOptions::default())?;
        iter.seek(&StaleNodeIndex {
            stale_since_version: start_version,
            node_key: NodeKey::new_empty_path(0),
        })?;

        // over fetch by 1
        for _ in 0..=batch_size {
            if let Some((index, _)) = iter.next().transpose()? {
                if index.stale_since_version <= target_version {
                    indices.push(index);
                    continue;
                }
            }
            break;
        }

        let is_end_of_target_version = if indices.len() > batch_size {
            indices.pop();
            false
        } else {
            true
        };
        Ok((indices, is_end_of_target_version))
    }
}

impl StateMerklePruner<StaleNodeIndexCrossEpochSchema> {
    /// Prunes the genesis state and saves the db alterations to the given change set
    pub fn prune_genesis(state_merkle_db: Arc<DB>, batch: &mut SchemaBatch) -> Result<()> {
        let target_version = 1; // The genesis version is 0. Delete [0,1) (exclusive)
        let max_version = 1; // We should only be pruning a single version

        let state_pruner =
            utils::create_state_pruner::<StaleNodeIndexCrossEpochSchema>(state_merkle_db);
        state_pruner.set_target_version(target_version);

        let min_readable_version = state_pruner.min_readable_version();
        let target_version = state_pruner.target_version();
        state_pruner.prune_state_merkle(
            min_readable_version,
            target_version,
            max_version,
            Some(batch),
        )?;

        Ok(())
    }
}
<commit_msg>[storage] trivial: distinguish state merkle pruner and epoch snapshot pruner in min_readable_version metric<commit_after>// Copyright (c) Aptos
// SPDX-License-Identifier: Apache-2.0

use crate::db_metadata::DbMetadataSchema;
use crate::pruner::state_store::generics::StaleNodeIndexSchemaTrait;
use crate::schema::db_metadata::DbMetadataValue;
use crate::{
    jellyfish_merkle_node::JellyfishMerkleNodeSchema, metrics::PRUNER_LEAST_READABLE_VERSION,
    pruner::db_pruner::DBPruner, utils, StaleNodeIndexCrossEpochSchema, OTHER_TIMERS_SECONDS,
};
use anyhow::Result;
use aptos_infallible::Mutex;
use aptos_jellyfish_merkle::node_type::NodeKey;
use aptos_jellyfish_merkle::StaleNodeIndex;
use aptos_logger::error;
use aptos_types::transaction::{AtomicVersion, Version};
use schemadb::schema::KeyCodec;
use schemadb::{ReadOptions, SchemaBatch, DB};
use std::sync::{atomic::Ordering, Arc};

pub mod generics;
pub(crate) mod state_value_pruner;

#[cfg(test)]
mod test;

pub const STATE_MERKLE_PRUNER_NAME: &str = "state_merkle_pruner";

/// Responsible for pruning the state tree.
#[derive(Debug)]
pub struct StateMerklePruner<S> {
    /// State DB.
    state_merkle_db: Arc<DB>,
    /// Keeps track of the target version that the pruner needs to achieve.
    target_version: AtomicVersion,
    /// 1. min readable version
    /// 2. if things before that version fully cleaned
    progress: Mutex<(Version, bool)>,
    _phantom: std::marker::PhantomData<S>,
}

impl<S: StaleNodeIndexSchemaTrait> DBPruner for StateMerklePruner<S>
where
    StaleNodeIndex: KeyCodec<S>,
{
    fn name(&self) -> &'static str {
        STATE_MERKLE_PRUNER_NAME
    }

    fn prune(&self, batch_size: usize) -> Result<Version> {
        if !self.is_pruning_pending() {
            return Ok(self.min_readable_version());
        }
        let min_readable_version = self.min_readable_version();
        let target_version = self.target_version();

        match self.prune_state_merkle(min_readable_version, target_version, batch_size, None) {
            Ok(new_min_readable_version) => Ok(new_min_readable_version),
            Err(e) => {
                error!(
                    error = ?e,
                    "Error pruning stale states.",
                );
                Err(e)
                // On error, stop retrying vigorously by making next recv() blocking.
            }
        }
    }

    fn initialize_min_readable_version(&self) -> Result<Version> {
        Ok(self
            .state_merkle_db
            .get::<DbMetadataSchema>(&S::tag())?
            .map_or(0, |v| v.expect_version()))
    }

    fn min_readable_version(&self) -> Version {
        let (version, _) = *self.progress.lock();
        version
    }

    fn set_target_version(&self, target_version: Version) {
        self.target_version.store(target_version, Ordering::Relaxed);
    }

    fn target_version(&self) -> Version {
        self.target_version.load(Ordering::Relaxed)
    }

    // used only by blanket `initialize()`, use the underlying implementation instead elsewhere.
    fn record_progress(&self, min_readable_version: Version) {
        self.record_progress_impl(min_readable_version, false /* is_fully_pruned */);
    }

    fn is_pruning_pending(&self) -> bool {
        let (min_readable_version, fully_pruned) = *self.progress.lock();
        self.target_version() > min_readable_version || !fully_pruned
    }

    /// (For tests only.) Updates the minimal readable version kept by pruner.
    fn testonly_update_min_version(&self, version: Version) {
        self.record_progress_impl(version, true /* is_fully_pruned */);
    }
}

impl<S: StaleNodeIndexSchemaTrait> StateMerklePruner<S>
where
    StaleNodeIndex: KeyCodec<S>,
{
    pub fn new(state_merkle_db: Arc<DB>) -> Self {
        let pruner = StateMerklePruner {
            state_merkle_db,
            target_version: AtomicVersion::new(0),
            progress: Mutex::new((0, true)),
            _phantom: std::marker::PhantomData,
        };
        pruner.initialize();
        pruner
    }

    // If the existing schema batch is not none, this function only adds items need to be
    // deleted to the schema batch and the caller is responsible for committing the schema batches
    // to the DB.
    pub fn prune_state_merkle(
        &self,
        min_readable_version: Version,
        target_version: Version,
        batch_size: usize,
        existing_schema_batch: Option<&mut SchemaBatch>,
    ) -> anyhow::Result<Version> {
        assert_ne!(batch_size, 0);
        let (indices, is_end_of_target_version) =
            self.get_stale_node_indices(min_readable_version, target_version, batch_size)?;
        if indices.is_empty() {
            self.record_progress_impl(target_version, is_end_of_target_version);
            Ok(target_version)
        } else {
            let _timer = OTHER_TIMERS_SECONDS
                .with_label_values(&["state_pruner_commit"])
                .start_timer();
            let new_min_readable_version =
                indices.last().expect("Should exist.").stale_since_version;

            // Delete stale nodes.
            if let Some(existing_schema_batch) = existing_schema_batch {
                indices.into_iter().try_for_each(|index| {
                    existing_schema_batch.delete::<JellyfishMerkleNodeSchema>(&index.node_key)?;
                    existing_schema_batch.delete::<S>(&index)
                })?;
            } else {
                let batch = SchemaBatch::new();
                indices.into_iter().try_for_each(|index| {
                    batch.delete::<JellyfishMerkleNodeSchema>(&index.node_key)?;
                    batch.delete::<S>(&index)
                })?;

                batch.put::<DbMetadataSchema>(
                    &S::tag(),
                    &DbMetadataValue::Version(new_min_readable_version),
                )?;

                // Commit to DB.
                self.state_merkle_db.write_schemas(batch)?;
            }

            // TODO(zcc): recording progress after writing schemas might provide wrong answers to
            // API calls when they query min_readable_version while the write_schemas are still in
            // progress.
            self.record_progress_impl(new_min_readable_version, is_end_of_target_version);
            Ok(new_min_readable_version)
        }
    }

    fn record_progress_impl(&self, min_readable_version: Version, is_fully_pruned: bool) {
        *self.progress.lock() = (min_readable_version, is_fully_pruned);
        PRUNER_LEAST_READABLE_VERSION
            .with_label_values(&[S::name()])
            .set(min_readable_version as i64);
    }

    fn get_stale_node_indices(
        &self,
        start_version: Version,
        target_version: Version,
        batch_size: usize,
    ) -> Result<(Vec<StaleNodeIndex>, bool)> {
        let mut indices = Vec::new();
        let mut iter = self.state_merkle_db.iter::<S>(ReadOptions::default())?;
        iter.seek(&StaleNodeIndex {
            stale_since_version: start_version,
            node_key: NodeKey::new_empty_path(0),
        })?;

        // over fetch by 1
        for _ in 0..=batch_size {
            if let Some((index, _)) = iter.next().transpose()? {
                if index.stale_since_version <= target_version {
                    indices.push(index);
                    continue;
                }
            }
            break;
        }

        let is_end_of_target_version = if indices.len() > batch_size {
            indices.pop();
            false
        } else {
            true
        };
        Ok((indices, is_end_of_target_version))
    }
}

impl StateMerklePruner<StaleNodeIndexCrossEpochSchema> {
    /// Prunes the genesis state and saves the db alterations to the given change set
    pub fn prune_genesis(state_merkle_db: Arc<DB>, batch: &mut SchemaBatch) -> Result<()> {
        let target_version = 1; // The genesis version is 0. Delete [0,1) (exclusive)
        let max_version = 1; // We should only be pruning a single version

        let state_pruner =
            utils::create_state_pruner::<StaleNodeIndexCrossEpochSchema>(state_merkle_db);
        state_pruner.set_target_version(target_version);

        let min_readable_version = state_pruner.min_readable_version();
        let target_version = state_pruner.target_version();
        state_pruner.prune_state_merkle(
            min_readable_version,
            target_version,
            max_version,
            Some(batch),
        )?;

        Ok(())
    }
}
<|endoftext|>


<commit_before>// allow(const_err) to work around a bug in warnings
#[allow(const_err)]
static FOO: fn() = || { assert_ne!(42, 43) };
#[allow(const_err)]
static BAR: fn(i32, i32) = |a, b| { assert_ne!(a, b) };

// use to first make the closure FnOnce() before making it fn()
fn magic0<R, F: FnOnce() -> R>(f: F) -> F { f }
fn magic1<T, R, F: FnOnce(T) -> R>(f: F) -> F { f }

fn main() {
    FOO();
    BAR(44, 45);
    let bar: unsafe fn(i32, i32) = BAR;
    unsafe { bar(46, 47) };
    let boo: &dyn Fn(i32, i32) = &BAR;
    boo(48, 49);

    let f: fn() = ||{};
    f();
    let f = magic0(||{}) as fn();
    f();

    let g: fn(i32) = |i| assert_eq!(i, 2);
    g(2);
    let g = magic1(|i| assert_eq!(i, 2)) as fn(i32);
    g(2);

    // FIXME: This fails with "invalid use of NULL pointer" <https://github.com/rust-lang/miri/issues/1075>
    //let h: fn() -> ! = || std::process::exit(0);
    //h();
    // FIXME: This does not even compile?!? <https://github.com/rust-lang/rust/issues/66738>
    //let h = magic0(|| std::process::exit(0)) as fn() -> !;
    //h();
    // Once these tests pass, they should be in separate files as they terminate the process.
}
<commit_msg>expand coerce fn test<commit_after>// allow(const_err) to work around a bug in warnings
#[allow(const_err)]
static FOO: fn() = || { assert_ne!(42, 43) };
#[allow(const_err)]
static BAR: fn(i32, i32) = |a, b| { assert_ne!(a, b) };

// use to first make the closure FnOnce() before making it fn()
fn force_once0<R, F: FnOnce() -> R>(f: F) -> F { f }
fn force_once1<T, R, F: FnOnce(T) -> R>(f: F) -> F { f }
fn force_mut0<R, F: FnMut() -> R>(f: F) -> F { f }
fn force_mut1<T, R, F: FnMut(T) -> R>(f: F) -> F { f }

fn main() {
    FOO();
    BAR(44, 45);
    let bar: unsafe fn(i32, i32) = BAR;
    unsafe { bar(46, 47) };
    let boo: &dyn Fn(i32, i32) = &BAR;
    boo(48, 49);

    let f: fn() = ||{};
    f();
    let f = force_once0(||{}) as fn();
    f();
    let f = force_mut0(||{}) as fn();
    f();

    let g: fn(i32) = |i| assert_eq!(i, 2);
    g(2);
    let g = force_once1(|i| assert_eq!(i, 2)) as fn(i32);
    g(2);
    let g = force_mut1(|i| assert_eq!(i, 2)) as fn(i32);
    g(2);

    // FIXME: This fails with "invalid use of NULL pointer" <https://github.com/rust-lang/miri/issues/1075>
    //let h: fn() -> ! = || std::process::exit(0);
    //h();
    // FIXME: This does not even compile?!? <https://github.com/rust-lang/rust/issues/66738>
    //let h = magic0(|| std::process::exit(0)) as fn() -> !;
    //h();
    // Once these tests pass, they should be in separate files as they terminate the process.
}
<|endoftext|>


<commit_before>use crate::net::{network_delay, network_poll};
use futures::pin_mut;
use futures::task::{FutureObj, SpawnError};
use smoltcp::time::{Duration, Instant};
use std::future::Future;
use std::sync::{
	atomic::{AtomicBool, Ordering},
	Arc, Mutex,
};
use std::task::{Context, Poll, Wake};

/// A thread handle type
type Tid = u32;

extern "C" {
	fn sys_getpid() -> u32;
	fn sys_yield();
	fn sys_wakeup_task(tid: Tid);
	fn sys_set_network_polling_mode(value: bool);
}

extern "Rust" {
	fn sys_block_current_task_with_timeout(timeout: Option<u64>);
}

thread_local! {
	static CURRENT_THREAD_NOTIFY: Arc<ThreadNotify> = {
		Arc::new(ThreadNotify::new())
	}
}

lazy_static! {
	static ref EXECUTOR: Mutex<SmoltcpExecutor> = Mutex::new(SmoltcpExecutor::new());
}

struct SmoltcpExecutor {
	pool: Vec<FutureObj<'static, ()>>,
}

impl SmoltcpExecutor {
	pub const fn new() -> Self {
		Self { pool: Vec::new() }
	}

	fn spawn_obj(&mut self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> {
		self.pool.push(future);
		Ok(())
	}
}

struct ThreadNotify {
	/// The (single) executor thread.
	thread: Tid,
	/// A flag to ensure a wakeup is not "forgotten" before the next `block_current_task`
	unparked: AtomicBool,
}

impl ThreadNotify {
	pub fn new() -> Self {
		Self {
			thread: unsafe { sys_getpid() },
			unparked: AtomicBool::new(false),
		}
	}
}

impl Drop for ThreadNotify {
	fn drop(&mut self) {
		println!("Dropping ThreadNotify!");
	}
}

impl Wake for ThreadNotify {
	fn wake(self: Arc<Self>) {
		self.wake_by_ref()
	}

	fn wake_by_ref(self: &Arc<Self>) {
		// Make sure the wakeup is remembered until the next `park()`.
		let unparked = self.unparked.swap(true, Ordering::Relaxed);
		if !unparked {
			unsafe {
				sys_wakeup_task(self.thread);
			}
		}
	}
}

// Set up and run a basic single-threaded spawner loop, invoking `f` on each
// turn.
fn run_until<T, F: FnMut(&mut Context<'_>) -> Poll<T>>(
	mut f: F,
	timeout: Option<Duration>,
) -> Result<T, ()> {
	unsafe {
		sys_set_network_polling_mode(true);
	}
	let start = Instant::now();

	CURRENT_THREAD_NOTIFY.with(|thread_notify| {
		let waker = thread_notify.clone().into();
		let mut cx = Context::from_waker(&waker);
		loop {
			if let Poll::Ready(t) = f(&mut cx) {
				unsafe {
					sys_set_network_polling_mode(false);
				}
				return Ok(t);
			}

			let now = Instant::now();

			if let Some(duration) = timeout {
				if now >= start + duration {
					unsafe {
						sys_set_network_polling_mode(false);
					}
					return Err(());
				}
			} else {
				let delay = network_delay(now).map(|d| d.total_millis());

				if delay.is_none() || delay.unwrap() > 100 {
					let unparked = thread_notify.unparked.swap(false, Ordering::Acquire);
					if !unparked {
						unsafe {
							sys_set_network_polling_mode(false);
							sys_block_current_task_with_timeout(delay);
							sys_yield();
							sys_set_network_polling_mode(true);
						}
						thread_notify.unparked.store(false, Ordering::Release);
						network_poll(&mut cx, Instant::now());
					}
				} else {
					network_poll(&mut cx, now);
				}
			}
		}
	})
}

pub fn block_on<F: Future>(f: F, timeout: Option<Duration>) -> Result<F::Output, ()> {
	pin_mut!(f);
	run_until(|cx| f.as_mut().poll(cx), timeout)
}

pub fn spawn<F: Future<Output = ()> + std::marker::Send + 'static>(f: F) -> Result<(), SpawnError> {
	EXECUTOR
		.lock()
		.unwrap()
		.spawn_obj(FutureObj::from(Box::new(f)))
}
<commit_msg>Migrate from FutureObj to BoxFuture<commit_after>use crate::net::{network_delay, network_poll};
use futures::future::BoxFuture;
use futures::pin_mut;
use futures::task::SpawnError;
use smoltcp::time::{Duration, Instant};
use std::future::Future;
use std::sync::{
	atomic::{AtomicBool, Ordering},
	Arc, Mutex,
};
use std::task::{Context, Poll, Wake};

/// A thread handle type
type Tid = u32;

extern "C" {
	fn sys_getpid() -> u32;
	fn sys_yield();
	fn sys_wakeup_task(tid: Tid);
	fn sys_set_network_polling_mode(value: bool);
}

extern "Rust" {
	fn sys_block_current_task_with_timeout(timeout: Option<u64>);
}

thread_local! {
	static CURRENT_THREAD_NOTIFY: Arc<ThreadNotify> = {
		Arc::new(ThreadNotify::new())
	}
}

lazy_static! {
	static ref EXECUTOR: Mutex<SmoltcpExecutor> = Mutex::new(SmoltcpExecutor::new());
}

struct SmoltcpExecutor {
	pool: Vec<BoxFuture<'static, ()>>,
}

impl SmoltcpExecutor {
	pub fn new() -> Self {
		Self { pool: Vec::new() }
	}

	fn spawn_obj(&mut self, future: BoxFuture<'static, ()>) -> Result<(), SpawnError> {
		self.pool.push(future);
		Ok(())
	}
}

struct ThreadNotify {
	/// The (single) executor thread.
	thread: Tid,
	/// A flag to ensure a wakeup is not "forgotten" before the next `block_current_task`
	unparked: AtomicBool,
}

impl ThreadNotify {
	pub fn new() -> Self {
		Self {
			thread: unsafe { sys_getpid() },
			unparked: AtomicBool::new(false),
		}
	}
}

impl Drop for ThreadNotify {
	fn drop(&mut self) {
		println!("Dropping ThreadNotify!");
	}
}

impl Wake for ThreadNotify {
	fn wake(self: Arc<Self>) {
		self.wake_by_ref()
	}

	fn wake_by_ref(self: &Arc<Self>) {
		// Make sure the wakeup is remembered until the next `park()`.
		let unparked = self.unparked.swap(true, Ordering::Relaxed);
		if !unparked {
			unsafe {
				sys_wakeup_task(self.thread);
			}
		}
	}
}

// Set up and run a basic single-threaded spawner loop, invoking `f` on each
// turn.
fn run_until<T, F: FnMut(&mut Context<'_>) -> Poll<T>>(
	mut f: F,
	timeout: Option<Duration>,
) -> Result<T, ()> {
	unsafe {
		sys_set_network_polling_mode(true);
	}
	let start = Instant::now();

	CURRENT_THREAD_NOTIFY.with(|thread_notify| {
		let waker = thread_notify.clone().into();
		let mut cx = Context::from_waker(&waker);
		loop {
			if let Poll::Ready(t) = f(&mut cx) {
				unsafe {
					sys_set_network_polling_mode(false);
				}
				return Ok(t);
			}

			let now = Instant::now();

			if let Some(duration) = timeout {
				if now >= start + duration {
					unsafe {
						sys_set_network_polling_mode(false);
					}
					return Err(());
				}
			} else {
				let delay = network_delay(now).map(|d| d.total_millis());

				if delay.is_none() || delay.unwrap() > 100 {
					let unparked = thread_notify.unparked.swap(false, Ordering::Acquire);
					if !unparked {
						unsafe {
							sys_set_network_polling_mode(false);
							sys_block_current_task_with_timeout(delay);
							sys_yield();
							sys_set_network_polling_mode(true);
						}
						thread_notify.unparked.store(false, Ordering::Release);
						network_poll(&mut cx, Instant::now());
					}
				} else {
					network_poll(&mut cx, now);
				}
			}
		}
	})
}

pub fn block_on<F: Future>(f: F, timeout: Option<Duration>) -> Result<F::Output, ()> {
	pin_mut!(f);
	run_until(|cx| f.as_mut().poll(cx), timeout)
}

pub fn spawn<F: Future<Output = ()> + std::marker::Send + 'static>(f: F) -> Result<(), SpawnError> {
	EXECUTOR.lock().unwrap().spawn_obj(Box::pin(f))
}
<|endoftext|>


<commit_before>// Copyright (c) 2020 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

use crate::{
    api_utils::{delete, get, get_all, get_one, post, put, wait_for_cmds_success},
    display_utils::{generate_table, wrap_fut},
    error::ImlManagerCliError,
};
use console::{style, Term};
use futures::future::try_join_all;
use iml_wire_types::{
    ApiList, Command, EndpointName, Filesystem, FlatQuery, Ost, OstPool, OstPoolApi,
};
use prettytable::{Row, Table};
use std::iter::FromIterator;
use structopt::StructOpt;

#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct ObjCommand {
    command: Command,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct ObjCommands {
    commands: Vec<Command>,
}

#[derive(Debug, StructOpt)]
pub enum OstPoolCommand {
    /// List all pools for filesystem
    #[structopt(name = "list")]
    List {
        #[structopt(name = "FSNAME")]
        fsname: Option<String>,
    },

    /// Show Pool Details
    #[structopt(name = "show")]
    Show {
        #[structopt(name = "FSNAME")]
        fsname: String,
        poolname: String,
    },

    /// Create Pool
    #[structopt(name = "create")]
    Create {
        fsname: String,
        poolname: String,
        osts: Vec<String>,
    },

    /// Add OST to Pool
    #[structopt(name = "grow")]
    Grow {
        fsname: String,
        poolname: String,
        osts: Vec<String>,
    },

    /// Remove OST to Pool
    #[structopt(name = "shrink")]
    Shrink {
        fsname: String,
        poolname: String,
        osts: Vec<String>,
    },

    /// Destroy Pool
    #[structopt(name = "destroy")]
    Destroy { fsname: String, poolname: String },
}

async fn pool_lookup(fsname: &str, poolname: &str) -> Result<OstPoolApi, ImlManagerCliError> {
    let fs: Filesystem =
        wrap_fut("Fetching Filesystem ...", get_one(vec![("name", fsname)])).await?;
    let mut pool: OstPoolApi = wrap_fut(
        "Fetching OstPool...",
        get_one(vec![
            ("filesystem", fs.id.to_string().as_str()),
            ("name", poolname),
        ]),
    )
    .await?;
    let osts: Vec<Ost> = try_join_all(
        pool.ost
            .osts
            .into_iter()
            .map(|o| async move { wrap_fut("Fetching OST...", get(&o, Ost::query())).await }),
    )
    .await?;
    pool.ost.osts = osts.into_iter().map(|m| m.name).collect();
    Ok(pool)
}

async fn ostpool_list(fsname: Option<String>) -> Result<(), ImlManagerCliError> {
    let xs: Vec<_> = match fsname {
        Some(fsname) => {
            let fs: Filesystem =
                wrap_fut("Fetching Filesystem ...", get_one(vec![("name", &fsname)])).await?;
            let pools: ApiList<OstPoolApi> = wrap_fut(
                "Fetching OstPools...",
                get(
                    OstPoolApi::endpoint_name(),
                    vec![("limit", 0), ("filesystem", fs.id)],
                ),
            )
            .await?;
            pools
                .objects
                .into_iter()
                .map(|p| vec![fsname.clone(), p.ost.name, p.ost.osts.len().to_string()])
                .collect()
        }
        None => {
            let pools: ApiList<OstPoolApi> = wrap_fut("Fetching OstPools...", get_all()).await?;

            let fs_ids = pools
                .objects
                .iter()
                .filter_map(|p| {
                    let id = iml_api_utils::extract_id(&p.ost.filesystem);

                    id.map(|x| x.to_string())
                })
                .collect::<std::collections::HashSet<String>>();

            let fs_ids = Vec::from_iter(fs_ids).join(",");

            let mut query = Filesystem::query();

            query.push(("id__in", &fs_ids));

            let fs: ApiList<Filesystem> = get(Filesystem::endpoint_name(), query).await?;

            pools
                .objects
                .into_iter()
                .filter_map(move |p| {
                    fs.objects
                        .iter()
                        .find(|f| f.resource_uri == p.ost.filesystem)
                        .map(move |fs| {
                            vec![fs.name.clone(), p.ost.name, p.ost.osts.len().to_string()]
                        })
                })
                .collect()
        }
    };

    let table = generate_table(&["Filesystem", "Pool Name", "OST Count"], xs);
    table.printstd();

    Ok(())
}

async fn ostpool_show(fsname: String, poolname: String) -> Result<(), ImlManagerCliError> {
    let pool = pool_lookup(&fsname, &poolname).await?;

    let mut table = Table::new();
    table.add_row(Row::from(&["Filesystem".to_string(), fsname]));
    table.add_row(Row::from(&["Name".to_string(), poolname]));
    table.add_row(Row::from(&["OSTs".to_string(), pool.ost.osts.join("\n")]));
    table.printstd();

    Ok(())
}

async fn ostpool_destroy(
    term: &Term,
    fsname: String,
    poolname: String,
) -> Result<(), ImlManagerCliError> {
    let pool = pool_lookup(&fsname, &poolname).await?;
    let resp = delete(&pool.resource_uri, "").await?;
    term.write_line(&format!("{} ost pool...", style("Destroying").green()))?;
    let objs: ObjCommands = resp.json().await?;
    wait_for_cmds_success(&objs.commands).await?;

    Ok(())
}

pub async fn ostpool_cli(command: OstPoolCommand) -> Result<(), ImlManagerCliError> {
    let term = Term::stdout();

    match command {
        OstPoolCommand::List { fsname } => ostpool_list(fsname).await?,
        OstPoolCommand::Show { fsname, poolname } => ostpool_show(fsname, poolname).await?,
        OstPoolCommand::Create {
            fsname,
            poolname,
            osts,
        } => {
            let pool = OstPoolApi {
                ost: OstPool {
                    filesystem: fsname,
                    name: poolname,
                    osts,
                },
                ..Default::default()
            };
            let resp = post(OstPoolApi::endpoint_name(), pool).await?;

            term.write_line(&format!("{} ost pool...", style("Creating").green()))?;
            let objs: ObjCommand = resp.json().await?;
            wait_for_cmds_success(&[objs.command]).await?;
        }
        OstPoolCommand::Destroy { fsname, poolname } => {
            ostpool_destroy(&term, fsname, poolname).await?;
        }
        OstPoolCommand::Grow {
            fsname,
            poolname,
            osts,
        } => {
            let mut pool = pool_lookup(&fsname, &poolname).await?;
            let mut newlist = pool.ost.osts;
            newlist.extend(osts);
            newlist.sort();
            newlist.dedup();
            pool.ost.osts = newlist;

            tracing::debug!("POOL: {:?}", pool);
            term.write_line(&format!("{} ost pool...", style("Growing").green()))?;
            let uri = pool.resource_uri.clone();
            let resp = put(&uri, pool).await?;
            let objs: ObjCommand = resp.json().await?;
            wait_for_cmds_success(&[objs.command]).await?;
        }
        OstPoolCommand::Shrink {
            fsname,
            poolname,
            osts,
        } => {
            let mut pool = pool_lookup(&fsname, &poolname).await?;

            pool.ost.osts.retain(|o| !osts.contains(o));

            tracing::debug!("POOL: {:?}", pool);
            term.write_line(&format!("{} ost pool...", style("Shrinking").green()))?;
            let uri = pool.resource_uri.clone();
            let resp = put(&uri, pool).await?;

            let objs: ObjCommand = resp.json().await?;
            wait_for_cmds_success(&[objs.command]).await?;
        }
    };

    Ok(())
}
<commit_msg>Sort OSTs in OST pools display (#1758)<commit_after>// Copyright (c) 2020 DDN. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

use crate::{
    api_utils::{delete, get, get_all, get_one, post, put, wait_for_cmds_success},
    display_utils::{generate_table, wrap_fut},
    error::ImlManagerCliError,
};
use console::{style, Term};
use futures::future::try_join_all;
use iml_wire_types::{
    ApiList, Command, EndpointName, Filesystem, FlatQuery, Ost, OstPool, OstPoolApi,
};
use prettytable::{Row, Table};
use std::iter::FromIterator;
use structopt::StructOpt;

#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct ObjCommand {
    command: Command,
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct ObjCommands {
    commands: Vec<Command>,
}

#[derive(Debug, StructOpt)]
pub enum OstPoolCommand {
    /// List all pools for filesystem
    #[structopt(name = "list")]
    List {
        #[structopt(name = "FSNAME")]
        fsname: Option<String>,
    },

    /// Show Pool Details
    #[structopt(name = "show")]
    Show {
        #[structopt(name = "FSNAME")]
        fsname: String,
        poolname: String,
    },

    /// Create Pool
    #[structopt(name = "create")]
    Create {
        fsname: String,
        poolname: String,
        osts: Vec<String>,
    },

    /// Add OST to Pool
    #[structopt(name = "grow")]
    Grow {
        fsname: String,
        poolname: String,
        osts: Vec<String>,
    },

    /// Remove OST to Pool
    #[structopt(name = "shrink")]
    Shrink {
        fsname: String,
        poolname: String,
        osts: Vec<String>,
    },

    /// Destroy Pool
    #[structopt(name = "destroy")]
    Destroy { fsname: String, poolname: String },
}

async fn pool_lookup(fsname: &str, poolname: &str) -> Result<OstPoolApi, ImlManagerCliError> {
    let fs: Filesystem =
        wrap_fut("Fetching Filesystem ...", get_one(vec![("name", fsname)])).await?;
    let mut pool: OstPoolApi = wrap_fut(
        "Fetching OstPool...",
        get_one(vec![
            ("filesystem", fs.id.to_string().as_str()),
            ("name", poolname),
        ]),
    )
    .await?;
    let osts: Vec<Ost> = try_join_all(
        pool.ost
            .osts
            .into_iter()
            .map(|o| async move { wrap_fut("Fetching OST...", get(&o, Ost::query())).await }),
    )
    .await?;
    pool.ost.osts = osts.into_iter().map(|m| m.name).collect();
    Ok(pool)
}

async fn ostpool_list(fsname: Option<String>) -> Result<(), ImlManagerCliError> {
    let xs: Vec<_> = match fsname {
        Some(fsname) => {
            let fs: Filesystem =
                wrap_fut("Fetching Filesystem ...", get_one(vec![("name", &fsname)])).await?;
            let pools: ApiList<OstPoolApi> = wrap_fut(
                "Fetching OstPools...",
                get(
                    OstPoolApi::endpoint_name(),
                    vec![("limit", 0), ("filesystem", fs.id)],
                ),
            )
            .await?;
            pools
                .objects
                .into_iter()
                .map(|p| vec![fsname.clone(), p.ost.name, p.ost.osts.len().to_string()])
                .collect()
        }
        None => {
            let pools: ApiList<OstPoolApi> = wrap_fut("Fetching OstPools...", get_all()).await?;

            let fs_ids = pools
                .objects
                .iter()
                .filter_map(|p| {
                    let id = iml_api_utils::extract_id(&p.ost.filesystem);

                    id.map(|x| x.to_string())
                })
                .collect::<std::collections::HashSet<String>>();

            let fs_ids = Vec::from_iter(fs_ids).join(",");

            let mut query = Filesystem::query();

            query.push(("id__in", &fs_ids));

            let fs: ApiList<Filesystem> = get(Filesystem::endpoint_name(), query).await?;

            pools
                .objects
                .into_iter()
                .filter_map(move |p| {
                    fs.objects
                        .iter()
                        .find(|f| f.resource_uri == p.ost.filesystem)
                        .map(move |fs| {
                            vec![fs.name.clone(), p.ost.name, p.ost.osts.len().to_string()]
                        })
                })
                .collect()
        }
    };

    let table = generate_table(&["Filesystem", "Pool Name", "OST Count"], xs);
    table.printstd();

    Ok(())
}

async fn ostpool_show(fsname: String, poolname: String) -> Result<(), ImlManagerCliError> {
    let mut pool = pool_lookup(&fsname, &poolname).await?;

    pool.ost.osts.sort_unstable();

    let mut table = Table::new();
    table.add_row(Row::from(&["Filesystem".to_string(), fsname]));
    table.add_row(Row::from(&["Name".to_string(), poolname]));
    table.add_row(Row::from(&["OSTs".to_string(), pool.ost.osts.join("\n")]));
    table.printstd();

    Ok(())
}

async fn ostpool_destroy(
    term: &Term,
    fsname: String,
    poolname: String,
) -> Result<(), ImlManagerCliError> {
    let pool = pool_lookup(&fsname, &poolname).await?;
    let resp = delete(&pool.resource_uri, "").await?;
    term.write_line(&format!("{} ost pool...", style("Destroying").green()))?;
    let objs: ObjCommands = resp.json().await?;
    wait_for_cmds_success(&objs.commands).await?;

    Ok(())
}

pub async fn ostpool_cli(command: OstPoolCommand) -> Result<(), ImlManagerCliError> {
    let term = Term::stdout();

    match command {
        OstPoolCommand::List { fsname } => ostpool_list(fsname).await?,
        OstPoolCommand::Show { fsname, poolname } => ostpool_show(fsname, poolname).await?,
        OstPoolCommand::Create {
            fsname,
            poolname,
            osts,
        } => {
            let pool = OstPoolApi {
                ost: OstPool {
                    filesystem: fsname,
                    name: poolname,
                    osts,
                },
                ..Default::default()
            };
            let resp = post(OstPoolApi::endpoint_name(), pool).await?;

            term.write_line(&format!("{} ost pool...", style("Creating").green()))?;
            let objs: ObjCommand = resp.json().await?;
            wait_for_cmds_success(&[objs.command]).await?;
        }
        OstPoolCommand::Destroy { fsname, poolname } => {
            ostpool_destroy(&term, fsname, poolname).await?;
        }
        OstPoolCommand::Grow {
            fsname,
            poolname,
            osts,
        } => {
            let mut pool = pool_lookup(&fsname, &poolname).await?;
            let mut newlist = pool.ost.osts;
            newlist.extend(osts);
            newlist.sort();
            newlist.dedup();
            pool.ost.osts = newlist;

            tracing::debug!("POOL: {:?}", pool);
            term.write_line(&format!("{} ost pool...", style("Growing").green()))?;
            let uri = pool.resource_uri.clone();
            let resp = put(&uri, pool).await?;
            let objs: ObjCommand = resp.json().await?;
            wait_for_cmds_success(&[objs.command]).await?;
        }
        OstPoolCommand::Shrink {
            fsname,
            poolname,
            osts,
        } => {
            let mut pool = pool_lookup(&fsname, &poolname).await?;

            pool.ost.osts.retain(|o| !osts.contains(o));

            tracing::debug!("POOL: {:?}", pool);
            term.write_line(&format!("{} ost pool...", style("Shrinking").green()))?;
            let uri = pool.resource_uri.clone();
            let resp = put(&uri, pool).await?;

            let objs: ObjCommand = resp.json().await?;
            wait_for_cmds_success(&[objs.command]).await?;
        }
    };

    Ok(())
}
<|endoftext|>


<commit_before>mod support;

use std::{
    collections::HashMap,
    time::Instant,
};

use lsp_types::{
    CodeActionContext, DocumentFormattingParams, FormattingOptions, Position, Range,
};
use ra_lsp_server::req::{
    CodeActionParams, CodeActionRequest, Formatting, Runnables, RunnablesParams, CompletionParams, Completion,
};
use serde_json::json;
use tempfile::TempDir;

use crate::support::{project, project_with_tmpdir};

const LOG: &'static str = "";

#[test]
fn completes_items_from_standard_library() {
    let project_start = Instant::now();
    let server = project(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
use std::collections::Spam;
"#,
    );
    server.wait_for_message("workspace loaded");
    eprintln!("loading took    {:?}", project_start.elapsed());
    let completion_start = Instant::now();
    let res = server.send_request::<Completion>(CompletionParams {
        text_document: server.doc_id("src/lib.rs"),
        context: None,
        position: Position::new(0, 23),
    });
    assert!(format!("{}", res).contains("HashMap"));
    eprintln!("completion took {:?}", completion_start.elapsed());
}

#[test]
fn test_runnables_no_project() {
    let server = project(
        r"
//- lib.rs
#[test]
fn foo() {
}
",
    );
    server.wait_for_message("workspace loaded");
    server.request::<Runnables>(
        RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
        json!([
          {
            "args": [ "test", "--", "foo", "--nocapture" ],
            "bin": "cargo",
            "env": { "RUST_BACKTRACE": "short" },
            "label": "test foo",
            "range": {
              "end": { "character": 1, "line": 2 },
              "start": { "character": 0, "line": 0 }
            }
          },
          {
            "args": [
              "check",
              "--all"
            ],
            "bin": "cargo",
            "env": {},
            "label": "cargo check --all",
            "range": {
              "end": {
                "character": 0,
                "line": 0
              },
              "start": {
                "character": 0,
                "line": 0
              }
            }
          }
        ]),
    );
}

#[test]
fn test_runnables_project() {
    let server = project(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
pub fn foo() {}

//- tests/spam.rs
#[test]
fn test_eggs() {}
"#,
    );
    server.wait_for_message("workspace loaded");
    server.request::<Runnables>(
        RunnablesParams {
            text_document: server.doc_id("tests/spam.rs"),
            position: None,
        },
        json!([
          {
            "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--nocapture" ],
            "bin": "cargo",
            "env": { "RUST_BACKTRACE": "short" },
            "label": "test test_eggs",
            "range": {
              "end": { "character": 17, "line": 1 },
              "start": { "character": 0, "line": 0 }
            }
          },
          {
            "args": [
              "check",
              "--package",
              "foo",
              "--test",
              "spam"
            ],
            "bin": "cargo",
            "env": {},
            "label": "cargo check -p foo",
            "range": {
              "end": {
                "character": 0,
                "line": 0
              },
              "start": {
                "character": 0,
                "line": 0
              }
            }
          }
        ])
    );
}

#[test]
fn test_format_document() {
    let server = project(
        r#"
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
mod bar;

fn main() {
}

pub use std::collections::HashMap;
"#,
    );
    server.wait_for_message("workspace loaded");

    server.request::<Formatting>(
        DocumentFormattingParams {
            text_document: server.doc_id("src/lib.rs"),
            options: FormattingOptions {
                tab_size: 4,
                insert_spaces: false,
                properties: HashMap::new(),
            },
        },
        json!([
            {
                "newText": r#"mod bar;

fn main() {}

pub use std::collections::HashMap;
"#,
                "range": {
                    "end": {
                        "character": 0,
                        "line": 6
                    },
                    "start": {
                        "character": 0,
                        "line": 0
                    }
                }
            }
        ]),
    );
}

#[test]
fn test_missing_module_code_action() {
    let server = project(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
mod bar;

fn main() {}
"#,
    );
    server.wait_for_message("workspace loaded");
    let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(0, 4), Position::new(0, 7)),
            context: empty_context(),
        },
        json!([
          {
            "command": {
              "arguments": [
                {
                  "cursorPosition": null,
                  "label": "create module",
                  "workspaceEdit": {
                    "documentChanges": [
                      {
                        "kind": "create",
                        "uri": "file:///[..]/src/bar.rs"
                      }
                    ]
                  }
                }
              ],
              "command": "rust-analyzer.applySourceChange",
              "title": "create module"
            },
            "title": "create module"
          }
        ]),
    );

    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(2, 4), Position::new(2, 7)),
            context: empty_context(),
        },
        json!([]),
    );
}

#[test]
fn test_missing_module_code_action_in_json_project() {
    let tmp_dir = TempDir::new().unwrap();
    let code = format!(
        r#"
//- rust-project.json
{{
    "roots": [ "{PATH}" ],
    "crates": [ {{ "root_module": "{PATH}/src/lib.rs", "deps": [], "edition": "2015" }} ]
}}

//- src/lib.rs
mod bar;

fn main() {}
"#,
        PATH = tmp_dir.path().display()
    );
    let server = project_with_tmpdir(tmp_dir, &code);
    server.wait_for_feedback("workspace loaded");
    let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(0, 4), Position::new(0, 7)),
            context: empty_context(),
        },
        json!([
          {
            "command": {
              "arguments": [
                {
                  "cursorPosition": null,
                  "label": "create module",
                  "workspaceEdit": {
                    "documentChanges": [
                      {
                        "kind": "create",
                        "uri": "file:///[..]/src/bar.rs"
                      }
                    ]
                  }
                }
              ],
              "command": "rust-analyzer.applySourceChange",
              "title": "create module"
            },
            "title": "create module"
          }
        ]),
    );

    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(2, 4), Position::new(2, 7)),
            context: empty_context(),
        },
        json!([]),
    );
}
<commit_msg>Add test demonstrating logic for handling deps.<commit_after>mod support;

use std::{
    collections::HashMap,
    time::Instant,
};

use lsp_types::{
    CodeActionContext, DocumentFormattingParams, FormattingOptions, Position, Range,
};
use ra_lsp_server::req::{
    CodeActionParams, CodeActionRequest, Formatting, Runnables, RunnablesParams, CompletionParams, Completion,
};
use serde_json::json;
use tempfile::TempDir;

use crate::support::{project, project_with_tmpdir};

const LOG: &'static str = "";

#[test]
fn completes_items_from_standard_library() {
    let project_start = Instant::now();
    let server = project(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
use std::collections::Spam;
"#,
    );
    server.wait_for_message("workspace loaded");
    eprintln!("loading took    {:?}", project_start.elapsed());
    let completion_start = Instant::now();
    let res = server.send_request::<Completion>(CompletionParams {
        text_document: server.doc_id("src/lib.rs"),
        context: None,
        position: Position::new(0, 23),
    });
    assert!(format!("{}", res).contains("HashMap"));
    eprintln!("completion took {:?}", completion_start.elapsed());
}

#[test]
fn test_runnables_no_project() {
    let server = project(
        r"
//- lib.rs
#[test]
fn foo() {
}
",
    );
    server.wait_for_message("workspace loaded");
    server.request::<Runnables>(
        RunnablesParams { text_document: server.doc_id("lib.rs"), position: None },
        json!([
          {
            "args": [ "test", "--", "foo", "--nocapture" ],
            "bin": "cargo",
            "env": { "RUST_BACKTRACE": "short" },
            "label": "test foo",
            "range": {
              "end": { "character": 1, "line": 2 },
              "start": { "character": 0, "line": 0 }
            }
          },
          {
            "args": [
              "check",
              "--all"
            ],
            "bin": "cargo",
            "env": {},
            "label": "cargo check --all",
            "range": {
              "end": {
                "character": 0,
                "line": 0
              },
              "start": {
                "character": 0,
                "line": 0
              }
            }
          }
        ]),
    );
}

#[test]
fn test_runnables_project() {
    let server = project(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
pub fn foo() {}

//- tests/spam.rs
#[test]
fn test_eggs() {}
"#,
    );
    server.wait_for_message("workspace loaded");
    server.request::<Runnables>(
        RunnablesParams {
            text_document: server.doc_id("tests/spam.rs"),
            position: None,
        },
        json!([
          {
            "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--nocapture" ],
            "bin": "cargo",
            "env": { "RUST_BACKTRACE": "short" },
            "label": "test test_eggs",
            "range": {
              "end": { "character": 17, "line": 1 },
              "start": { "character": 0, "line": 0 }
            }
          },
          {
            "args": [
              "check",
              "--package",
              "foo",
              "--test",
              "spam"
            ],
            "bin": "cargo",
            "env": {},
            "label": "cargo check -p foo",
            "range": {
              "end": {
                "character": 0,
                "line": 0
              },
              "start": {
                "character": 0,
                "line": 0
              }
            }
          }
        ])
    );
}

#[test]
fn test_format_document() {
    let server = project(
        r#"
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
mod bar;

fn main() {
}

pub use std::collections::HashMap;
"#,
    );
    server.wait_for_message("workspace loaded");

    server.request::<Formatting>(
        DocumentFormattingParams {
            text_document: server.doc_id("src/lib.rs"),
            options: FormattingOptions {
                tab_size: 4,
                insert_spaces: false,
                properties: HashMap::new(),
            },
        },
        json!([
            {
                "newText": r#"mod bar;

fn main() {}

pub use std::collections::HashMap;
"#,
                "range": {
                    "end": {
                        "character": 0,
                        "line": 6
                    },
                    "start": {
                        "character": 0,
                        "line": 0
                    }
                }
            }
        ]),
    );
}

#[test]
fn test_missing_module_code_action() {
    let server = project(
        r#"
//- Cargo.toml
[package]
name = "foo"
version = "0.0.0"

//- src/lib.rs
mod bar;

fn main() {}
"#,
    );
    server.wait_for_message("workspace loaded");
    let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(0, 4), Position::new(0, 7)),
            context: empty_context(),
        },
        json!([
          {
            "command": {
              "arguments": [
                {
                  "cursorPosition": null,
                  "label": "create module",
                  "workspaceEdit": {
                    "documentChanges": [
                      {
                        "kind": "create",
                        "uri": "file:///[..]/src/bar.rs"
                      }
                    ]
                  }
                }
              ],
              "command": "rust-analyzer.applySourceChange",
              "title": "create module"
            },
            "title": "create module"
          }
        ]),
    );

    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(2, 4), Position::new(2, 7)),
            context: empty_context(),
        },
        json!([]),
    );
}

#[test]
fn test_missing_module_code_action_in_json_project() {
    let tmp_dir = TempDir::new().unwrap();
    let code = format!(
        r#"
//- rust-project.json
{{
    "roots": [ "{PATH}" ],
    "crates": [ {{ "root_module": "{PATH}/src/lib.rs", "deps": [], "edition": "2015" }} ]
}}

//- src/lib.rs
mod bar;

fn main() {}
"#,
        PATH = tmp_dir.path().display()
    );
    let server = project_with_tmpdir(tmp_dir, &code);
    server.wait_for_message("workspace loaded");
    let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(0, 4), Position::new(0, 7)),
            context: empty_context(),
        },
        json!([
          {
            "command": {
              "arguments": [
                {
                  "cursorPosition": null,
                  "label": "create module",
                  "workspaceEdit": {
                    "documentChanges": [
                      {
                        "kind": "create",
                        "uri": "file:///[..]/src/bar.rs"
                      }
                    ]
                  }
                }
              ],
              "command": "rust-analyzer.applySourceChange",
              "title": "create module"
            },
            "title": "create module"
          }
        ]),
    );

    server.request::<CodeActionRequest>(
        CodeActionParams {
            text_document: server.doc_id("src/lib.rs"),
            range: Range::new(Position::new(2, 4), Position::new(2, 7)),
            context: empty_context(),
        },
        json!([]),
    );
}

#[test]
fn completes_items_from_second_crate_in_json_project() {
    let tmp_dir = TempDir::new().unwrap();
    let code = format!(
        r#"
//- rust-project.json
{{
    "roots": [ "{PATH}" ],
    "crates": [
        {{
            "root_module": "{PATH}/foo/lib.rs",
            "deps": [
                {{
                    "name": "bar",
                    "crate": 1
                }}
            ],
            "edition": "2015"
        }},
        {{ "root_module": "{PATH}/bar/lib.rs", "deps": [], "edition": "2015" }}
    ]
}}

//- bar/lib.rs
pub struct Spam;
pub struct CannedMeat;

//- foo/lib.rs
extern crate bar;
use bar::Spam;
"#,
        PATH = tmp_dir.path().display()
    );
    let server = project_with_tmpdir(tmp_dir, &code);
    server.wait_for_message("workspace loaded");
    let res = server.send_request::<Completion>(CompletionParams {
        text_document: server.doc_id("foo/lib.rs"),
        context: None,
        position: Position::new(1, 10),
    });
    assert!(format!("{}", res).contains("CannedMeat"));
}
<|endoftext|>