<commit_before>import cast = unsafe::reinterpret_cast;
import comm;
import option::{some, none};
import option = option::t;
import ptr;

export task;
export joinable_task;
export sleep;
export yield;
export task_notification;
export join;
export unsupervise;
export pin;
export unpin;
export set_min_stack;
export task_result;
export tr_success;
export tr_failure;
export get_task_id;
export spawn2;
export spawn_notify2;
export spawn_joinable2;

native "rust" mod rustrt {
    fn task_sleep(time_in_us: uint);
    fn task_yield();
    fn task_join(t: task_id) -> int;
    fn unsupervise();
    fn pin_task();
    fn unpin_task();
    fn get_task_id() -> task_id;

    type rust_chan;

    fn set_min_stack(stack_size: uint);

    fn new_task() -> task_id;
    fn drop_task(task: *rust_task);
    fn get_task_pointer(id: task_id) -> *rust_task;

    fn migrate_alloc(alloc: *u8, target: task_id);
    fn start_task(id: task_id, closure: *u8);
}

type rust_task =
    {id: task,
     mutable notify_enabled: u32,
     mutable notify_chan: comm::chan<task_notification>,
     mutable stack_ptr: *u8};

resource rust_task_ptr(task: *rust_task) { rustrt::drop_task(task); }

type task = int;
type task_id = task;
type joinable_task = (task_id, comm::port<task_notification>);

fn get_task_id() -> task_id { rustrt::get_task_id() }

/**
 * Hints the scheduler to yield this task for a specified ammount of time.
 *
 * arg: time_in_us maximum number of microseconds to yield control for
 */
fn sleep(time_in_us: uint) { ret rustrt::task_sleep(time_in_us); }

fn yield() { ret rustrt::task_yield(); }

tag task_result { tr_success; tr_failure; }

tag task_notification { exit(task, task_result); }

