Silent running operating modes (#1477)

* Command=line options.

* Keep alive for the eth protocol.

* Wire up final pieces.

* No network when dark.

* Passive and dark mode work.

* Ensure all RPCs keep alive.

* Fix tests.

* Fix minor bug.

* Minor whitespace.

* Split out some of the sleep-state.

* Fix help text.
This commit is contained in:
Gav Wood
2016-07-05 17:50:46 +02:00
committed by GitHub
parent 62b9c1b14f
commit c26cfc1c5a
16 changed files with 326 additions and 24 deletions

View File

@@ -17,7 +17,8 @@
//! Blockchain database client.
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
use std::time::Instant;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrdering};
use util::*;
use util::panics::*;
use views::BlockView;
@@ -38,7 +39,7 @@ use filter::Filter;
use log_entry::LocalizedLogEntry;
use block_queue::{BlockQueue, BlockQueueInfo};
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute};
use client::{BlockID, TransactionID, UncleID, TraceId, ClientConfig, DatabaseCompactionProfile,
use client::{BlockID, TransactionID, UncleID, TraceId, Mode, ClientConfig, DatabaseCompactionProfile,
BlockChainClient, MiningBlockChainClient, TraceFilter, CallAnalytics, TransactionImportError,
BlockImportError, TransactionImportResult};
use client::Error as ClientError;
@@ -54,6 +55,7 @@ use evm::Factory as EvmFactory;
use miner::{Miner, MinerService, AccountDetails};
const MAX_TX_QUEUE_SIZE: usize = 4096;
const MAX_QUEUE_SIZE_TO_SLEEP_ON: usize = 2;
impl fmt::Display for BlockChainInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
@@ -83,9 +85,24 @@ impl ClientReport {
}
}
struct SleepState {
last_activity: Option<Instant>,
last_autosleep: Option<Instant>,
}
impl SleepState {
fn new(awake: bool) -> Self {
SleepState {
last_activity: match awake { false => None, true => Some(Instant::now()) },
last_autosleep: match awake { false => Some(Instant::now()), true => None },
}
}
}
/// Blockchain database client backed by a persistent database. Owns and manages a blockchain and a block queue.
/// Call `import_block()` to import a block asynchronously; `flush_queue()` flushes the queue.
pub struct Client {
mode: Mode,
chain: Arc<BlockChain>,
tracedb: Arc<TraceDB<BlockChain>>,
engine: Arc<Box<Engine>>,
@@ -98,6 +115,8 @@ pub struct Client {
vm_factory: Arc<EvmFactory>,
trie_factory: TrieFactory,
miner: Arc<Miner>,
sleep_state: Mutex<SleepState>,
liveness: AtomicBool,
io_channel: IoChannel<NetSyncMessage>,
queue_transactions: AtomicUsize,
}
@@ -134,9 +153,8 @@ impl Client {
spec: Spec,
path: &Path,
miner: Arc<Miner>,
message_channel: IoChannel<NetSyncMessage>)
-> Result<Arc<Client>, ClientError>
{
message_channel: IoChannel<NetSyncMessage>
) -> Result<Arc<Client>, ClientError> {
let path = get_db_path(path, config.pruning, spec.genesis_header().hash());
let gb = spec.genesis_block();
let chain = Arc::new(BlockChain::new(config.blockchain, &gb, &path));
@@ -167,7 +185,11 @@ impl Client {
let panic_handler = PanicHandler::new_in_arc();
panic_handler.forward_from(&block_queue);
let awake = match config.mode { Mode::Dark(..) => false, _ => true };
let client = Client {
sleep_state: Mutex::new(SleepState::new(awake)),
liveness: AtomicBool::new(awake),
mode: config.mode,
chain: chain,
tracedb: tracedb,
engine: engine,
@@ -183,7 +205,6 @@ impl Client {
io_channel: message_channel,
queue_transactions: AtomicUsize::new(0),
};
Ok(Arc::new(client))
}
@@ -449,9 +470,41 @@ impl Client {
}
/// Tick the client.
// TODO: manage by real events.
pub fn tick(&self) {
self.chain.collect_garbage();
self.block_queue.collect_garbage();
match self.mode {
Mode::Dark(timeout) => {
let mut ss = self.sleep_state.lock().unwrap();
if let Some(t) = ss.last_activity {
if Instant::now() > t + timeout {
self.sleep();
ss.last_activity = None;
}
}
}
Mode::Passive(timeout, wakeup_after) => {
let mut ss = self.sleep_state.lock().unwrap();
let now = Instant::now();
if let Some(t) = ss.last_activity {
if now > t + timeout {
self.sleep();
ss.last_activity = None;
ss.last_autosleep = Some(now);
}
}
if let Some(t) = ss.last_autosleep {
if now > t + wakeup_after {
self.wake_up();
ss.last_activity = Some(now);
ss.last_autosleep = None;
}
}
}
_ => {}
}
}
/// Set up the cache behaviour.
@@ -487,6 +540,29 @@ impl Client {
})
}
}
fn wake_up(&self) {
if !self.liveness.load(AtomicOrdering::Relaxed) {
self.liveness.store(true, AtomicOrdering::Relaxed);
self.io_channel.send(NetworkIoMessage::User(SyncMessage::StartNetwork)).unwrap();
trace!(target: "mode", "wake_up: Waking.");
}
}
fn sleep(&self) {
if self.liveness.load(AtomicOrdering::Relaxed) {
// only sleep if the import queue is mostly empty.
if self.queue_info().total_queue_size() <= MAX_QUEUE_SIZE_TO_SLEEP_ON {
self.liveness.store(false, AtomicOrdering::Relaxed);
self.io_channel.send(NetworkIoMessage::User(SyncMessage::StopNetwork)).unwrap();
trace!(target: "mode", "sleep: Sleeping.");
} else {
trace!(target: "mode", "sleep: Cannot sleep - syncing ongoing.");
// TODO: Consider uncommenting.
//*self.last_activity.lock().unwrap() = Some(Instant::now());
}
}
}
}
impl BlockChainClient for Client {
@@ -528,6 +604,12 @@ impl BlockChainClient for Client {
ret
}
fn keep_alive(&self) {
if self.mode != Mode::Active {
self.wake_up();
(*self.sleep_state.lock().unwrap()).last_activity = Some(Instant::now());
}
}
fn block_header(&self, id: BlockID) -> Option<Bytes> {
Self::block_hash(&self.chain, id).and_then(|hash| self.chain.block(&hash).map(|bytes| BlockView::new(&bytes).rlp().at(0).as_raw().to_vec()))

View File

@@ -14,6 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
pub use std::time::Duration;
pub use block_queue::BlockQueueConfig;
pub use blockchain::Config as BlockChainConfig;
pub use trace::{Config as TraceConfig, Switch};
@@ -35,6 +36,23 @@ impl Default for DatabaseCompactionProfile {
fn default() -> Self { DatabaseCompactionProfile::Default }
}
/// Operating mode for the client.
#[derive(Debug, Eq, PartialEq)]
pub enum Mode {
/// Always on.
Active,
/// Goes offline after RLP is inactive for some (given) time, but
/// comes back online after a while of inactivity.
Passive(Duration, Duration),
/// Goes offline after RLP is inactive for some (given) time and
/// stays inactive.
Dark(Duration),
}
impl Default for Mode {
fn default() -> Self { Mode::Active }
}
/// Client configuration. Includes configs for all sub-systems.
#[derive(Debug, Default)]
pub struct ClientConfig {
@@ -56,6 +74,8 @@ pub struct ClientConfig {
pub db_cache_size: Option<usize>,
/// State db compaction profile
pub db_compaction: DatabaseCompactionProfile,
/// Operating mode
pub mode: Mode,
/// Type of block verifier used by client.
pub verifier_type: VerifierType,
}

View File

@@ -23,7 +23,7 @@ mod test_client;
mod trace;
pub use self::client::*;
pub use self::config::{ClientConfig, DatabaseCompactionProfile, BlockQueueConfig, BlockChainConfig, Switch, VMType};
pub use self::config::{Mode, ClientConfig, DatabaseCompactionProfile, BlockQueueConfig, BlockChainConfig, Switch, VMType};
pub use self::error::Error;
pub use types::ids::*;
pub use self::test_client::{TestBlockChainClient, EachBlockWith};
@@ -63,6 +63,11 @@ pub struct CallAnalytics {
/// Blockchain database client. Owns and manages a blockchain and a block queue.
pub trait BlockChainClient : Sync + Send {
/// Should be called by any external-facing interface when actively using the client.
/// To minimise chatter, there's no need to call more than once every 30s.
fn keep_alive(&self) {}
/// Get raw block header data by block id.
fn block_header(&self, id: BlockID) -> Option<Bytes>;