From 7ace30f9bd1dd835ddb1b12f788773cef855dae7 Mon Sep 17 00:00:00 2001 From: Alan Jeffrey Date: Thu, 5 Jan 2017 13:51:53 +0000 Subject: [PATCH] An in-memory RNG that shares its file descriptor. --- Cargo.lock | 16 +- components/bluetooth/Cargo.toml | 2 +- components/bluetooth/lib.rs | 6 +- components/constellation/Cargo.toml | 2 +- components/constellation/constellation.rs | 6 +- components/constellation/lib.rs | 2 +- components/rand/Cargo.toml | 15 ++ components/rand/lib.rs | 158 ++++++++++++++++++ components/script/Cargo.toml | 2 +- components/script/dom/crypto.rs | 8 +- .../script/dom/dedicatedworkerglobalscope.rs | 2 +- components/script/dom/htmlformelement.rs | 2 +- .../script/dom/serviceworkerglobalscope.rs | 2 +- components/script/lib.rs | 2 +- components/style/Cargo.toml | 1 - 15 files changed, 203 insertions(+), 23 deletions(-) create mode 100644 components/rand/Cargo.toml create mode 100644 components/rand/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 117493224ea..a85e07d10a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -188,7 +188,7 @@ dependencies = [ "bluetooth_traits 0.0.1", "device 0.0.1 (git+https://github.com/servo/devices)", "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_rand 0.0.1", "tinyfiledialogs 2.5.9 (git+https://github.com/jdm/tinyfiledialogs)", "uuid 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -436,11 +436,11 @@ dependencies = [ "offscreen_gl_context 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "plugins 0.0.1", "profile_traits 0.0.1", - "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "script_traits 0.0.1", "serde 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)", "servo_config 0.0.1", + "servo_rand 0.0.1", "servo_remutex 0.0.1", "servo_url 0.0.1", "style_traits 0.0.1", @@ -2285,7 +2285,6 @@ dependencies = [ "phf_macros 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", "plugins 0.0.1", "profile_traits 0.0.1", - "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "range 0.0.1", "ref_filter_map 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "ref_slice 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2298,6 +2297,7 @@ dependencies = [ "servo_atoms 0.0.1", "servo_config 0.0.1", "servo_geometry 0.0.1", + "servo_rand 0.0.1", "servo_url 0.0.1", "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "style 0.0.1", @@ -2606,6 +2606,15 @@ dependencies = [ "heapsize 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "servo_rand" +version = "0.0.1" +dependencies = [ + "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "servo_remutex" version = "0.0.1" @@ -2747,7 +2756,6 @@ dependencies = [ "phf 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", "phf_codegen 0.7.20 (registry+https://github.com/rust-lang/crates.io-index)", "quickersort 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/components/bluetooth/Cargo.toml b/components/bluetooth/Cargo.toml index b94363904e1..ca09b87a10c 100644 --- a/components/bluetooth/Cargo.toml +++ b/components/bluetooth/Cargo.toml @@ -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] diff --git a/components/bluetooth/lib.rs b/components/bluetooth/lib.rs index 8af1fab71da..bcfa1b9c4dc 100644 --- a/components/bluetooth/lib.rs +++ b/components/bluetooth/lib.rs @@ -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; @@ -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; @@ -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::().to_string(); if !self.cached_devices.contains_key(&device_id) { diff --git a/components/constellation/Cargo.toml b/components/constellation/Cargo.toml index d919cd63af3..5299aa9bef1 100644 --- a/components/constellation/Cargo.toml +++ b/components/constellation/Cargo.toml @@ -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"]} diff --git a/components/constellation/constellation.rs b/components/constellation/constellation.rs index f5a5983fa9b..deebeb1d0cf 100644 --- a/components/constellation/constellation.rs +++ b/components/constellation/constellation.rs @@ -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}; @@ -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; @@ -276,7 +276,7 @@ pub struct Constellation { /// 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)>, @@ -530,7 +530,7 @@ impl Constellation 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) diff --git a/components/constellation/lib.rs b/components/constellation/lib.rs index ecfa1c80dd8..b6c768bd641 100644 --- a/components/constellation/lib.rs +++ b/components/constellation/lib.rs @@ -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; diff --git a/components/rand/Cargo.toml b/components/rand/Cargo.toml new file mode 100644 index 00000000000..d7726013158 --- /dev/null +++ b/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" diff --git a/components/rand/lib.rs b/components/rand/lib.rs new file mode 100644 index 00000000000..2771bc1f412 --- /dev/null +++ b/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 = 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, +} + +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 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>, +} + +// 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 { + thread_rng().gen() +} diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 01e4079ccde..3709775a422 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -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" @@ -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"} diff --git a/components/script/dom/crypto.rs b/components/script/dom/crypto.rs index 57b561e8a58..6c7fce6c8d1 100644 --- a/components/script/dom/crypto.rs +++ b/components/script/dom/crypto.rs @@ -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, + rng: DOMRefCell, } impl Crypto { fn new_inherited() -> Crypto { Crypto { reflector_: Reflector::new(), - rng: DOMRefCell::new(OsRng::new().unwrap()), + rng: DOMRefCell::new(ServoRng::new()), } } diff --git a/components/script/dom/dedicatedworkerglobalscope.rs b/components/script/dom/dedicatedworkerglobalscope.rs index 911847aadb8..2d6f5ea14af 100644 --- a/components/script/dom/dedicatedworkerglobalscope.rs +++ b/components/script/dom/dedicatedworkerglobalscope.rs @@ -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}; diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index a0a34b081c8..b403b1389a8 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -44,9 +44,9 @@ use html5ever_atoms::LocalName; use hyper::header::{Charset, ContentDisposition, ContentType, DispositionParam, DispositionType}; use hyper::method::Method; use msg::constellation_msg::PipelineId; -use rand::random; use script_thread::{MainThreadScriptMsg, Runnable}; use script_traits::LoadData; +use servo_rand::random; use std::borrow::ToOwned; use std::cell::Cell; use std::sync::mpsc::Sender; diff --git a/components/script/dom/serviceworkerglobalscope.rs b/components/script/dom/serviceworkerglobalscope.rs index 8dd3da01671..854a0ee0a75 100644 --- a/components/script/dom/serviceworkerglobalscope.rs +++ b/components/script/dom/serviceworkerglobalscope.rs @@ -25,10 +25,10 @@ use js::jsval::UndefinedValue; use js::rust::Runtime; use net_traits::{load_whole_resource, IpcSend, CustomResponseMediator}; use net_traits::request::{CredentialsMode, Destination, RequestInit, Type as RequestType}; -use rand::random; use script_runtime::{CommonScriptMsg, StackRootTLS, get_reports, new_rt_and_cx, ScriptChan}; use script_traits::{TimerEvent, WorkerGlobalScopeInit, ScopeThings, ServiceWorkerMsg, WorkerScriptLoadOrigin}; use servo_config::prefs::PREFS; +use servo_rand::random; use servo_url::ServoUrl; use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel}; use std::thread; diff --git a/components/script/lib.rs b/components/script/lib.rs index d47156b126f..a6637d06dfd 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -73,7 +73,6 @@ extern crate parking_lot; extern crate phf; #[macro_use] extern crate profile_traits; -extern crate rand; extern crate range; extern crate ref_filter_map; extern crate ref_slice; @@ -86,6 +85,7 @@ extern crate serde; #[macro_use] extern crate servo_atoms; #[macro_use] extern crate servo_config; extern crate servo_geometry; +extern crate servo_rand; extern crate servo_url; extern crate smallvec; #[macro_use] diff --git a/components/style/Cargo.toml b/components/style/Cargo.toml index b300f1fd595..dba73f66796 100644 --- a/components/style/Cargo.toml +++ b/components/style/Cargo.toml @@ -44,7 +44,6 @@ owning_ref = "0.2.2" parking_lot = "0.3.3" phf = "0.7.20" quickersort = "2.0.0" -rand = "0.3" rayon = "0.6" rustc-serialize = "0.3" selectors = "0.15"