Skip to content

Commit

Permalink
An in-memory RNG that shares its file descriptor.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alan Jeffrey committed Jan 5, 2017
1 parent 143dfc8 commit 7ace30f
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 23 deletions.
16 changes: 12 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion components/bluetooth/Cargo.toml
Expand Up @@ -14,7 +14,7 @@ bitflags = "0.7"
bluetooth_traits = {path = "../bluetooth_traits"}
device = {git = "https://github.com/servo/devices", features = ["bluetooth-test"]}
ipc-channel = "0.5"
rand = "0.3"
servo_rand = {path = "../rand"}
uuid = {version = "0.3.1", features = ["v4"]}

[target.'cfg(target_os = "linux")'.dependencies]
Expand Down
6 changes: 3 additions & 3 deletions components/bluetooth/lib.rs
Expand Up @@ -7,7 +7,7 @@ extern crate bitflags;
extern crate bluetooth_traits;
extern crate device;
extern crate ipc_channel;
extern crate rand;
extern crate servo_rand;
#[cfg(target_os = "linux")]
extern crate tinyfiledialogs;
extern crate uuid;
Expand All @@ -22,7 +22,7 @@ use bluetooth_traits::scanfilter::{BluetoothScanfilter, BluetoothScanfilterSeque
use device::bluetooth::{BluetoothAdapter, BluetoothDevice, BluetoothGATTCharacteristic};
use device::bluetooth::{BluetoothGATTDescriptor, BluetoothGATTService};
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use rand::Rng;
use servo_rand::Rng;
use std::borrow::ToOwned;
use std::collections::{HashMap, HashSet};
use std::string::String;
Expand Down Expand Up @@ -397,7 +397,7 @@ impl BluetoothManager {

fn generate_device_id(&mut self) -> String {
let mut device_id;
let mut rng = rand::thread_rng();
let mut rng = servo_rand::thread_rng();
loop {
device_id = rng.gen::<u32>().to_string();
if !self.cached_devices.contains_key(&device_id) {
Expand Down
2 changes: 1 addition & 1 deletion components/constellation/Cargo.toml
Expand Up @@ -28,12 +28,12 @@ net_traits = {path = "../net_traits"}
offscreen_gl_context = "0.5.0"
plugins = {path = "../plugins"}
profile_traits = {path = "../profile_traits"}
rand = "0.3"
script_traits = {path = "../script_traits"}
serde = "0.8"
serde_derive = "0.8"
style_traits = {path = "../style_traits"}
servo_config = {path = "../config", features = ["servo"]}
servo_rand = {path = "../rand"}
servo_remutex = {path = "../remutex"}
servo_url = {path = "../url", features = ["servo"]}

Expand Down
6 changes: 3 additions & 3 deletions components/constellation/constellation.rs
Expand Up @@ -93,7 +93,6 @@ use offscreen_gl_context::{GLContextAttributes, GLLimits};
use pipeline::{InitialPipelineState, Pipeline};
use profile_traits::mem;
use profile_traits::time;
use rand::{Rng, SeedableRng, StdRng, random};
use script_traits::{AnimationState, AnimationTickType, CompositorEvent};
use script_traits::{ConstellationControlMsg, ConstellationMsg as FromCompositorMsg};
use script_traits::{DocumentState, LayoutControlMsg, LoadData};
Expand All @@ -104,6 +103,7 @@ use script_traits::{MozBrowserErrorType, MozBrowserEvent, WebDriverCommandMsg, W
use script_traits::{SWManagerMsg, ScopeThings, WindowSizeType};
use servo_config::opts;
use servo_config::prefs::PREFS;
use servo_rand::{Rng, SeedableRng, ServoRng, random};
use servo_remutex::ReentrantMutex;
use servo_url::ServoUrl;
use std::borrow::ToOwned;
Expand Down Expand Up @@ -276,7 +276,7 @@ pub struct Constellation<Message, LTF, STF> {

/// The random number generator and probability for closing pipelines.
/// This is for testing the hardening of the constellation.
random_pipeline_closure: Option<(StdRng, f32)>,
random_pipeline_closure: Option<(ServoRng, f32)>,

/// Phantom data that keeps the Rust type system happy.
phantom: PhantomData<(Message, LTF, STF)>,
Expand Down Expand Up @@ -530,7 +530,7 @@ impl<Message, LTF, STF> Constellation<Message, LTF, STF>
handled_warnings: VecDeque::new(),
random_pipeline_closure: opts::get().random_pipeline_closure_probability.map(|prob| {
let seed = opts::get().random_pipeline_closure_seed.unwrap_or_else(random);
let rng = StdRng::from_seed(&[seed]);
let rng = ServoRng::from_seed(&[seed]);
warn!("Randomly closing pipelines.");
info!("Using seed {} for random pipeline closure.", seed);
(rng, prob)
Expand Down
2 changes: 1 addition & 1 deletion components/constellation/lib.rs
Expand Up @@ -32,12 +32,12 @@ extern crate net_traits;
extern crate offscreen_gl_context;
#[macro_use]
extern crate profile_traits;
extern crate rand;
extern crate script_traits;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate servo_config;
extern crate servo_rand;
extern crate servo_remutex;
extern crate servo_url;
extern crate style_traits;
Expand Down
15 changes: 15 additions & 0 deletions components/rand/Cargo.toml
@@ -0,0 +1,15 @@
[package]
name = "servo_rand"
version = "0.0.1"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
publish = false

[lib]
name = "servo_rand"
path = "lib.rs"

[dependencies]
lazy_static = "0.2"
log = "0.3"
rand = "0.3"
158 changes: 158 additions & 0 deletions components/rand/lib.rs
@@ -0,0 +1,158 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/// A random number generator which shares one instance of an `OsRng`.
///
/// A problem with `OsRng`, which is inherited by `StdRng` and so
/// `ThreadRng`, is that it reads from `/dev/random`, and so consumes
/// a file descriptor. For multi-threaded applications like Servo,
/// it is easy to exhaust the supply of file descriptors this way.
///
/// This crate fixes that, by only using one `OsRng`, which is just
/// used to seed and re-seed an `ServoRng`.

#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate rand;

pub use rand::{Rand, Rng, SeedableRng};
#[cfg(target_pointer_width = "64")]
use rand::isaac::Isaac64Rng as IsaacWordRng;
#[cfg(target_pointer_width = "32")]
use rand::isaac::IsaacRng as IsaacWordRng;
use rand::os::OsRng;
use rand::reseeding::{ReseedingRng, Reseeder};
use std::cell::RefCell;
use std::mem;
use std::rc::Rc;
use std::sync::Mutex;
use std::u64;

// Slightly annoying having to cast between sizes.

#[cfg(target_pointer_width = "64")]
fn as_isaac_seed(seed: &[usize]) -> &[u64] {
unsafe { mem::transmute(seed) }
}

#[cfg(target_pointer_width = "32")]
fn as_isaac_seed(seed: &[usize]) -> &[u32] {
unsafe { mem::transmute(seed) }
}

// The shared RNG which may hold on to a file descriptor
lazy_static! {
static ref OS_RNG: Mutex<OsRng> = match OsRng::new() {
Ok(r) => Mutex::new(r),
Err(e) => panic!("Failed to seed OsRng: {}", e),
};
}

// Generate 32K of data between reseedings
const RESEED_THRESHOLD: u64 = 32_768;

// An in-memory RNG that only uses the shared file descriptor for seeding and reseeding.
pub struct ServoRng {
rng: ReseedingRng<IsaacWordRng, ServoReseeder>,
}

impl Rng for ServoRng {
#[inline]
fn next_u32(&mut self) -> u32 {
self.rng.next_u32()
}

#[inline]
fn next_u64(&mut self) -> u64 {
self.rng.next_u64()
}
}

impl<'a> SeedableRng<&'a [usize]> for ServoRng {
/// Create a manually-reseeding instane of `ServoRng`.
///
/// Note that this RNG does not reseed itself, so care is needed to reseed the RNG
/// is required to be cryptographically sound.
fn from_seed(seed: &[usize]) -> ServoRng {
debug!("Creating new manually-reseeded ServoRng.");
let isaac_rng = IsaacWordRng::from_seed(as_isaac_seed(seed));
let reseeding_rng = ReseedingRng::new(isaac_rng, u64::MAX, ServoReseeder);
ServoRng { rng: reseeding_rng }
}
/// Reseed the RNG.
fn reseed(&mut self, seed: &'a [usize]) {
debug!("Manually reseeding ServoRng.");
self.rng.reseed((ServoReseeder, as_isaac_seed(seed)))
}
}

impl ServoRng {
/// Create an auto-reseeding instance of `ServoRng`.
///
/// This uses the shared `OsRng`, so avoids consuming
/// a file descriptor.
pub fn new() -> ServoRng {
debug!("Creating new ServoRng.");
let mut os_rng = OS_RNG.lock().expect("Poisoned lock.");
let isaac_rng = IsaacWordRng::rand(&mut *os_rng);
let reseeding_rng = ReseedingRng::new(isaac_rng, RESEED_THRESHOLD, ServoReseeder);
ServoRng { rng: reseeding_rng }
}
}

// The reseeder for the in-memory RNG.
struct ServoReseeder;

impl Reseeder<IsaacWordRng> for ServoReseeder {
fn reseed(&mut self, rng: &mut IsaacWordRng) {
debug!("Reseeding ServoRng.");
let mut os_rng = OS_RNG.lock().expect("Poisoned lock.");
*rng = IsaacWordRng::rand(&mut *os_rng);
}
}

impl Default for ServoReseeder {
fn default() -> ServoReseeder {
ServoReseeder
}
}

// A thread-local RNG, designed as a drop-in replacement for rand::ThreadRng.
#[derive(Clone)]
pub struct ServoThreadRng {
rng: Rc<RefCell<ServoRng>>,
}

// A thread-local RNG, designed as a drop-in replacement for rand::thread_rng.
pub fn thread_rng() -> ServoThreadRng {
SERVO_THREAD_RNG.with(|t| t.clone())
}

thread_local! {
static SERVO_THREAD_RNG: ServoThreadRng = ServoThreadRng { rng: Rc::new(RefCell::new(ServoRng::new())) };
}

impl Rng for ServoThreadRng {
fn next_u32(&mut self) -> u32 {
self.rng.borrow_mut().next_u32()
}

fn next_u64(&mut self) -> u64 {
self.rng.borrow_mut().next_u64()
}

#[inline]
fn fill_bytes(&mut self, bytes: &mut [u8]) {
self.rng.borrow_mut().fill_bytes(bytes)
}
}

// Generates a random value using the thread-local random number generator.
// A drop-in replacement for rand::random.
#[inline]
pub fn random<T: Rand>() -> T {
thread_rng().gen()
}
2 changes: 1 addition & 1 deletion components/script/Cargo.toml
Expand Up @@ -60,7 +60,6 @@ phf = "0.7.18"
phf_macros = "0.7.18"
plugins = {path = "../plugins"}
profile_traits = {path = "../profile_traits"}
rand = "0.3"
range = {path = "../range"}
ref_filter_map = "1.0.1"
ref_slice = "1.0"
Expand All @@ -73,6 +72,7 @@ serde = "0.8"
servo_atoms = {path = "../atoms"}
servo_config = {path = "../config", features = ["servo"] }
servo_geometry = {path = "../geometry" }
servo_rand = {path = "../rand"}
servo_url = {path = "../url", features = ["servo"] }
smallvec = "0.1"
style = {path = "../style"}
Expand Down
8 changes: 4 additions & 4 deletions components/script/dom/crypto.rs
Expand Up @@ -12,23 +12,23 @@ use dom::bindings::reflector::{Reflector, reflect_dom_object};
use dom::globalscope::GlobalScope;
use js::jsapi::{JSContext, JSObject};
use js::jsapi::{JS_GetArrayBufferViewType, Type};
use rand::{OsRng, Rng};
use servo_rand::{ServoRng, Rng};

unsafe_no_jsmanaged_fields!(OsRng);
unsafe_no_jsmanaged_fields!(ServoRng);

// https://developer.mozilla.org/en-US/docs/Web/API/Crypto
#[dom_struct]
pub struct Crypto {
reflector_: Reflector,
#[ignore_heap_size_of = "Defined in rand"]
rng: DOMRefCell<OsRng>,
rng: DOMRefCell<ServoRng>,
}

impl Crypto {
fn new_inherited() -> Crypto {
Crypto {
reflector_: Reflector::new(),
rng: DOMRefCell::new(OsRng::new().unwrap()),
rng: DOMRefCell::new(ServoRng::new()),
}
}

Expand Down
2 changes: 1 addition & 1 deletion components/script/dom/dedicatedworkerglobalscope.rs
Expand Up @@ -29,10 +29,10 @@ use js::rust::Runtime;
use msg::constellation_msg::FrameId;
use net_traits::{IpcSend, load_whole_resource};
use net_traits::request::{CredentialsMode, Destination, RequestInit, Type as RequestType};
use rand::random;
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, StackRootTLS, get_reports, new_rt_and_cx};
use script_runtime::ScriptThreadEventCategory::WorkerEvent;
use script_traits::{TimerEvent, TimerSource, WorkerGlobalScopeInit, WorkerScriptLoadOrigin};
use servo_rand::random;
use servo_url::ServoUrl;
use std::mem::replace;
use std::sync::{Arc, Mutex};
Expand Down

4 comments on commit 7ace30f

@dhardy
Copy link

@dhardy dhardy commented on 7ace30f Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asajeffrey can you tell me what the motivation for this is?

I'm working on improving rand, and I'd like to know if there is a deficiency. It may be that OsRng can use multiple file descriptors, but if so we should fix it internally.

@jdm
Copy link
Member

@jdm jdm commented on 7ace30f Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The motivation was the frequency with which Service would run out of file descriptors during tests. I don't recall seeing that problem since making this change

@jdm
Copy link
Member

@jdm jdm commented on 7ace30f Jan 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Servo, not Service.

@asajeffrey
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dhardy, sharing the OS RNG among threads, to avoid running out of FDs, but only using the shared RNG for reseeding, to avoid lock contention.

Please sign in to comment.