fn join(task_port: (task_id, comm::port<task_notification>)) -> task_result {
    let (id, port) = task_port;
    alt comm::recv::<task_notification>(port) {
      exit(_id, res) {
        if _id == id {
            ret res
        } else { fail #fmt["join received id %d, expected %d", _id, id] }
      }
    }
}

fn join_id(t: task_id) -> task_result {
    alt rustrt::task_join(t) { 0 { tr_success } _ { tr_failure } }
}

fn unsupervise() { ret rustrt::unsupervise(); }

fn pin() { rustrt::pin_task(); }

fn unpin() { rustrt::unpin_task(); }

fn set_min_stack(stack_size: uint) { rustrt::set_min_stack(stack_size); }

fn spawn2<~T>(-data: T, f: fn#(T)) -> task {
    spawn_inner2(data, f, none)
}

fn spawn_notify2<~T>(-data: T, f: fn#(T),
                         notify: comm::chan<task_notification>) -> task {
    spawn_inner2(data, f, some(notify))
}

fn spawn_joinable2<~T>(-data: T, f: fn#(T)) -> joinable_task {
    let p = comm::port::<task_notification>();
    let id = spawn_notify2(data, f, comm::chan::<task_notification>(p));
    ret (id, p);
}

// FIXME: To transition from the unsafe spawn that spawns a shared closure to
// the safe spawn that spawns a bare function we're going to write
// barefunc-spawn on top of unsafe-spawn.  Sadly, bind does not work reliably
// enough to suite our needs (#1034, probably others yet to be discovered), so
// we're going to copy the bootstrap data into a unique pointer, cast it to an
// unsafe pointer then wrap up the bare function and the unsafe pointer in a
// shared closure to spawn.
//
// After the transition this should all be rewritten.

fn spawn_inner2<~T>(-data: T, f: fn#(T),
                    notify: option<comm::chan<task_notification>>)
    -> task_id {

    fn wrapper<~T>(-data: *u8, f: fn#(T)) {
        let data: ~T = unsafe::reinterpret_cast(data);
        f(*data);
    }

    let data = ~data;
    let dataptr: *u8 = unsafe::reinterpret_cast(data);
    unsafe::leak(data);
    let wrapped = bind wrapper(dataptr, f);
    ret spawn_inner(wrapped, notify);
}

fn spawn(-thunk: fn()) -> task { spawn_inner(thunk, none) }

fn spawn_notify(-thunk: fn(), notify: comm::chan<task_notification>) -> task {
    spawn_inner(thunk, some(notify))
}

fn spawn_joinable(-thunk: fn()) -> joinable_task {
    let p = comm::port::<task_notification>();
    let id = spawn_notify(thunk, comm::chan::<task_notification>(p));
    ret (id, p);
}

// FIXME: make this a fn~ once those are supported.
fn spawn_inner(-thunk: fn(), notify: option<comm::chan<task_notification>>) ->
   task_id unsafe {
    let id = rustrt::new_task();

    let raw_thunk: {code: u32, env: u32} = cast(thunk);

    // set up the task pointer
    let task_ptr <- rust_task_ptr(rustrt::get_task_pointer(id));

    assert (ptr::null() != (**task_ptr).stack_ptr);

    // copy the thunk from our stack to the new stack
    let sp: uint = cast((**task_ptr).stack_ptr);
    let ptrsize = sys::size_of::<*u8>();
    let thunkfn: *mutable uint = cast(sp - ptrsize * 2u);
    let thunkenv: *mutable uint = cast(sp - ptrsize);
    *thunkfn = cast(raw_thunk.code);;
    *thunkenv = cast(raw_thunk.env);;
    // align the stack to 16 bytes
    (**task_ptr).stack_ptr = cast(sp - ptrsize * 4u);

    // set up notifications if they are enabled.
    alt notify {
      some(c) {
        (**task_ptr).notify_enabled = 1u32;;
        (**task_ptr).notify_chan = c;
      }
      none { }
    }

    // give the thunk environment's allocation to the new task
    rustrt::migrate_alloc(cast(raw_thunk.env), id);
    rustrt::start_task(id, cast(thunkfn));
    // don't cleanup the thunk in this task
    unsafe::leak(thunk);
    ret id;
}

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<commit_msg>Rename std::task::spawn_inner to unsafe_spawn_inner<commit_after>import cast = unsafe::reinterpret_cast;
import comm;
import option::{some, none};
import option = option::t;
import ptr;

export task;
export joinable_task;
export sleep;
export yield;
export task_notification;
export join;
export unsupervise;
export pin;
export unpin;
export set_min_stack;
export task_result;
export tr_success;
export tr_failure;
export get_task_id;
export spawn2;
export spawn_notify2;
export spawn_joinable2;

native "rust" mod rustrt {
    fn task_sleep(time_in_us: uint);
    fn task_yield();
    fn task_join(t: task_id) -> int;
    fn unsupervise();
    fn pin_task();
    fn unpin_task();
    fn get_task_id() -> task_id;

    type rust_chan;

    fn set_min_stack(stack_size: uint);

    fn new_task() -> task_id;
    fn drop_task(task: *rust_task);
    fn get_task_pointer(id: task_id) -> *rust_task;

    fn migrate_alloc(alloc: *u8, target: task_id);
    fn start_task(id: task_id, closure: *u8);
}

type rust_task =
    {id: task,
     mutable notify_enabled: u32,
     mutable notify_chan: comm::chan<task_notification>,
     mutable stack_ptr: *u8};

resource rust_task_ptr(task: *rust_task) { rustrt::drop_task(task); }

type task = int;
type task_id = task;
type joinable_task = (task_id, comm::port<task_notification>);

fn get_task_id() -> task_id { rustrt::get_task_id() }

/**
 * Hints the scheduler to yield this task for a specified ammount of time.
 *
 * arg: time_in_us maximum number of microseconds to yield control for
 */
fn sleep(time_in_us: uint) { ret rustrt::task_sleep(time_in_us); }

fn yield() { ret rustrt::task_yield(); }

tag task_result { tr_success; tr_failure; }

tag task_notification { exit(task, task_result); }

fn join(task_port: (task_id, comm::port<task_notification>)) -> task_result {
    let (id, port) = task_port;
    alt comm::recv::<task_notification>(port) {
      exit(_id, res) {
        if _id == id {
            ret res
        } else { fail #fmt["join received id %d, expected %d", _id, id] }
      }
    }
}

fn join_id(t: task_id) -> task_result {
    alt rustrt::task_join(t) { 0 { tr_success } _ { tr_failure } }
}

fn unsupervise() { ret rustrt::unsupervise(); }

fn pin() { rustrt::pin_task(); }

fn unpin() { rustrt::unpin_task(); }

fn set_min_stack(stack_size: uint) { rustrt::set_min_stack(stack_size); }

fn spawn2<~T>(-data: T, f: fn#(T)) -> task {
    spawn_inner2(data, f, none)
}

fn spawn_notify2<~T>(-data: T, f: fn#(T),
                         notify: comm::chan<task_notification>) -> task {
    spawn_inner2(data, f, some(notify))
}

fn spawn_joinable2<~T>(-data: T, f: fn#(T)) -> joinable_task {
    let p = comm::port::<task_notification>();
    let id = spawn_notify2(data, f, comm::chan::<task_notification>(p));
    ret (id, p);
}

// FIXME: To transition from the unsafe spawn that spawns a shared closure to
// the safe spawn that spawns a bare function we're going to write
// barefunc-spawn on top of unsafe-spawn.  Sadly, bind does not work reliably
// enough to suite our needs (#1034, probably others yet to be discovered), so
// we're going to copy the bootstrap data into a unique pointer, cast it to an
// unsafe pointer then wrap up the bare function and the unsafe pointer in a
// shared closure to spawn.
//
// After the transition this should all be rewritten.

fn spawn_inner2<~T>(-data: T, f: fn#(T),
                    notify: option<comm::chan<task_notification>>)
    -> task_id {

    fn wrapper<~T>(-data: *u8, f: fn#(T)) {
        let data: ~T = unsafe::reinterpret_cast(data);
        f(*data);
    }

    let data = ~data;
    let dataptr: *u8 = unsafe::reinterpret_cast(data);
    unsafe::leak(data);
    let wrapped = bind wrapper(dataptr, f);
    ret unsafe_spawn_inner(wrapped, notify);
}

// FIXME: This is the old spawn function that spawns a shared closure.
// It is a hack and needs to be rewritten.
fn unsafe_spawn_inner(-thunk: fn(),
                      notify: option<comm::chan<task_notification>>) ->
   task_id unsafe {
    let id = rustrt::new_task();

    let raw_thunk: {code: u32, env: u32} = cast(thunk);

    // set up the task pointer
    let task_ptr <- rust_task_ptr(rustrt::get_task_pointer(id));

    assert (ptr::null() != (**task_ptr).stack_ptr);

    // copy the thunk from our stack to the new stack
    let sp: uint = cast((**task_ptr).stack_ptr);
    let ptrsize = sys::size_of::<*u8>();
    let thunkfn: *mutable uint = cast(sp - ptrsize * 2u);
    let thunkenv: *mutable uint = cast(sp - ptrsize);
    *thunkfn = cast(raw_thunk.code);;
    *thunkenv = cast(raw_thunk.env);;
    // align the stack to 16 bytes
    (**task_ptr).stack_ptr = cast(sp - ptrsize * 4u);

    // set up notifications if they are enabled.
    alt notify {
      some(c) {
        (**task_ptr).notify_enabled = 1u32;;
        (**task_ptr).notify_chan = c;
      }
      none { }
    }

    // give the thunk environment's allocation to the new task
    rustrt::migrate_alloc(cast(raw_thunk.env), id);
    rustrt::start_task(id, cast(thunkfn));
    // don't cleanup the thunk in this task
    unsafe::leak(thunk);
    ret id;
}

// Local Variables:
// mode: rust;
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
<|endoftext|>


<commit_before>//! ICWS '94 draft
//!

use std::cmp::PartialEq;
use std::default::Default;
use std::ops::{Index, IndexMut};

// #[derive(Clone, Copy, Debug, Default)]
// pub struct Address(u32);
pub type Address = u32;

#[derive(Clone, Copy, Debug)]
pub enum Opcode {
    DAT, // terminate process
    MOV, // move from A to B
    ADD, // add A to B, store result in B
    SUB, // subtract A from B, store result in B
    MUL, // multiply A by B, store result in B
    DIV, // divide B by A, store result in B if A <> 0, else terminate
    MOD, // divide B by A, store remainder in B if A <> 0, else terminate
    JMP, // transfer execution to A
    JMZ, // transfer execution to A if B is zero
    JMN, // transfer execution to A if B is non-zero
    DJN, // decrement B, if B is non-zero, transfer execution to A
    CMP, // same as SEQ
    SLT, // skip next instruction if A is less than B
    SPL, // split off process to A
    SEQ, // (*) Skip next instruction if A is equal to B
    SNE, // (*) Skip next instruction if A is not equal to B
    NOP, // (*) No operation
    LDP, // (+) Load P-space cell A into core address B
    STP, // (+) Store A-number into P-space cell B
}

impl Default for Opcode {
    fn default() -> Self {
        Opcode::DAT
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Modifier {
    A,
    B,
    AB,
    BA,
    F,
    X,
    I,
}

impl Default for Modifier {
    fn default() -> Self {
        Modifier::A
    }
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Mode {
    IMMEDIATE, // #
    DIRECT, // $ (Default)
    INDIRECT, // @
    DECREMENT, // <
    INCREMENT, // >
}

impl Default for Mode {
    fn default() -> Self {
        Mode::IMMEDIATE
    }
}

#[derive(Clone, Copy, Debug, Default)]
pub struct Instruction {
    pub opcode: Opcode,
    pub modifier: Modifier,
    pub a_mode: Mode,
    pub a_number: Address,
    pub b_mode: Mode,
    pub b_number: Address,
}

impl Instruction {
    pub fn random() -> Self {
        Instruction::default()  // XXX Need real random implementation
    }
}

#[derive(Clone, Copy, Debug, Default)]
pub struct Warrior(u32);

// #[derive(Debug)]
// struct TaskPointer(Address);

#[derive(Debug)]
pub struct TaskQueue {
    task_limit: usize,
    task_pointer: Vec<Address>,
}

impl TaskQueue {
    fn new(limit: usize) -> Self {
        TaskQueue {
            task_limit: limit,
            task_pointer: vec![0,],
        }
    }

    pub fn queue(&mut self, addr: Address) {
        self.task_pointer.push(addr);
    }
}

#[derive(Debug)]
struct CoreMemory(Vec<Instruction>);

impl Index<Address> for CoreMemory {
    type Output = Instruction;

    fn index<'a>(&'a self, _index: Address) -> &'a Instruction {
        &self.0[_index as usize]
    }
}

impl IndexMut<Address> for CoreMemory {
    fn index_mut<'a>(&'a mut self, _index: Address) -> &'a mut Instruction {
        &mut self.0[_index as usize]
    }
}

#[derive(Debug)]
pub struct Core {
    core_size: Address,
    core: CoreMemory,
    warrior: [TaskQueue; 2],
    pc: Address,
    read_limit: Address,
    write_limit: Address,
}

impl Core {
    pub fn queue(&mut self, warrior: usize, task_pointer: Address) {
        self.warrior[warrior].queue(task_pointer)
    }

    fn fold(&self, pointer: Address, limit: Address) -> Address {
        let mut result = pointer % limit;
        if result > limit / 2 {
            result += self.core_size - limit;
        }
        result
    }

    pub fn emi94(&mut self) {
        let ir: Instruction;
        let ira: Instruction;
        let irb: Instruction;
        let mut rpa: Address;
        let mut wpa: Address;
        let rpb: Address;
        let wpb: Address;
        let pip: Address;

        ir = self.core[self.pc];

        // Evaluate A-operand
        //
        if ir.a_mode == Mode::IMMEDIATE {
            // For instructions with an Immediate A-mode, the Pointer A
            // points to the source of the current instruction.
            rpa = 0;
            wpa = 0;
        } else {
            // For instructions with a Direct A-mode, the Pointer A
            // points to the instruction IR.ANumber away, relative to
            // the Program Counter.
            rpa = self.fold(ir.a_number, self.read_limit);
            wpa = self.fold(ir.a_number, self.write_limit);

            if ir.a_mode != Mode::DIRECT {
                if ir.a_mode == Mode::DECREMENT {
                    self.core[((self.pc + wpa) % self.core_size)].b_number =
                        (self.core[((self.pc + wpa) % self.core_size)].b_number +
                         self.core_size - 1) % self.core_size;
                }

                if ir.a_mode == Mode::INCREMENT {
                    pip = (self.pc + wpa) % self.core_size;
                }

                rpa = self.fold((rpa + self.core[((self.pc + rpa) % self.core_size)].b_number),
                                self.read_limit);
                wpa = self.fold((wpa + self.core[((self.pc + wpa) % self.core_size)].b_number),
                                self.write_limit);
            }
        }

    }
}

#[derive(Debug)]
pub enum InitialInstruction {
    Default,
    Random,
    Instruction(Instruction),
}

#[derive(Debug)]
pub struct CoreBuilder {
    core_size: u32,
    initial_instruction: InitialInstruction,
    task_limit: usize,
}

impl CoreBuilder {
    pub fn new() -> Self {
        CoreBuilder {
            core_size: 8192,
            initial_instruction: InitialInstruction::Default,
            task_limit: 64,
        }
    }

    pub fn coresize(&mut self, size: u32) -> &Self {
        self.core_size = size;
        self
    }

    pub fn initial_instruction(&mut self, insn: InitialInstruction) -> &Self {
        self.initial_instruction = insn;
        self
    }

    pub fn task_limit(&mut self, limit: usize) -> &Self {
        self.task_limit = limit;
        self
    }

    pub fn build(&self) -> Core {
        let mut instructions = Vec::<Instruction>::new();
        for _ in 0..self.core_size {
            instructions.push(match self.initial_instruction {
                InitialInstruction::Default => Instruction::default(),
                InitialInstruction::Random => Instruction::random(),
                InitialInstruction::Instruction(insn) => insn,
            })
        }

        Core {
            core_size: self.core_size,
            core: CoreMemory(instructions),
            warrior: [TaskQueue::new(self.task_limit), TaskQueue::new(self.task_limit)],
            pc: 0,
            read_limit: 300,
            write_limit: 300,
        }
    }
}

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {}
}
<commit_msg>Add TaskQueue tests<commit_after>//! ICWS '94 draft
//!

