从不同线程同时调用 DLL 的不同函数会出现段错误或导致 status_stack_buffer_overrun

问题描述 投票:0回答:1

我正在编写一个关于 Bridge(纸牌游戏)的库,它执行交易生成、性能分析和其他内容。我使用 Rust,并且对于某些功能,我依靠 C++ 库的 DLL,称为 Double Dummy Solver(以下称为 DDS - SiteGitHub)。我使用

bindgen
创建 DLL 的绑定(我不知道这是否是正确的方法:我不是一名程序员,我很难理解 DLL 和 FFI 的工作原理以及如何工作)在 Rust 中使用它们。我很乐意接受有关此的建议。)并且一切似乎都很完美。

实现并行函数以同时分析多个交易(AnalyseAllPlaysBin,链接到文档)并为其编写一些测试后,

cargo test
开始失败:

exit code: 0xc0000409, STATUS_STACK_BUFFER_OVERRUN
或与:

exit code: 0xc0000005, STATUS_ACCESS_VIOLATION
note: test exited abnormally; to see the full output pass --nocapture to the harness.
Segmentation fault

我有点惊讶,我挖掘了一下以理解这个问题。我发现失败的原因是

cargo
并行运行测试,并且我认为同时调用会扰乱 DLL,使其出现段错误。

使用

cargo t -- --test-threads=1
解决了问题,但由于我正在编写一个库,希望将来用于应用程序,所以我想了解问题并以某种方式解决它。我不打算同时调用多个 DLL,但这个问题仍然困扰着我。
我无法提供 MWE,但我会提供一些代码供参考:


use dds::{
    deal, // The `deal` type the C++ library uses
    solvedPlays, // Type of the C++ library
    solvedPlay,  // Same as above
    playTraceBin, // Same as above
    playTracesBin, // Same as above
    PlayTraceBin, // Cards played for the deal to analyze
    PlayTracesBin, // Collection of PlayTraceBin for different deals
    SolvedPlays, // My wrapper around the C++ type
    SolvedPlay, // Same
    RankSeq, // Sequence of the rank (2,3,Q,K,A ecc.) of cards played
    RawDDSRef, // Trait (fn get_raw(&self)) for getting a ref to the underlying struct of the Rust wrapper types
    RawDDSRefMut, // Same as above, but mut (fn get_raw_mut(&mut self))
    AsRawDDS, // Same as above, but not a ref (fn as_raw(self))
    SuitSeq, // Sequence of suits of cards played
    Target,Mode,Solutions // Parameters used by the DLL
    MAXNOOFBOARDS,
};

const TRIES: usize = 200;
const CHUNK_SIZE: i32 = 10;


pub fn initialize_test() -> DealMock {
    DealMock {
        hands: [
            [8712, 256114688, 2199023255552, 2344123606046343168],
            [22528, 10485760, 79182017069056, 744219838422974464],
            [484, 1612185600, 1924145348608, 4611686018427387904],
            [1040, 268435456, 57415122812928, 1522216674051227648],
        ],
    }
}

pub trait PlayAnalyzer {
    /// Analyzes a single hand
    /// # Errors
    /// Will return an Error when DDS fails in some way.
    fn analyze_play<D: AsDDSDeal, C: AsDDSContract>(
        deal: &D,
        contract: &C,
        play: PlayTraceBin,
    ) -> Result<SolvedPlay, DDSError>;
    /// Analyzes a bunch of hands in paraller.
    /// # Errors
    /// Will return an Error when DDS fails in some way or the deals and contracts vecs have
    /// different length or their length doe
    fn analyze_all_plays<D: AsDDSDeal, C: AsDDSContract>(
        deals: Vec<&D>,
        contracts: Vec<&C>,
        plays: &mut PlayTracesBin,
    ) -> Result<SolvedPlays, DDSError>;
}

impl PlayAnalyzer for DDSPlayAnalyzer {
    #[inline]
    fn analyze_all_plays<D: AsDDSDeal, C: AsDDSContract>(
        deals: Vec<&D>,
        contracts: Vec<&C>,
        plays: &mut PlayTracesBin,
    ) -> Result<SolvedPlays, DDSError> {
        let deals_len = i32::try_from(deals.len().clamp(0, MAXNOOFBOARDS)).unwrap();
        let contracts_len = i32::try_from(contracts.len().clamp(0, MAXNOOFBOARDS)).unwrap();

        if deals_len != contracts_len || deals_len == 0 || contracts_len == 0 {
            return Err(RETURN_UNKNOWN_FAULT.into()); // The error tells that
                // either something went terribly wrong or we used wrongly sized inputs.
        }

        let mut c_deals: Vec<deal> = contracts
            .into_iter()
            .zip(deals)
            .map(|(contract, deal)| construct_dds_deal(contract, deal))
            .collect();
        c_deals.resize(
            MAXNOOFBOARDS,
            deal {
                trump: -1,
                first: -1,
                currentTrickSuit: [-1i32; 3],
                currentTrickRank: [-1i32; 3],
                remainCards: [[0u32; 4]; 4],
            },
        );
        let mut boards = boards {
            noOfBoards: deals_len,
            // We know vec has the right length
            deals: match c_deals.try_into().unwrap(),
            target: [Target::MaxTricks.into(); MAXNOOFBOARDS],
            solutions: [Solutions::Best.into(); MAXNOOFBOARDS],
            mode: [Mode::Auto.into(); MAXNOOFBOARDS],
        };
        let mut solved_plays = SolvedPlays {
            solved_plays: solvedPlays {
                noOfBoards: deals_len,
                solved: [solvedPlay::new(); MAXNOOFBOARDS],
            },
        };

        let bop: *mut boards = &mut boards;
        let solved: *mut solvedPlays = solved_plays.get_raw_mut();
        let play_trace: *mut playTracesBin = (*plays).get_raw_mut();

        // SAFETY: calling C
        let result = unsafe { AnalyseAllPlaysBin(bop, play_trace, solved, CHUNK_SIZE) };
        match result {
            // RETURN_NO_FAULT == 1i32
            1i32 => Ok(solved_plays),
            n => Err(n.into()),
        }
    }

