diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dfe853b..09d1ab39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All changes in this project will be noted in this file. +## Unreleased + +### Additions + +- The maximum number of clients can now be manually configured [see [#182](https://github.com/skytable/skytable/pull/182)] + +### Fixes + +- Ability to connect to the server over TLS has been restored in `skysh` [see [#181](https://github.com/skytable/skytable/pull/181)] + ## Version 0.6.2 [2021-06-24] ### Fixes diff --git a/examples/config-files/template.toml b/examples/config-files/template.toml index 320ed8de..41bf98ed 100644 --- a/examples/config-files/template.toml +++ b/examples/config-files/template.toml @@ -11,6 +11,7 @@ host = "127.0.0.1" # The IP address to which you want sdb to bind to port = 2003 # The port to which you want sdb to bind to # Set `noart` to true if you want to disable terminal artwork noart = false +maxcon = 50000 # set the maximum number of clients that the server can accept # This key is *OPTIONAL* [bgsave] @@ -30,4 +31,4 @@ failsafe = true # stops accepting writes if snapshotting fails key = "/path/to/keyfile.pem" chain = "/path/to/chain.pem" port = 2004 -only = true # optional to enable SSL-only requests \ No newline at end of file +only = true # optional to enable SSL-only requests diff --git a/server/src/arbiter.rs b/server/src/arbiter.rs new file mode 100644 index 00000000..fe545c60 --- /dev/null +++ b/server/src/arbiter.rs @@ -0,0 +1,131 @@ +/* + * Created on Sat Jun 26 2021 + * + * This file is a part of Skytable + * Skytable (formerly known as TerrabaseDB or Skybase) is a free and open-source + * NoSQL database written by Sayan Nandan ("the Author") with the + * vision to provide flexibility in data modelling without compromising + * on performance, queryability or scalability. + * + * Copyright (c) 2021, Sayan Nandan + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * +*/ + +use crate::config::BGSave; +use crate::config::SnapshotConfig; +use crate::coredb::CoreDB; +use crate::dbnet::{self, Terminator}; +use crate::diskstore::snapshot::DIR_REMOTE_SNAPSHOT; +use crate::services; +use crate::PortConfig; +use std::fs; +use tokio::sync::broadcast; + +#[cfg(unix)] +use core::{future::Future, pin::Pin, task::Context, task::Poll}; +#[cfg(unix)] +use tokio::signal::unix::{signal as fnsignal, Signal, SignalKind}; +#[cfg(unix)] +/// Object to bind to unix-specific signals +pub struct UnixTerminationSignal { + sigterm: Signal, +} + +#[cfg(unix)] +impl UnixTerminationSignal { + pub fn init() -> Result { + let sigterm = fnsignal(SignalKind::terminate()) + .map_err(|e| format!("Failed to bind to signal with: {}", e))?; + Ok(Self { sigterm }) + } +} + +#[cfg(unix)] +impl Future for UnixTerminationSignal { + type Output = Option<()>; + + fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { + self.sigterm.poll_recv(ctx) + } +} + +/// Start the server waiting for incoming connections or a termsig +pub async fn run( + ports: PortConfig, + bgsave_cfg: BGSave, + snapshot_cfg: SnapshotConfig, + restore_filepath: Option, + maxcon: usize, +) -> Result { + // Intialize the broadcast channel + let (signal, _) = broadcast::channel(1); + + // create the data directories and intialize the coredb + fs::create_dir_all(&*DIR_REMOTE_SNAPSHOT) + .map_err(|e| format!("Failed to create data directories: '{}'", e))?; + let db = CoreDB::new(&snapshot_cfg, restore_filepath) + .map_err(|e| format!("Error while initializing database: {}", e))?; + + // initialize the background services + let bgsave_handle = tokio::spawn(services::bgsave::bgsave_scheduler( + db.clone(), + bgsave_cfg, + Terminator::new(signal.subscribe()), + )); + let snapshot_handle = tokio::spawn(services::snapshot::snapshot_service( + db.clone(), + snapshot_cfg, + Terminator::new(signal.subscribe()), + )); + + // bind the ctrlc handler + let sig = tokio::signal::ctrl_c(); + + // start the server (single or multiple listeners) + let mut server = dbnet::connect(ports, maxcon, db.clone(), signal.clone()).await?; + + #[cfg(not(unix))] + { + // Non-unix, usually Windows specific signal handling. + // FIXME(@ohsayan): For now, let's just + // bother with ctrl+c, we'll move ahead as users require them + tokio::select! { + _ = server.run_server() => {} + _ = sig => {} + } + } + #[cfg(unix)] + { + let sigterm = UnixTerminationSignal::init()?; + // apart from CTRLC, the only other thing we care about is SIGTERM + // FIXME(@ohsayan): Maybe we should respond to SIGHUP too? + tokio::select! { + _ = server.run_server() => {}, + _ = sig => {}, + _ = sigterm => {} + } + } + + log::info!("Signalling all workers to shut down"); + // drop the signal and let others exit + drop(signal); + server.finish_with_termsig().await; + + // wait for the background services to terminate + let _ = snapshot_handle.await; + let _ = bgsave_handle.await; + Ok(db) +} diff --git a/server/src/cli.yml b/server/src/cli.yml index 3306d681..8ba492c9 100644 --- a/server/src/cli.yml +++ b/server/src/cli.yml @@ -84,6 +84,12 @@ args: long: stop-write-on-fail takes_value: true help: Stop accepting writes if any persistence method except BGSAVE fails (defaults to true) + - maxcon: + required: false + long: maxcon + takes_value: true + help: Set the maximum number of connections + value_name: maxcon subcommands: - upgrade: about: Upgrades old datsets to the latest format supported by this server edition diff --git a/server/src/config/mod.rs b/server/src/config/mod.rs index 6b123e2a..8e4f3b27 100644 --- a/server/src/config/mod.rs +++ b/server/src/config/mod.rs @@ -27,6 +27,7 @@ //! This module provides tools to handle configuration files and settings use crate::compat; +use crate::dbnet::MAXIMUM_CONNECTION_LIMIT; use core::hint::unreachable_unchecked; #[cfg(test)] use libsky::TResult; @@ -114,6 +115,8 @@ pub struct ConfigKeyServer { /// The noart key is an `Option`al boolean value which is set to true /// for secure environments to disable terminal artwork noart: Option, + /// The maximum number of clients + maxclient: Option, } /// The snapshot section in the TOML file @@ -260,6 +263,8 @@ pub struct ParsedConfig { pub snapshot: SnapshotConfig, /// Port configuration pub ports: PortConfig, + /// The maximum number of connections + pub maxcon: usize, } impl ParsedConfig { @@ -327,6 +332,7 @@ impl ParsedConfig { port: cfg_info.server.port, } }, + maxcon: libsky::option_unwrap_or!(cfg_info.server.maxclient, MAXIMUM_CONNECTION_LIMIT), } } #[cfg(test)] @@ -340,12 +346,14 @@ impl ParsedConfig { bgsave: BGSave, snapshot: SnapshotConfig, ports: PortConfig, + maxcon: usize, ) -> Self { ParsedConfig { noart, bgsave, snapshot, ports, + maxcon, } } /// Create a default `ParsedConfig` with the following setup defaults: @@ -361,6 +369,7 @@ impl ParsedConfig { bgsave: BGSave::default(), snapshot: SnapshotConfig::default(), ports: PortConfig::new_insecure_only(DEFAULT_IPV4, 2003), + maxcon: MAXIMUM_CONNECTION_LIMIT, } } /// Returns `false` if `noart` is enabled. Otherwise it returns `true` @@ -442,6 +451,7 @@ pub fn get_config_file_or_return_cfg() -> Result Result Result "127.0.0.1".parse().unwrap(), }; + let maxcon: usize = match maxcon { + Some(limit) => match limit.parse() { + Ok(l) => l, + Err(_) => { + return Err(ConfigError::CliArgErr( + "Invalid value for `--maxcon`. Expected a valid positive integer", + )); + } + }, + None => MAXIMUM_CONNECTION_LIMIT, + }; let bgsave = if nosave { if saveduration.is_some() { // If there is both `nosave` and `saveduration` - the arguments aren't logically correct! @@ -582,7 +604,7 @@ pub fn get_config_file_or_return_cfg() -> Result Result { - let sigterm = fnsignal(SignalKind::terminate()) - .map_err(|e| format!("Failed to bind to signal with: {}", e))?; - Ok(Self { sigterm }) - } -} - -#[cfg(unix)] -impl Future for UnixTerminationSignal { - type Output = Option<()>; - - fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { - self.sigterm.poll_recv(ctx) - } -} - -/// Start the server waiting for incoming connections or a CTRL+C signal -pub async fn run( +/// Initialize the database networking +pub async fn connect( ports: PortConfig, - bgsave_cfg: BGSave, - snapshot_cfg: SnapshotConfig, - sig: impl Future, - restore_filepath: Option, -) -> Result { - let (signal, _) = broadcast::channel(1); - fs::create_dir_all(&*DIR_REMOTE_SNAPSHOT) - .map_err(|e| format!("Failed to create data directories: '{}'", e))?; - let db = CoreDB::new(&snapshot_cfg, restore_filepath) - .map_err(|e| format!("Error while initializing database: {}", e))?; - let bgsave_handle = tokio::spawn(services::bgsave::bgsave_scheduler( - db.clone(), - bgsave_cfg, - Terminator::new(signal.subscribe()), - )); - let snapshot_handle = tokio::spawn(services::snapshot::snapshot_service( - db.clone(), - snapshot_cfg, - Terminator::new(signal.subscribe()), - )); - let climit = Arc::new(Semaphore::const_new(MAXIMUM_CONNECTION_LIMIT)); - let mut server = match ports { + maxcon: usize, + db: CoreDB, + signal: broadcast::Sender<()>, +) -> Result { + let climit = Arc::new(Semaphore::const_new(maxcon)); + let server = match ports { PortConfig::InsecureOnly { host, port } => MultiListener::new_insecure_only( BaseListener::init(&db, host, port, climit.clone(), signal.clone()) .await @@ -322,32 +271,5 @@ pub async fn run( MultiListener::new_multi(secure_listener, insecure_listener, ssl).await? } }; - #[cfg(not(unix))] - { - // Non-unix, usually Windows specific signal handling. - // FIXME(@ohsayan): For now, let's just - // bother with ctrl+c, we'll move ahead as users require them - tokio::select! { - _ = server.run_server() => {} - _ = sig => {} - } - } - #[cfg(unix)] - { - let sigterm = UnixTerminationSignal::init()?; - // apart from CTRLC, the only other thing we care about is SIGTERM - // FIXME(@ohsayan): Maybe we should respond to SIGHUP too? - tokio::select! { - _ = server.run_server() => {}, - _ = sig => {}, - _ = sigterm => {} - } - } - log::info!("Signalling all workers to shut down"); - // drop the signal and let others exit - drop(signal); - server.finish_with_termsig().await; - let _ = snapshot_handle.await; - let _ = bgsave_handle.await; - Ok(db) + Ok(server) } diff --git a/server/src/main.rs b/server/src/main.rs index 7a085c9f..d5f34486 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -45,9 +45,9 @@ use std::process; use std::sync::Arc; use std::thread; use std::time; -use tokio::signal; mod actions; mod admin; +mod arbiter; mod compat; mod config; mod coredb; @@ -85,21 +85,21 @@ fn main() { .enable_all() .build() .unwrap(); - let (ports, bgsave_config, snapshot_config, restore_filepath) = check_args_and_get_cfg(); + let (ports, bgsave_config, snapshot_config, restore_filepath, maxcon) = + check_args_and_get_cfg(); // check if any other process is using the data directory and lock it if not (else error) // important: create the pid_file just here and nowhere else because check_args can also // involve passing --help or wrong arguments which can falsely create a PID file let pid_file = run_pre_startup_tasks(); let db: Result = runtime.block_on(async move { - let db = dbnet::run( + arbiter::run( ports, bgsave_config, snapshot_config, - signal::ctrl_c(), restore_filepath, + maxcon, ) - .await; - db + .await }); // Make sure all background workers terminate drop(runtime); @@ -150,7 +150,7 @@ use self::config::{BGSave, PortConfig, SnapshotConfig}; /// This function checks the command line arguments and either returns a config object /// or prints an error to `stderr` and terminates the server -fn check_args_and_get_cfg() -> (PortConfig, BGSave, SnapshotConfig, Option) { +fn check_args_and_get_cfg() -> (PortConfig, BGSave, SnapshotConfig, Option, usize) { let cfg = config::get_config_file_or_return_cfg(); let binding_and_cfg = match cfg { Ok(config::ConfigType::Custom(cfg, file)) => { @@ -160,12 +160,12 @@ fn check_args_and_get_cfg() -> (PortConfig, BGSave, SnapshotConfig, Option { println!("Skytable v{} | {}\n{}", VERSION, URL, TEXT); log::warn!("No configuration file supplied. Using default settings"); - (cfg.ports, cfg.bgsave, cfg.snapshot, file) + (cfg.ports, cfg.bgsave, cfg.snapshot, file, cfg.maxcon) } Err(e) => { log::error!("{}", e);