use std::cmp::PartialEq;
use std::default::Default;
use std::ops::{Index, IndexMut};

// #[derive(Clone, Copy, Debug, Default)]
// pub struct Address(u32);
pub type Address = u32;

#[derive(Clone, Copy, Debug)]
pub enum Opcode {
    DAT, // terminate process
    MOV, // move from A to B
    ADD, // add A to B, store result in B
    SUB, // subtract A from B, store result in B
    MUL, // multiply A by B, store result in B
    DIV, // divide B by A, store result in B if A <> 0, else terminate
    MOD, // divide B by A, store remainder in B if A <> 0, else terminate
    JMP, // transfer execution to A
    JMZ, // transfer execution to A if B is zero
    JMN, // transfer execution to A if B is non-zero
    DJN, // decrement B, if B is non-zero, transfer execution to A
    CMP, // same as SEQ
    SLT, // skip next instruction if A is less than B
    SPL, // split off process to A
    SEQ, // (*) Skip next instruction if A is equal to B
    SNE, // (*) Skip next instruction if A is not equal to B
    NOP, // (*) No operation
    LDP, // (+) Load P-space cell A into core address B
    STP, // (+) Store A-number into P-space cell B
}

impl Default for Opcode {
    fn default() -> Self {
        Opcode::DAT
    }
}

#[derive(Clone, Copy, Debug)]
pub enum Modifier {
    A,
    B,
    AB,
    BA,
    F,
    X,
    I,
}

impl Default for Modifier {
    fn default() -> Self {
        Modifier::A
    }
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Mode {
    IMMEDIATE, // #
    DIRECT, // $ (Default)
    INDIRECT, // @
    DECREMENT, // <
    INCREMENT, // >
}

impl Default for Mode {
    fn default() -> Self {
        Mode::IMMEDIATE
    }
}

#[derive(Clone, Copy, Debug, Default)]
pub struct Instruction {
    pub opcode: Opcode,
    pub modifier: Modifier,
    pub a_mode: Mode,
    pub a_number: Address,
    pub b_mode: Mode,
    pub b_number: Address,
}

impl Instruction {
    pub fn random() -> Self {
        Instruction::default()  // XXX Need real random implementation
    }
}

#[derive(Clone, Copy, Debug, Default)]
pub struct Warrior(u32);

#[derive(Debug)]
pub struct TaskQueue {
    task_limit: usize,
    task_pointer: Vec<Address>,
}

impl TaskQueue {
    fn new(limit: usize) -> Self {
        TaskQueue {
            task_limit: limit,
            task_pointer: Vec::<Address>::with_capacity(limit),
        }
    }

    pub fn queue(&mut self, addr: Address) {
        if self.task_pointer.len() < self.task_limit {
            self.task_pointer.push(addr);
        }
    }

    pub fn next(&mut self) -> Option<Address> {
        if self.task_pointer.len() > 0 {
            Some(self.task_pointer.remove(0))
        } else {
            None
        }
    }
}

#[derive(Debug)]
struct CoreMemory(Vec<Instruction>);

impl Index<Address> for CoreMemory {
    type Output = Instruction;

    fn index<'a>(&'a self, _index: Address) -> &'a Instruction {
        &self.0[_index as usize]
    }
}

impl IndexMut<Address> for CoreMemory {
    fn index_mut<'a>(&'a mut self, _index: Address) -> &'a mut Instruction {
        &mut self.0[_index as usize]
    }
}

#[derive(Debug)]
pub struct Core {
    core_size: Address,
    core: CoreMemory,
    warrior: [TaskQueue; 2],
    pc: Address,
    read_limit: Address,
    write_limit: Address,
}

impl Core {
    pub fn queue(&mut self, warrior: usize, task_pointer: Address) {
        self.warrior[warrior].queue(task_pointer)
    }

    fn fold(&self, pointer: Address, limit: Address) -> Address {
        let mut result = pointer % limit;
        if result > limit / 2 {
            result += self.core_size - limit;
        }
        result
    }

    pub fn emi94(&mut self) {
        let ir: Instruction;
        let ira: Instruction;
        let irb: Instruction;
        let mut rpa: Address;
        let mut wpa: Address;
        let rpb: Address;
        let wpb: Address;
        let pip: Address;

        ir = self.core[self.pc];

        // Evaluate A-operand
        //
        if ir.a_mode == Mode::IMMEDIATE {
            // For instructions with an Immediate A-mode, the Pointer A
            // points to the source of the current instruction.
            rpa = 0;
            wpa = 0;
        } else {
            // For instructions with a Direct A-mode, the Pointer A
            // points to the instruction IR.ANumber away, relative to
            // the Program Counter.
            rpa = self.fold(ir.a_number, self.read_limit);
            wpa = self.fold(ir.a_number, self.write_limit);

            if ir.a_mode != Mode::DIRECT {
                if ir.a_mode == Mode::DECREMENT {
                    self.core[((self.pc + wpa) % self.core_size)].b_number =
                        (self.core[((self.pc + wpa) % self.core_size)].b_number +
                         self.core_size - 1) % self.core_size;
                }

                if ir.a_mode == Mode::INCREMENT {
                    pip = (self.pc + wpa) % self.core_size;
                }

                rpa = self.fold((rpa + self.core[((self.pc + rpa) % self.core_size)].b_number),
                                self.read_limit);
                wpa = self.fold((wpa + self.core[((self.pc + wpa) % self.core_size)].b_number),
                                self.write_limit);
            }
        }

    }
}

#[derive(Debug)]
pub enum InitialInstruction {
    Default,
    Random,
    Instruction(Instruction),
}

#[derive(Debug)]
pub struct CoreBuilder {
    core_size: u32,
    initial_instruction: InitialInstruction,
    task_limit: usize,
}

impl CoreBuilder {
    pub fn new() -> Self {
        CoreBuilder {
            core_size: 8192,
            initial_instruction: InitialInstruction::Default,
            task_limit: 64,
        }
    }

    pub fn coresize(&mut self, size: u32) -> &Self {
        self.core_size = size;
        self
    }

    pub fn initial_instruction(&mut self, insn: InitialInstruction) -> &Self {
        self.initial_instruction = insn;
        self
    }

    pub fn task_limit(&mut self, limit: usize) -> &Self {
        self.task_limit = limit;
        self
    }

