Как избежать тупика, вызванного паникой потока? - PullRequest
1 голос
/ 21 марта 2019

Мой сервер использует Barrier, чтобы уведомить клиента, когда можно попытаться подключиться. Без барьера мы рискуем случайно выйти из строя, так как нет гарантии, что сокет сервера был бы связан.

Теперь представьте, что сервер паникует - например, пытается привязать сокет к порту 80. Клиент останется wait() -ing навсегда. Мы не можем join() серверный поток, чтобы выяснить, не запаниковал ли он, потому что join() является блокирующей операцией - если мы join(), мы не сможем connect().

Как правильно выполнять такую ​​синхронизацию, учитывая, что API std::sync не предоставляют методы с тайм-аутами?

Это просто MCVE, чтобы продемонстрировать проблему. У меня был похожий случай в модульном тесте - он был запущен навсегда.

use std::{
    io::prelude::*,
    net::{SocketAddr, TcpListener, TcpStream},
    sync::{Arc, Barrier},
};

fn main() {
    let port = 9090;
    //let port = 80;

    let barrier = Arc::new(Barrier::new(2));
    let server_barrier = barrier.clone();

    let client_sync = move || {
        barrier.wait();
    };

    let server_sync = Box::new(move || {
        server_barrier.wait();
    });

    server(server_sync, port);
    //server(Box::new(|| { no_sync() }), port); //use to test without synchronisation

    client(&client_sync, port);
    //client(&no_sync, port); //use to test without synchronisation
}

fn no_sync() {
    // do nothing in order to demonstrate the need for synchronization
}

fn server(sync: Box<Fn() + Send + Sync>, port: u16) {
    std::thread::spawn(move || {
        std::thread::sleep_ms(100); //there is no guarantee when the os will schedule the thread. make it 100% reproducible
        let addr = SocketAddr::from(([127, 0, 0, 1], port));
        let socket = TcpListener::bind(&addr).unwrap();
        println!("server socket bound");
        sync();

        let (mut client, _) = socket.accept().unwrap();

        client.write_all(b"hello mcve").unwrap();
    });
}

fn client(sync: &Fn(), port: u16) {
    sync();

    let addr = SocketAddr::from(([127, 0, 0, 1], port));
    let mut socket = TcpStream::connect(&addr).unwrap();
    println!("client socket connected");

    let mut buf = String::new();
    socket.read_to_string(&mut buf).unwrap();
    println!("client received: {}", buf);
}

1 Ответ

2 голосов
/ 21 марта 2019

Вместо Barrier я бы использовал здесь Condvar.

Чтобы реально решить вашу проблему, я вижу как минимум три возможных решения:

  1. Используйте Condvar::wait_timeout и установите разумное время ожидания (например, 1 секунда, которого должно быть достаточно для привязки к порту)
  2. Вы можете использовать тот же метод, что и выше, но с меньшим временем ожидания (например, 10 мсек) и проверить, не отравлен ли Mutex.
  3. Вместо Condvar вы можете использовать простой Mutex (убедитесь, что Mutex заблокирован сначала другим потоком), а затем использовать Mutex::try_lock чтобы проверить, отравлен ли Mutex

Я думаю, что следует предпочесть решение 1 или 2 третьему, потому что вы избежите того, чтобы другой поток сначала заблокировал Mutex.

...