    #[inline]
    fn analyze_play<D: AsDDSDeal, C: AsDDSContract>(
        deal: &D,
        contract: &C,
        play: PlayTraceBin,
    ) -> Result<SolvedPlay, DDSError> {
        let c_deal = construct_dds_deal(contract, deal);
        let mut solved_play = SolvedPlay::new();
        let solved: *mut solvedPlay = &mut solved_play.solved_play;
        let play_trace = play.as_raw();
        // SAFETY: calling an external C function
        let result = unsafe { AnalysePlayBin(c_deal, play_trace, solved, 0) };
        match result {
            1i32 => Ok(solved_play),
            n => Err(n.into()),
        }
    }
}

/// Constructs a DDS deal from a DDS contract and a DDS deal representation
fn construct_dds_deal<D: AsDDSDeal, C: AsDDSContract>(contract: &C, deal: &D) -> deal {
    let (trump, first) = contract.as_dds_contract();
    deal {
        trump,
        first,
        currentTrickSuit: [0i32; 3],
        currentTrickRank: [0i32; 3],
        remainCards: deal.as_dds_deal().as_slice(),
    }
}


#[test]
fn analyse_play_test() {
    let deal = initialize_test();
    let contract = ContractMock {};
    let suitseq = SuitSeq::try_from([0i32, 0i32, 0i32, 0i32]).unwrap();
    let rankseq = RankSeq::try_from([4i32, 3i32, 12i32, 2i32]).unwrap();
    let play = PlayTraceBin::new(suitseq, rankseq);
    let solvedplay = DDSPlayAnalyzer::analyze_play(&deal, &contract, play).unwrap();
    assert_eq!([2, 2, 2, 2, 2], solvedplay.solved_play.tricks[..5]);
}

#[test]
fn analyse_all_play_test() {
    let mut deals_owner = Vec::with_capacity(TRIES);
    deals_owner.resize_with(TRIES, initialize_test);
    let deals = deals_owner.iter().collect();
    let suitseq = SuitSeq::try_from([0, 0, 0, 0]).unwrap();
    let rankseq = RankSeq::try_from([4, 3, 12, 2]).unwrap();
    let mut suitseqs = Vec::with_capacity(TRIES);
    let mut rankseqs = Vec::with_capacity(TRIES);
    suitseqs.resize_with(TRIES, || suitseq.clone());
    rankseqs.resize_with(TRIES, || rankseq.clone());
    let contracts_owner = Vec::from([ContractMock {}; TRIES]);
    let contracts = contracts_owner.iter().collect();
    let mut plays = PlayTracesBin::from_sequences(suitseqs, rankseqs).unwrap();
    let solved_plays = DDSPlayAnalyzer::analyze_all_plays(deals, contracts, &mut plays).unwrap();
    let real_plays = solved_plays.get_raw();
    assert_eq!(TRIES, real_plays.noOfBoards.try_into().unwrap());
    for plays in real_plays.solved {
        assert_eq!([2, 2, 2, 2, 2], plays.tricks[..5]);
    }
}

我可以将

DDSAnalyzer
包装在
Arc<Mutex<_>>
中,然后在外部函数调用期间锁定它。我认为这应该可行(没有时间尝试),但我不知道这是否是正确的方法。

我想问两件事:

  1. 为什么我在多线程情况下会出现这些错误?
  2. 使用
    Arc<Mutex<DDSAnalyzer>>
    有用吗?这是正确的解决方案还是我应该做一些不同的事情?

谢谢大家!

rust dll ffi
1个回答
0
投票

正如 @kmdreko 的评论中指出的,问题在于 DLL 的函数不是线程安全的,可能是由于全局状态不同步所致。
感谢他和 @nanofarad 的建议(以及最近快速浏览了

std::io::stdin()
),我通过将调用函数的结构包装在
Mutex<StructThatActuallyCallsDLL>
中以在调用函数时对其保持锁定来解决这个问题(因此,当另一个线程已经在运行 DLL 的函数时,我们不会让其他线程尝试调用该函数)并使用
OnceLock
来初始化管理 DLL 的结构体一次,并获得对它的
'static
引用:

struct DDSPlayAnalyzer {
    inner: &'static Mutex<DDSAnalyzerRaw>
}

impl DDSPlayAnalyzer {
    pub fn new() -> Self {
        static RAWDDSANALYZER: OnceLock<Mutex<DDSPlayAnalyzerRaw>> = OnceLock::new();
        Self {
            inner: RAWDDSANALYZER.get_or_init(|| Mutex::new(DDSPlayAnalyzerRaw {})),
        }
    }
}

然后通过锁定并调用包含的

DDSAnalyzer
上的函数来实现
DDSAnalyzerRaw
的正确特征。

© www.soinside.com 2019 - 2024. All rights reserved.