    pub fn build(&self) -> Core {
        let mut instructions = Vec::<Instruction>::new();
        for _ in 0..self.core_size {
            instructions.push(match self.initial_instruction {
                InitialInstruction::Default => Instruction::default(),
                InitialInstruction::Random => Instruction::random(),
                InitialInstruction::Instruction(insn) => insn,
            })
        }

        Core {
            core_size: self.core_size,
            core: CoreMemory(instructions),
            warrior: [TaskQueue::new(self.task_limit), TaskQueue::new(self.task_limit)],
            pc: 0,
            read_limit: 300,
            write_limit: 300,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn task_queue_simple() {
        let mut tq = TaskQueue::new(2);
        tq.queue(1);
        tq.queue(3);
        assert_eq!(tq.next(), Some(1));
        assert_eq!(tq.next(), Some(3));
        assert_eq!(tq.next(), None);
    }

    #[test]
    fn task_queue_limit() {
        let mut tq = TaskQueue::new(3);
        tq.queue(100);
        tq.queue(200);
        tq.queue(300);
        tq.queue(400);

        assert_eq!(tq.next(), Some(100));
        assert_eq!(tq.next(), Some(200));
        assert_eq!(tq.next(), Some(300));
        assert_eq!(tq.next(), None);
    }

    #[test]
    fn task_queue_empty() {
        let mut tq = TaskQueue::new(100);

        assert_eq!(tq.next(), None);
    }
}
<|endoftext|>


<commit_before>// test1.rs
// This is a test for the following sections:
// - Variables
// - Functions

// Mary is buying apples. One apple usually costs 2 dollars, but if you buy
// 40 or more at once, each apple only costs 1! Write a function that calculates
// the price of an order of apples given the order amount. No hints this time!

// Put your function here!
// fn ..... {

// Don't modify this function!
#[test]
fn verify_test() {
    let price1 = calculate_price(55);
    let price2 = calculate_price(40);

    assert_eq!(55, price1);
    assert_eq!(80, price2);
}
<commit_msg>fix: Rewrite test1 logic<commit_after>// test1.rs
// This is a test for the following sections:
// - Variables
// - Functions

// Mary is buying apples. One apple usually costs 2 dollars, but if you buy
// more than 40 at once, each apple only costs 1! Write a function that calculates
// the price of an order of apples given the order amount. No hints this time!

// Put your function here!
// fn ..... {

// Don't modify this function!
#[test]
fn verify_test() {
    let price1 = calculate_apple_price(35);
    let price2 = calculate_apple_price(65);

    assert_eq!(70, price1);
    assert_eq!(65, price2);
}
<|endoftext|>


<commit_before>use std::sync::atomic::{compiler_fence, fence, AtomicBool, AtomicIsize, AtomicU64, Ordering::*};

fn main() {
    atomic_bool();
    atomic_isize();
    atomic_u64();
    atomic_fences();
    weak_sometimes_fails();
}

fn atomic_bool() {
    static mut ATOMIC: AtomicBool = AtomicBool::new(false);

    unsafe {
        assert_eq!(*ATOMIC.get_mut(), false);
        ATOMIC.store(true, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), true);
        ATOMIC.fetch_or(false, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), true);
        ATOMIC.fetch_and(false, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), false);
        ATOMIC.fetch_nand(true, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), true);
        ATOMIC.fetch_xor(true, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), false);
    }
}
// There isn't a trait to use to make this generic, so just use a macro
macro_rules! compare_exchange_weak_loop {
    ($atom:expr, $from:expr, $to:expr, $succ_order:expr, $fail_order:expr) => {
        loop {
            match $atom.compare_exchange_weak($from, $to, $succ_order, $fail_order) {
                Ok(n) => {
                    assert_eq!(n, $from);
                    break;
                }
                Err(n) => assert_eq!(n, $from),
            }
        }
    };
}
fn atomic_isize() {
    static ATOMIC: AtomicIsize = AtomicIsize::new(0);

    // Make sure trans can emit all the intrinsics correctly
    assert_eq!(ATOMIC.compare_exchange(0, 1, Relaxed, Relaxed), Ok(0));
    assert_eq!(ATOMIC.compare_exchange(0, 2, Acquire, Relaxed), Err(1));
    assert_eq!(ATOMIC.compare_exchange(0, 1, Release, Relaxed), Err(1));
    assert_eq!(ATOMIC.compare_exchange(1, 0, AcqRel, Relaxed), Ok(1));
    ATOMIC.compare_exchange(0, 1, SeqCst, Relaxed).ok();
    ATOMIC.compare_exchange(0, 1, Acquire, Acquire).ok();
    ATOMIC.compare_exchange(0, 1, AcqRel, Acquire).ok();
    ATOMIC.compare_exchange(0, 1, SeqCst, Acquire).ok();
    ATOMIC.compare_exchange(0, 1, SeqCst, SeqCst).ok();

    ATOMIC.store(0, SeqCst);
    compare_exchange_weak_loop!(ATOMIC, 0, 1, Relaxed, Relaxed);
    assert_eq!(ATOMIC.compare_exchange_weak(0, 2, Acquire, Relaxed), Err(1));
    assert_eq!(ATOMIC.compare_exchange_weak(0, 1, Release, Relaxed), Err(1));
    compare_exchange_weak_loop!(ATOMIC, 1, 0, AcqRel, Relaxed);
    assert_eq!(ATOMIC.load(Relaxed), 0);
    ATOMIC.compare_exchange_weak(0, 1, AcqRel, Relaxed).ok();
    ATOMIC.compare_exchange_weak(0, 1, SeqCst, Relaxed).ok();
    ATOMIC.compare_exchange_weak(0, 1, Acquire, Acquire).ok();
    ATOMIC.compare_exchange_weak(0, 1, AcqRel, Acquire).ok();
    ATOMIC.compare_exchange_weak(0, 1, SeqCst, Acquire).ok();
    ATOMIC.compare_exchange_weak(0, 1, SeqCst, SeqCst).ok();
}

fn atomic_u64() {
    static ATOMIC: AtomicU64 = AtomicU64::new(0);

    ATOMIC.store(1, SeqCst);
    assert_eq!(ATOMIC.compare_exchange(0, 0x100, AcqRel, Acquire), Err(1));
    compare_exchange_weak_loop!(ATOMIC, 1, 0x100, AcqRel, Acquire);
    assert_eq!(ATOMIC.load(Relaxed), 0x100);

    assert_eq!(ATOMIC.fetch_max(0x10, SeqCst), 0x100);
    assert_eq!(ATOMIC.fetch_max(0x100, SeqCst), 0x100);
    assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x100);
    assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x1000);
    assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x1000);
    assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x2000);

    assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000);
    assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000);
    assert_eq!(ATOMIC.fetch_min(0x1000, SeqCst), 0x2000);
    assert_eq!(ATOMIC.fetch_min(0x1000, SeqCst), 0x1000);
    assert_eq!(ATOMIC.fetch_min(0x100, SeqCst), 0x1000);
    assert_eq!(ATOMIC.fetch_min(0x10, SeqCst), 0x100);
}

fn atomic_fences() {
    fence(SeqCst);
    fence(Release);
    fence(Acquire);
    fence(AcqRel);
    compiler_fence(SeqCst);
    compiler_fence(Release);
    compiler_fence(Acquire);
    compiler_fence(AcqRel);
}

fn weak_sometimes_fails() {
    let atomic = AtomicBool::new(false);
    let tries = 100;
    for _ in 0..tries {
        let cur = atomic.load(Relaxed);
        // Try (weakly) to flip the flag.
        if atomic.compare_exchange_weak(cur, !cur, Relaxed, Relaxed).is_err() {
            // We failed, so return and skip the panic.
            return;
        }
    }
    panic!("compare_exchange_weak succeeded {} tries in a row", tries);
}
<commit_msg>improve atomics test coverage<commit_after>use std::sync::atomic::{compiler_fence, fence, AtomicBool, AtomicIsize, AtomicU64, Ordering::*};

fn main() {
    atomic_bool();
    atomic_all_ops();
    atomic_u64();
    atomic_fences();
    weak_sometimes_fails();
}

fn atomic_bool() {
    static mut ATOMIC: AtomicBool = AtomicBool::new(false);

    unsafe {
        assert_eq!(*ATOMIC.get_mut(), false);
        ATOMIC.store(true, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), true);
        ATOMIC.fetch_or(false, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), true);
        ATOMIC.fetch_and(false, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), false);
        ATOMIC.fetch_nand(true, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), true);
        ATOMIC.fetch_xor(true, SeqCst);
        assert_eq!(*ATOMIC.get_mut(), false);
    }
}

// There isn't a trait to use to make this generic, so just use a macro
macro_rules! compare_exchange_weak_loop {
    ($atom:expr, $from:expr, $to:expr, $succ_order:expr, $fail_order:expr) => {
        loop {
            match $atom.compare_exchange_weak($from, $to, $succ_order, $fail_order) {
                Ok(n) => {
                    assert_eq!(n, $from);
                    break;
                }
                Err(n) => assert_eq!(n, $from),
            }
        }
    };
}

/// Make sure we can handle all the intrinsics
fn atomic_all_ops() {
    static ATOMIC: AtomicIsize = AtomicIsize::new(0);
    static ATOMIC_UNSIGNED: AtomicU64 = AtomicU64::new(0);

    // loads
    for o in [Relaxed, Acquire, SeqCst] {
        ATOMIC.load(o);
    }

    // stores
    for o in [Relaxed, Release, SeqCst] {
        ATOMIC.store(1, o);
    }

    // most RMWs
    for o in [Relaxed, Release, Acquire, AcqRel, SeqCst] {
        ATOMIC.swap(0, o);
        ATOMIC.fetch_or(0, o);
        ATOMIC.fetch_xor(0, o);
        ATOMIC.fetch_and(0, o);
        ATOMIC.fetch_nand(0, o);
        ATOMIC.fetch_add(0, o);
        ATOMIC.fetch_sub(0, o);
        ATOMIC.fetch_min(0, o);
        ATOMIC.fetch_max(0, o);
        ATOMIC_UNSIGNED.fetch_min(0, o);
        ATOMIC_UNSIGNED.fetch_max(0, o);
    }

    // RMWs with deparate failure ordering
    ATOMIC.store(0, SeqCst);
    assert_eq!(ATOMIC.compare_exchange(0, 1, Relaxed, Relaxed), Ok(0));
    assert_eq!(ATOMIC.compare_exchange(0, 2, Acquire, Relaxed), Err(1));
    assert_eq!(ATOMIC.compare_exchange(0, 1, Release, Relaxed), Err(1));
    assert_eq!(ATOMIC.compare_exchange(1, 0, AcqRel, Relaxed), Ok(1));
    ATOMIC.compare_exchange(0, 1, SeqCst, Relaxed).ok();
    ATOMIC.compare_exchange(0, 1, Acquire, Acquire).ok();
    ATOMIC.compare_exchange(0, 1, AcqRel, Acquire).ok();
    ATOMIC.compare_exchange(0, 1, SeqCst, Acquire).ok();
    ATOMIC.compare_exchange(0, 1, SeqCst, SeqCst).ok();

    ATOMIC.store(0, SeqCst);
    compare_exchange_weak_loop!(ATOMIC, 0, 1, Relaxed, Relaxed);
    assert_eq!(ATOMIC.compare_exchange_weak(0, 2, Acquire, Relaxed), Err(1));
    assert_eq!(ATOMIC.compare_exchange_weak(0, 1, Release, Relaxed), Err(1));
    compare_exchange_weak_loop!(ATOMIC, 1, 0, AcqRel, Relaxed);
    assert_eq!(ATOMIC.load(Relaxed), 0);
    ATOMIC.compare_exchange_weak(0, 1, SeqCst, Relaxed).ok();
    ATOMIC.compare_exchange_weak(0, 1, Acquire, Acquire).ok();
    ATOMIC.compare_exchange_weak(0, 1, AcqRel, Acquire).ok();
    ATOMIC.compare_exchange_weak(0, 1, SeqCst, Acquire).ok();
    ATOMIC.compare_exchange_weak(0, 1, SeqCst, SeqCst).ok();
}

fn atomic_u64() {
    static ATOMIC: AtomicU64 = AtomicU64::new(0);

    ATOMIC.store(1, SeqCst);
    assert_eq!(ATOMIC.compare_exchange(0, 0x100, AcqRel, Acquire), Err(1));
    compare_exchange_weak_loop!(ATOMIC, 1, 0x100, AcqRel, Acquire);
    assert_eq!(ATOMIC.load(Relaxed), 0x100);

    assert_eq!(ATOMIC.fetch_max(0x10, SeqCst), 0x100);
    assert_eq!(ATOMIC.fetch_max(0x100, SeqCst), 0x100);
    assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x100);
    assert_eq!(ATOMIC.fetch_max(0x1000, SeqCst), 0x1000);
    assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x1000);
    assert_eq!(ATOMIC.fetch_max(0x2000, SeqCst), 0x2000);

    assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000);
    assert_eq!(ATOMIC.fetch_min(0x2000, SeqCst), 0x2000);
    assert_eq!(ATOMIC.fetch_min(0x1000, SeqCst), 0x2000);
    assert_eq!(ATOMIC.fetch_min(0x1000, SeqCst), 0x1000);
    assert_eq!(ATOMIC.fetch_min(0x100, SeqCst), 0x1000);
    assert_eq!(ATOMIC.fetch_min(0x10, SeqCst), 0x100);
}

fn atomic_fences() {
    fence(SeqCst);
    fence(Release);
    fence(Acquire);
    fence(AcqRel);
    compiler_fence(SeqCst);
    compiler_fence(Release);
    compiler_fence(Acquire);
    compiler_fence(AcqRel);
}

fn weak_sometimes_fails() {
    let atomic = AtomicBool::new(false);
    let tries = 100;
    for _ in 0..tries {
        let cur = atomic.load(Relaxed);
        // Try (weakly) to flip the flag.
        if atomic.compare_exchange_weak(cur, !cur, Relaxed, Relaxed).is_err() {
            // We failed, so return and skip the panic.
            return;
        }
    }
    panic!("compare_exchange_weak succeeded {} tries in a row", tries);
}
<|endoftext|>


<commit_before>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Protocol used to ensure peer liveness
//!
//! The HealthChecker is responsible for ensuring liveness of all peers of a node.
//! It does so by periodically selecting a random connected peer and sending a Ping probe. A
//! healthy peer is expected to respond with a corresponding Pong message.
//!
//! If a certain number of successive liveness probes for a peer fail, the HealthChecker initiates a
//! disconnect from the peer. It relies on ConnectivityManager or the remote peer to re-establish
//! the connection.
//!
//! Future Work
//! -----------
//! We can make a few other improvements to the health checker. These are:
//! - Make the policy for interpreting ping failures pluggable
//! - Use successful inbound pings as a sign of remote note being healthy
//! - Ping a peer only in periods of no application-level communication with the peer
use crate::{
    constants::NETWORK_CHANNEL_SIZE,
    counters,
    error::NetworkError,
    logging::NetworkSchema,
    peer_manager::{ConnectionRequestSender, PeerManagerRequestSender},
    protocols::{
        network::{Event, NetworkEvents, NetworkSender, NewNetworkSender},
        rpc::error::RpcError,
    },
    ProtocolId,
};
use bytes::Bytes;
use channel::message_queues::QueueStyle;
use futures::{
    channel::oneshot,
    stream::{FusedStream, FuturesUnordered, Stream, StreamExt},
};
use libra_config::network_id::NetworkContext;
use libra_logger::prelude::*;
use libra_metrics::IntCounterVec;
use libra_types::PeerId;
use rand::{rngs::SmallRng, seq::SliceRandom, Rng, SeedableRng};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc, time::Duration};

pub mod builder;
#[cfg(test)]
mod test;

/// The interface from Network to HealthChecker layer.
///
/// `HealthCheckerNetworkEvents` is a `Stream` of `PeerManagerNotification` where the
/// raw `Bytes` rpc messages are deserialized into
/// `HealthCheckerMsg` types. `HealthCheckerNetworkEvents` is a thin wrapper
/// around an `channel::Receiver<PeerManagerNotification>`.
pub type HealthCheckerNetworkEvents = NetworkEvents<HealthCheckerMsg>;

/// The interface from HealthChecker to Networking layer.
///
/// This is a thin wrapper around a `NetworkSender<HealthCheckerMsg>`, so it is
/// easy to clone and send off to a separate task. For example, the rpc requests
/// return Futures that encapsulate the whole flow, from sending the request to
/// remote, to finally receiving the response and deserializing. It therefore
/// makes the most sense to make the rpc call on a separate async task, which
/// requires the `HealthCheckerNetworkSender` to be `Clone` and `Send`.
#[derive(Clone)]
pub struct HealthCheckerNetworkSender {
    inner: NetworkSender<HealthCheckerMsg>,
}

/// Configuration for the network endpoints to support HealthChecker.
pub fn network_endpoint_config() -> (
    Vec<ProtocolId>,
    Vec<ProtocolId>,
    QueueStyle,
    usize,
    Option<&'static IntCounterVec>,
) {
    (
        vec![ProtocolId::HealthCheckerRpc],
        vec![],
        QueueStyle::LIFO,
        NETWORK_CHANNEL_SIZE,
        Some(&counters::PENDING_HEALTH_CHECKER_NETWORK_EVENTS),
    )
}

impl NewNetworkSender for HealthCheckerNetworkSender {
    fn new(
        peer_mgr_reqs_tx: PeerManagerRequestSender,
        connection_reqs_tx: ConnectionRequestSender,
    ) -> Self {
        Self {
            inner: NetworkSender::new(peer_mgr_reqs_tx, connection_reqs_tx),
        }
    }
}

impl HealthCheckerNetworkSender {
    /// Send a HealthChecker Ping RPC request to remote peer `recipient`. Returns
    /// the remote peer's future `Pong` reply.
    ///
    /// The rpc request can be canceled at any point by dropping the returned
    /// future.
    pub async fn send_rpc(
        &mut self,
        recipient: PeerId,
        req_msg: HealthCheckerMsg,
        timeout: Duration,
    ) -> Result<HealthCheckerMsg, RpcError> {
        let protocol = ProtocolId::HealthCheckerRpc;
        self.inner
            .send_rpc(recipient, protocol, req_msg, timeout)
            .await
    }

    pub async fn disconnect_peer(&mut self, peer_id: PeerId) -> Result<(), NetworkError> {
        self.inner.disconnect_peer(peer_id).await
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum HealthCheckerMsg {
    Ping(Ping),
    Pong(Pong),
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Ping(u32);

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Pong(u32);

/// The actor performing health checks by running the Ping protocol
pub struct HealthChecker<TTicker> {
    network_context: Arc<NetworkContext>,
    /// Ticker to trigger ping to a random peer. In production, the ticker is likely to be
    /// fixed duration interval timer.
    ticker: TTicker,
    /// Channel to send requests to Network layer.
    network_tx: HealthCheckerNetworkSender,
    /// Channel to receive notifications from Network layer about new/lost connections.
    network_rx: HealthCheckerNetworkEvents,
    /// Map from connected peer to last round of successful ping, and number of failures since
    /// then.
    connected: HashMap<PeerId, (u64, u64)>,
    /// Random-number generator.
    rng: SmallRng,
    /// Ping timmeout duration.
    ping_timeout: Duration,
    /// Number of successive ping failures we tolerate before declaring a node as unhealthy and
    /// disconnecting from it. In the future, this can be replaced with a more general failure
    /// detection policy.
    ping_failures_tolerated: u64,
    /// Counter incremented in each round of health checks
    round: u64,
}

impl<TTicker> HealthChecker<TTicker>
where
    TTicker: Stream + FusedStream + Unpin,
{
    /// Create new instance of the [`HealthChecker`] actor.
    pub fn new(
        network_context: Arc<NetworkContext>,
        ticker: TTicker,
        network_tx: HealthCheckerNetworkSender,
        network_rx: HealthCheckerNetworkEvents,
        ping_timeout: Duration,
        ping_failures_tolerated: u64,
    ) -> Self {
        HealthChecker {
            network_context,
            ticker,
            network_tx,
            network_rx,
            connected: HashMap::new(),
            rng: SmallRng::from_entropy(),
            ping_timeout,
            ping_failures_tolerated,
            round: 0,
        }
    }

    pub async fn start(mut self) {
        let mut tick_handlers = FuturesUnordered::new();
        info!(
            NetworkSchema::new(&self.network_context),
            "{} Health checker actor started", self.network_context
        );
        loop {
            futures::select! {
                event = self.network_rx.select_next_some() => {
                    match event {
                        Event::NewPeer(peer_id, _origin) => {
                            self.connected.insert(peer_id, (self.round, 0));
                        },
                        Event::LostPeer(peer_id, _origin) => {
                            self.connected.remove(&peer_id);
                        },
                        Event::RpcRequest(peer_id, msg, res_tx) => {
                            match msg {
                            HealthCheckerMsg::Ping(ping) => self.handle_ping_request(peer_id, ping, res_tx),
                            _ => {
                                warn!(
                                    SecurityEvent::InvalidHealthCheckerMsg,
                                    NetworkSchema::new(&self.network_context)
                                        .remote_peer(&peer_id),
                                    rpc_message = msg,
                                    "{} Unexpected RPC message from {}",
                                    self.network_context,
                                    peer_id
                                );
                            },
                            };
                        }
                        Event::Message(peer_id, msg) => {
                            error!(
                                SecurityEvent::InvalidNetworkEventHC,
                                NetworkSchema::new(&self.network_context)
                                    .remote_peer(&peer_id),
                                "{} Unexpected direct send from {} msg {:?}",
                                self.network_context,
                                peer_id,
                                msg,
                            );
                            debug_assert!(false, "Unexpected network event");
                        },
                    }
                }
                _ = self.ticker.select_next_some() => {
                    self.round += 1;
                    match self.sample_random_peer() {
                        Some(peer_id) => {
                            let nonce = self.sample_nonce();
                            debug!(
                                NetworkSchema::new(&self.network_context),
                                round = self.round,
                                "{} Will ping: {} for round: {} nonce: {}",
                                self.network_context, peer_id.short_str(),
                                self.round,
                                nonce
                            );

                            tick_handlers.push(
                                Self::ping_peer(
                                    self.network_context.clone(),
                                    self.network_tx.clone(),
                                    peer_id,
                                    self.round,
                                    nonce,
                                    self.ping_timeout.clone()));
                        }
                        None => {
                            debug!(
                                NetworkSchema::new(&self.network_context),
                                round = self.round,
                                "{} No connected peer to ping round: {}",
                                self.network_context,
                                self.round
                            );
                        }
                    }
                }
                res = tick_handlers.select_next_some() => {
                    let (peer_id, round, nonce, ping_result) = res;
                    self.handle_ping_response(peer_id, round, nonce, ping_result).await;
                }
                complete => {
                    break;
                }
            }
        }
        warn!(
            NetworkSchema::new(&self.network_context),
            "{} Health checker actor terminated", self.network_context
        );
    }

    fn handle_ping_request(
        &mut self,
        peer_id: PeerId,
        ping: Ping,
        res_tx: oneshot::Sender<Result<Bytes, RpcError>>,
    ) {
        let message = match lcs::to_bytes(&HealthCheckerMsg::Pong(Pong(ping.0))) {
            Ok(msg) => msg,
            Err(e) => {
                warn!(
                    NetworkSchema::new(&self.network_context),
                    error = ?e,
                    "{} Unable to serialize pong response: {}", self.network_context, e
                );
                return;
            }
        };
        debug!(
            NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
            "{} Sending Pong response to peer: {} with nonce: {}",
            self.network_context,
            peer_id.short_str(),
            ping.0,
        );
        let _ = res_tx.send(Ok(message.into()));
    }

    async fn handle_ping_response(
        &mut self,
        peer_id: PeerId,
        round: u64,
        req_nonce: u32,
        ping_result: Result<Pong, RpcError>,
    ) {
        match ping_result {
            Ok(pong) => {
                if pong.0 == req_nonce {
                    debug!(
                        NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                        rount = round,
                        "{} Ping successful for peer: {} round: {}",
                        self.network_context,
                        peer_id.short_str(),
                        round
                    );
                    // Update last successful ping to current round.
                    self.connected
                        .entry(peer_id)
                        .and_modify(|(ref mut r, ref mut count)| {
                            if round > *r {
                                *r = round;
                                *count = 0;
                            }
                        });
                } else {
                    warn!(
                        SecurityEvent::InvalidHealthCheckerMsg,
                        NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                        "{} Pong nonce doesn't match Ping nonce. Round: {}, Pong: {}, Ping: {}",
                        self.network_context,
                        round,
                        pong.0,
                        req_nonce
                    );
                    debug_assert!(false, "Pong nonce doesn't match our challenge Ping nonce");
                }
            }
            Err(err) => {
                warn!(
                    NetworkSchema::new(&self.network_context)
                        .remote_peer(&peer_id),
                    error = ?err,
                    round = round,
                    "{} Ping failed for peer: {} round: {} with error: {:?}",
                    self.network_context,
                    peer_id.short_str(),
                    round,
                    err
                );
                match self.connected.get_mut(&peer_id) {
                    None => {
                        // If we are no longer connected to the peer, we ignore ping
                        // failure.
                    }
                    Some((ref mut prev, ref mut failures)) => {
                        // If this is the result of an older ping, we ignore it.
                        if *prev > round {
                            return;
                        }
                        // Increment num of failures. If the ping failures are now more than
                        // `self.ping_failures_tolerated`, we disconnect from the node.
                        // The HealthChecker only performs the disconnect. It relies on
                        // ConnectivityManager or the remote peer to re-establish the connection.
                        *failures += 1;
                        if *failures > self.ping_failures_tolerated {
                            info!(
                                NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                                "{} Disconnecting from peer: {}",
                                self.network_context,
                                peer_id.short_str()
                            );
                            if let Err(err) = self.network_tx.disconnect_peer(peer_id).await {
                                warn!(
                                    NetworkSchema::new(&self.network_context)
                                        .remote_peer(&peer_id),
                                    error = ?err,
                                    "{} Failed to disconnect from peer: {} with error: {:?}",
                                    self.network_context,
                                    peer_id.short_str(),
                                    err
                                );
                            }
                        }
                    }
                }
            }
        }
    }

    async fn ping_peer(
        network_context: Arc<NetworkContext>,
        mut network_tx: HealthCheckerNetworkSender,
        peer_id: PeerId,
        round: u64,
        nonce: u32,
        ping_timeout: Duration,
    ) -> (PeerId, u64, u32, Result<Pong, RpcError>) {
        debug!(
            NetworkSchema::new(&network_context).remote_peer(&peer_id),
            round = round,
            "{} Sending Ping request to peer: {} for round: {} nonce: {}",
            network_context,
            peer_id.short_str(),
            round,
            nonce
        );
        let res_pong_msg = network_tx
            .send_rpc(peer_id, HealthCheckerMsg::Ping(Ping(nonce)), ping_timeout)
            .await
            .and_then(|msg| match msg {
                HealthCheckerMsg::Pong(res) => Ok(res),
                _ => Err(RpcError::InvalidRpcResponse),
            });
        (peer_id, round, nonce, res_pong_msg)
    }

    fn sample_random_peer(&mut self) -> Option<PeerId> {
        let peers: Vec<_> = self.connected.keys().cloned().collect();
        peers.choose(&mut self.rng).cloned()
    }

    fn sample_nonce(&mut self) -> u32 {
        self.rng.gen::<u32>()
    }
}
<commit_msg>[network] Decrease Healthchecker logs<commit_after>// Copyright (c) The Libra Core Contributors
// SPDX-License-Identifier: Apache-2.0

//! Protocol used to ensure peer liveness
//!
//! The HealthChecker is responsible for ensuring liveness of all peers of a node.
//! It does so by periodically selecting a random connected peer and sending a Ping probe. A
//! healthy peer is expected to respond with a corresponding Pong message.
//!
//! If a certain number of successive liveness probes for a peer fail, the HealthChecker initiates a
//! disconnect from the peer. It relies on ConnectivityManager or the remote peer to re-establish
//! the connection.
//!
//! Future Work
//! -----------
//! We can make a few other improvements to the health checker. These are:
//! - Make the policy for interpreting ping failures pluggable
//! - Use successful inbound pings as a sign of remote note being healthy
//! - Ping a peer only in periods of no application-level communication with the peer
use crate::{
    constants::NETWORK_CHANNEL_SIZE,
    counters,
    error::NetworkError,
    logging::NetworkSchema,
    peer_manager::{ConnectionRequestSender, PeerManagerRequestSender},
    protocols::{
        network::{Event, NetworkEvents, NetworkSender, NewNetworkSender},
        rpc::error::RpcError,
    },
    ProtocolId,
};
use bytes::Bytes;
use channel::message_queues::QueueStyle;
use futures::{
    channel::oneshot,
    stream::{FusedStream, FuturesUnordered, Stream, StreamExt},
};
use libra_config::network_id::NetworkContext;
use libra_logger::prelude::*;
use libra_metrics::IntCounterVec;
use libra_types::PeerId;
use rand::{rngs::SmallRng, seq::SliceRandom, Rng, SeedableRng};
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, sync::Arc, time::Duration};

pub mod builder;
#[cfg(test)]
mod test;

/// The interface from Network to HealthChecker layer.
///
/// `HealthCheckerNetworkEvents` is a `Stream` of `PeerManagerNotification` where the
/// raw `Bytes` rpc messages are deserialized into
/// `HealthCheckerMsg` types. `HealthCheckerNetworkEvents` is a thin wrapper
/// around an `channel::Receiver<PeerManagerNotification>`.
pub type HealthCheckerNetworkEvents = NetworkEvents<HealthCheckerMsg>;

/// The interface from HealthChecker to Networking layer.
///
/// This is a thin wrapper around a `NetworkSender<HealthCheckerMsg>`, so it is
/// easy to clone and send off to a separate task. For example, the rpc requests
/// return Futures that encapsulate the whole flow, from sending the request to
/// remote, to finally receiving the response and deserializing. It therefore
/// makes the most sense to make the rpc call on a separate async task, which
/// requires the `HealthCheckerNetworkSender` to be `Clone` and `Send`.
#[derive(Clone)]
pub struct HealthCheckerNetworkSender {
    inner: NetworkSender<HealthCheckerMsg>,
}

/// Configuration for the network endpoints to support HealthChecker.
pub fn network_endpoint_config() -> (
    Vec<ProtocolId>,
    Vec<ProtocolId>,
    QueueStyle,
    usize,
    Option<&'static IntCounterVec>,
) {
    (
        vec![ProtocolId::HealthCheckerRpc],
        vec![],
        QueueStyle::LIFO,
        NETWORK_CHANNEL_SIZE,
        Some(&counters::PENDING_HEALTH_CHECKER_NETWORK_EVENTS),
    )
}

impl NewNetworkSender for HealthCheckerNetworkSender {
    fn new(
        peer_mgr_reqs_tx: PeerManagerRequestSender,
        connection_reqs_tx: ConnectionRequestSender,
    ) -> Self {
        Self {
            inner: NetworkSender::new(peer_mgr_reqs_tx, connection_reqs_tx),
        }
    }
}

impl HealthCheckerNetworkSender {
    /// Send a HealthChecker Ping RPC request to remote peer `recipient`. Returns
    /// the remote peer's future `Pong` reply.
    ///
    /// The rpc request can be canceled at any point by dropping the returned
    /// future.
    pub async fn send_rpc(
        &mut self,
        recipient: PeerId,
        req_msg: HealthCheckerMsg,
        timeout: Duration,
    ) -> Result<HealthCheckerMsg, RpcError> {
        let protocol = ProtocolId::HealthCheckerRpc;
        self.inner
            .send_rpc(recipient, protocol, req_msg, timeout)
            .await
    }

    pub async fn disconnect_peer(&mut self, peer_id: PeerId) -> Result<(), NetworkError> {
        self.inner.disconnect_peer(peer_id).await
    }
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum HealthCheckerMsg {
    Ping(Ping),
    Pong(Pong),
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Ping(u32);

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Pong(u32);

/// The actor performing health checks by running the Ping protocol
pub struct HealthChecker<TTicker> {
    network_context: Arc<NetworkContext>,
    /// Ticker to trigger ping to a random peer. In production, the ticker is likely to be
    /// fixed duration interval timer.
    ticker: TTicker,
    /// Channel to send requests to Network layer.
    network_tx: HealthCheckerNetworkSender,
    /// Channel to receive notifications from Network layer about new/lost connections.
    network_rx: HealthCheckerNetworkEvents,
    /// Map from connected peer to last round of successful ping, and number of failures since
    /// then.
    connected: HashMap<PeerId, (u64, u64)>,
    /// Random-number generator.
    rng: SmallRng,
    /// Ping timmeout duration.
    ping_timeout: Duration,
    /// Number of successive ping failures we tolerate before declaring a node as unhealthy and
    /// disconnecting from it. In the future, this can be replaced with a more general failure
    /// detection policy.
    ping_failures_tolerated: u64,
    /// Counter incremented in each round of health checks
    round: u64,
}

impl<TTicker> HealthChecker<TTicker>
where
    TTicker: Stream + FusedStream + Unpin,
{
    /// Create new instance of the [`HealthChecker`] actor.
    pub fn new(
        network_context: Arc<NetworkContext>,
        ticker: TTicker,
        network_tx: HealthCheckerNetworkSender,
        network_rx: HealthCheckerNetworkEvents,
        ping_timeout: Duration,
        ping_failures_tolerated: u64,
    ) -> Self {
        HealthChecker {
            network_context,
            ticker,
            network_tx,
            network_rx,
            connected: HashMap::new(),
            rng: SmallRng::from_entropy(),
            ping_timeout,
            ping_failures_tolerated,
            round: 0,
        }
    }

    pub async fn start(mut self) {
        let mut tick_handlers = FuturesUnordered::new();
        info!(
            NetworkSchema::new(&self.network_context),
            "{} Health checker actor started", self.network_context
        );
        loop {
            futures::select! {
                event = self.network_rx.select_next_some() => {
                    match event {
                        Event::NewPeer(peer_id, _origin) => {
                            self.connected.insert(peer_id, (self.round, 0));
                        },
                        Event::LostPeer(peer_id, _origin) => {
                            self.connected.remove(&peer_id);
                        },
                        Event::RpcRequest(peer_id, msg, res_tx) => {
                            match msg {
                            HealthCheckerMsg::Ping(ping) => self.handle_ping_request(peer_id, ping, res_tx),
                            _ => {
                                warn!(
                                    SecurityEvent::InvalidHealthCheckerMsg,
                                    NetworkSchema::new(&self.network_context)
                                        .remote_peer(&peer_id),
                                    rpc_message = msg,
                                    "{} Unexpected RPC message from {}",
                                    self.network_context,
                                    peer_id
                                );
                            },
                            };
                        }
                        Event::Message(peer_id, msg) => {
                            error!(
                                SecurityEvent::InvalidNetworkEventHC,
                                NetworkSchema::new(&self.network_context)
                                    .remote_peer(&peer_id),
                                "{} Unexpected direct send from {} msg {:?}",
                                self.network_context,
                                peer_id,
                                msg,
                            );
                            debug_assert!(false, "Unexpected network event");
                        },
                    }
                }
                _ = self.ticker.select_next_some() => {
                    self.round += 1;
                    match self.sample_random_peer() {
                        Some(peer_id) => {
                            let nonce = self.sample_nonce();
                            trace!(
                                NetworkSchema::new(&self.network_context),
                                round = self.round,
                                "{} Will ping: {} for round: {} nonce: {}",
                                self.network_context, peer_id.short_str(),
                                self.round,
                                nonce
                            );

                            tick_handlers.push(
                                Self::ping_peer(
                                    self.network_context.clone(),
                                    self.network_tx.clone(),
                                    peer_id,
                                    self.round,
                                    nonce,
                                    self.ping_timeout.clone()));
                        }
                        None => {
                            trace!(
                                NetworkSchema::new(&self.network_context),
                                round = self.round,
                                "{} No connected peer to ping round: {}",
                                self.network_context,
                                self.round
                            );
                        }
                    }
                }
                res = tick_handlers.select_next_some() => {
                    let (peer_id, round, nonce, ping_result) = res;
                    self.handle_ping_response(peer_id, round, nonce, ping_result).await;
                }
                complete => {
                    break;
                }
            }
        }
        warn!(
            NetworkSchema::new(&self.network_context),
            "{} Health checker actor terminated", self.network_context
        );
    }

    fn handle_ping_request(
        &mut self,
        peer_id: PeerId,
        ping: Ping,
        res_tx: oneshot::Sender<Result<Bytes, RpcError>>,
    ) {
        let message = match lcs::to_bytes(&HealthCheckerMsg::Pong(Pong(ping.0))) {
            Ok(msg) => msg,
            Err(e) => {
                warn!(
                    NetworkSchema::new(&self.network_context),
                    error = ?e,
                    "{} Unable to serialize pong response: {}", self.network_context, e
                );
                return;
            }
        };
        trace!(
            NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
            "{} Sending Pong response to peer: {} with nonce: {}",
            self.network_context,
            peer_id.short_str(),
            ping.0,
        );
        let _ = res_tx.send(Ok(message.into()));
    }

    async fn handle_ping_response(
        &mut self,
        peer_id: PeerId,
        round: u64,
        req_nonce: u32,
        ping_result: Result<Pong, RpcError>,
    ) {
        match ping_result {
            Ok(pong) => {
                if pong.0 == req_nonce {
                    trace!(
                        NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                        rount = round,
                        "{} Ping successful for peer: {} round: {}",
                        self.network_context,
                        peer_id.short_str(),
                        round
                    );
                    // Update last successful ping to current round.
                    self.connected
                        .entry(peer_id)
                        .and_modify(|(ref mut r, ref mut count)| {
                            if round > *r {
                                *r = round;
                                *count = 0;
                            }
                        });
                } else {
                    warn!(
                        SecurityEvent::InvalidHealthCheckerMsg,
                        NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                        "{} Pong nonce doesn't match Ping nonce. Round: {}, Pong: {}, Ping: {}",
                        self.network_context,
                        round,
                        pong.0,
                        req_nonce
                    );
                    debug_assert!(false, "Pong nonce doesn't match our challenge Ping nonce");
                }
            }
            Err(err) => {
                warn!(
                    NetworkSchema::new(&self.network_context)
                        .remote_peer(&peer_id),
                    error = ?err,
                    round = round,
                    "{} Ping failed for peer: {} round: {} with error: {:?}",
                    self.network_context,
                    peer_id.short_str(),
                    round,
                    err
                );
                match self.connected.get_mut(&peer_id) {
                    None => {
                        // If we are no longer connected to the peer, we ignore ping
                        // failure.
                    }
                    Some((ref mut prev, ref mut failures)) => {
                        // If this is the result of an older ping, we ignore it.
                        if *prev > round {
                            return;
                        }
                        // Increment num of failures. If the ping failures are now more than
                        // `self.ping_failures_tolerated`, we disconnect from the node.
                        // The HealthChecker only performs the disconnect. It relies on
                        // ConnectivityManager or the remote peer to re-establish the connection.
                        *failures += 1;
                        if *failures > self.ping_failures_tolerated {
                            info!(
                                NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                                "{} Disconnecting from peer: {}",
                                self.network_context,
                                peer_id.short_str()
                            );
                            if let Err(err) = self.network_tx.disconnect_peer(peer_id).await {
                                warn!(
                                    NetworkSchema::new(&self.network_context)
                                        .remote_peer(&peer_id),
                                    error = ?err,
                                    "{} Failed to disconnect from peer: {} with error: {:?}",
                                    self.network_context,
                                    peer_id.short_str(),
                                    err
                                );
                            }
                        }
                    }
                }
            }
        }
    }

    async fn ping_peer(
        network_context: Arc<NetworkContext>,
        mut network_tx: HealthCheckerNetworkSender,
        peer_id: PeerId,
        round: u64,
        nonce: u32,
        ping_timeout: Duration,
    ) -> (PeerId, u64, u32, Result<Pong, RpcError>) {
        trace!(
            NetworkSchema::new(&network_context).remote_peer(&peer_id),
            round = round,
            "{} Sending Ping request to peer: {} for round: {} nonce: {}",
            network_context,
            peer_id.short_str(),
            round,
            nonce
        );
        let res_pong_msg = network_tx
            .send_rpc(peer_id, HealthCheckerMsg::Ping(Ping(nonce)), ping_timeout)
            .await
            .and_then(|msg| match msg {
                HealthCheckerMsg::Pong(res) => Ok(res),
                _ => Err(RpcError::InvalidRpcResponse),
            });
        (peer_id, round, nonce, res_pong_msg)
    }

    fn sample_random_peer(&mut self) -> Option<PeerId> {
        let peers: Vec<_> = self.connected.keys().cloned().collect();
        peers.choose(&mut self.rng).cloned()
    }

    fn sample_nonce(&mut self) -> u32 {
        self.rng.gen::<u32>()
    }
}
<|endoftext|>