Skip to content

Commit

Permalink
Convert supervisor config to global singleton
Browse files Browse the repository at this point in the history
This commit converts to using a global singleton for configuration,
rather than threading a reference to everything that needs configuration
data.

Since we need to populate our `Config` struct with data from `clap`, we
need to be able to modify it at runtime with an argument, rather than
simply use `lazy_static!`. This implementation stores the configuration
as a static pointer to a location on the heap via `Box`, and updates
the pointer via `mem::transmute` in a `gcache()` method. This method
is called early in `main` - right after we populate the configuration
struct, in fact.

Once `gcache()` has been called (one time only; subsequent calls will be
ignored), you can access the `Config` struct through the
`config::gconfig()` method.

This removes all the references to threading an `&Config` through the
code base, and replaces them with calls to `gconfig()`.

In particular, it cleans up the need for lifetimes in the topology and
worker code, which is a nice side effect.

Signed-off-by: Adam Jacob <[email protected]>
  • Loading branch information
adamhjk committed Sep 16, 2016
1 parent 65da87b commit c1b18ae
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 106 deletions.
6 changes: 3 additions & 3 deletions components/sup/src/command/configure.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::io::prelude::*;
use std::fs::File;

use error::Result;
use config::Config;
use config::gconfig;
use package::Package;

/// Print the default.toml for a given package.
Expand All @@ -36,8 +36,8 @@ use package::Package;
/// * If the package cannot be found
/// * If the default.toml does not exist, or cannot be read
/// * If we can't read the file into a string
pub fn display(config: &Config) -> Result<()> {
let package = try!(Package::load(config.package(), None));
pub fn display() -> Result<()> {
let package = try!(Package::load(gconfig().package(), None));
let mut file = try!(File::open(package.path().join("default.toml")));
let mut s = String::new();
try!(file.read_to_string(&mut s));
Expand Down
38 changes: 19 additions & 19 deletions components/sup/src/command/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ use hcore::package::PackageIdent;

use {PRODUCT, VERSION};
use error::Result;
use config::{Config, UpdateStrategy};
use config::{gconfig, UpdateStrategy};
use package::Package;
use topology::{self, Topology};

Expand All @@ -83,17 +83,17 @@ static LOGKEY: &'static str = "CS";
/// * Fails if it cannot find a package with the given name
/// * Fails if the `run` method for the topology fails
/// * Fails if an unknown topology was specified on the command line
pub fn package(config: &Config) -> Result<()> {
pub fn package() -> Result<()> {
let mut ui = UI::default();
match Package::load(config.package(), None) {
match Package::load(gconfig().package(), None) {
Ok(mut package) => {
let update_strategy = config.update_strategy();
let update_strategy = gconfig().update_strategy();
match update_strategy {
UpdateStrategy::None => {}
_ => {
let url = config.url();
let url = gconfig().url();
outputln!("Checking Depot for newer versions...");
// It is important to pass `config.package()` to `show_package()` instead
// It is important to pass `gconfig().package()` to `show_package()` instead
// of the package identifier of the loaded package. This will ensure that
// if the operator starts a package while specifying a version number, they
// will only automaticaly receive release updates for the started package.
Expand All @@ -103,7 +103,7 @@ pub fn package(config: &Config) -> Result<()> {
// number, for the started package.
let depot_client = try!(Client::new(url, PRODUCT, VERSION, None));
let latest_pkg_data =
try!(depot_client.show_package((*config.package()).clone()));
try!(depot_client.show_package((*gconfig().package()).clone()));
let latest_ident: PackageIdent = latest_pkg_data.get_ident().clone().into();
if &latest_ident > package.ident() {
outputln!("Downloading latest version from Depot: {}", latest_ident);
Expand All @@ -121,13 +121,13 @@ pub fn package(config: &Config) -> Result<()> {
};
}
}
start_package(package, config)
start_package(package)
}
Err(_) => {
outputln!("{} is not installed",
Yellow.bold().paint(config.package().to_string()));
let url = config.url();
let new_pkg_data = match config.local_artifact() {
Yellow.bold().paint(gconfig().package().to_string()));
let url = gconfig().url();
let new_pkg_data = match gconfig().local_artifact() {
Some(artifact) => {
try!(install::start(&mut ui,
url,
Expand All @@ -140,11 +140,11 @@ pub fn package(config: &Config) -> Result<()> {
}
None => {
outputln!("Searching for {} in remote {}",
Yellow.bold().paint(config.package().to_string()),
Yellow.bold().paint(gconfig().package().to_string()),
url);
try!(install::start(&mut ui,
url,
&config.package().to_string(),
&gconfig().package().to_string(),
PRODUCT,
VERSION,
Path::new(FS_ROOT_PATH),
Expand All @@ -153,18 +153,18 @@ pub fn package(config: &Config) -> Result<()> {
}
};
let package = try!(Package::load(&new_pkg_data, None));
start_package(package, config)
start_package(package)
}
}
}

fn start_package(package: Package, config: &Config) -> Result<()> {
fn start_package(package: Package) -> Result<()> {
let run_path = try!(package.run_path());
debug!("Setting the PATH to {}", run_path);
env::set_var("PATH", &run_path);
match *config.topology() {
Topology::Standalone => topology::standalone::run(package, config),
Topology::Leader => topology::leader::run(package, config),
Topology::Initializer => topology::initializer::run(package, config),
match *gconfig().topology() {
Topology::Standalone => topology::standalone::run(package),
Topology::Leader => topology::leader::run(package),
Topology::Initializer => topology::initializer::run(package),
}
}
28 changes: 28 additions & 0 deletions components/sup/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
//! See the [Config](struct.Config.html) struct for the specific options available.
use std::str::FromStr;
use std::sync::{Once, ONCE_INIT};
use std::mem;

use hcore::package::PackageIdent;

Expand All @@ -30,6 +32,32 @@ use topology::Topology;

static LOGKEY: &'static str = "CFG";

/// The Static Global Configuration.
///
/// This sets up a raw pointer, which we are going to transmute to a Box<Config>
/// with the first call to gcache().
static mut CONFIG: *const Config = 0 as *const Config;

/// Store a configuration, for later use through `gconfig()`.
///
/// MUST BE CALLED BEFORE ANY CALLS TO `gconfig()`.
pub fn gcache(config: Config) {
static ONCE: Once = ONCE_INIT;
unsafe {
ONCE.call_once(|| {
CONFIG = mem::transmute(Box::new(config));
});
}
}

/// Return a reference to our cached configuration.
///
/// This is unsafe, because we are de-referencing the raw pointer stored in
/// CONFIG.
pub fn gconfig() -> &'static Config {
unsafe { &*CONFIG }
}

#[derive(Debug, Clone, PartialEq, Eq)]
/// An enum with the various CLI commands. Used to keep track of what command was called.
pub enum Command {
Expand Down
41 changes: 21 additions & 20 deletions components/sup/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use hcore::crypto::init as crypto_init;
use hcore::package::{PackageArchive, PackageIdent};
use hcore::url::{DEFAULT_DEPOT_URL, DEPOT_URL_ENVVAR};

use sup::config::{Command, Config, UpdateStrategy};
use sup::config::{gcache, gconfig, Command, Config, UpdateStrategy};
use sup::error::{Error, Result, SupError};
use sup::command::*;
use sup::topology::Topology;
Expand All @@ -65,7 +65,7 @@ static RING_KEY_ENVVAR: &'static str = "HAB_RING_KEY";

/// Creates a [Config](config/struct.Config.html) from global args
/// and subcommand args.
fn config_from_args(subcommand: &str, sub_args: &ArgMatches) -> Result<Config> {
fn config_from_args(subcommand: &str, sub_args: &ArgMatches) -> Result<()> {
let mut config = Config::new();
let command = try!(Command::from_str(subcommand));
config.set_command(command);
Expand Down Expand Up @@ -218,10 +218,11 @@ fn config_from_args(subcommand: &str, sub_args: &ArgMatches) -> Result<Config> {
config.set_organization(org.to_string());
}
debug!("Config:\n{:?}", config);
Ok(config)
gcache(config);
Ok({})
}

type Handler = fn(&Config) -> result::Result<(), sup::error::SupError>;
type Handler = fn() -> result::Result<(), sup::error::SupError>;

/// The entrypoint for the Supervisor.
///
Expand Down Expand Up @@ -315,7 +316,7 @@ fn main() {
let sub_config = SubCommand::with_name("config")
.about("Print the default.toml for a given package")
.aliases(&["c", "co", "con", "conf", "confi"])
.arg(Arg::with_name("package")
.arg(Arg::with_name("pkg_ident_or_artifact")
.index(1)
.required(true)
.help("Name of package"));
Expand Down Expand Up @@ -343,16 +344,16 @@ fn main() {
debug!("subcommand name {:?}", &subcommand_name);
debug!("Subcommand matches {:?}", &subcommand_matches);

let config = match config_from_args(subcommand_name, &subcommand_matches) {
Ok(config) => config,
match config_from_args(subcommand_name, &subcommand_matches) {
Ok(()) => {}
Err(e) => return exit_with(e, 1),
};

let result = match config.command() {
Command::ShellBash => shell_bash(&config),
Command::ShellSh => shell_sh(&config),
Command::Config => configure(&config),
Command::Start => start(&config),
let result = match gconfig().command() {
Command::ShellBash => shell_bash(),
Command::ShellSh => shell_sh(),
Command::Config => configure(),
Command::Start => start(),
};

match result {
Expand All @@ -370,30 +371,30 @@ fn exit_with(e: SupError, code: i32) {

/// Start a sh shell
#[allow(dead_code)]
fn shell_sh(_config: &Config) -> Result<()> {
fn shell_sh() -> Result<()> {
shell::sh()
}

/// Start a bash shell
#[allow(dead_code)]
fn shell_bash(_config: &Config) -> Result<()> {
fn shell_bash() -> Result<()> {
shell::bash()
}

/// Show the configuration options for a service
#[allow(dead_code)]
fn configure(config: &Config) -> Result<()> {
try!(configure::display(config));
fn configure() -> Result<()> {
try!(configure::display());
Ok(())
}

/// Start a service
#[allow(dead_code)]
fn start(config: &Config) -> Result<()> {
fn start() -> Result<()> {
outputln!("Starting {}",
Yellow.bold().paint(config.package().to_string()));
try!(start::package(config));
Yellow.bold().paint(gconfig().package().to_string()));
try!(start::package());
outputln!("Finished with {}",
Yellow.bold().paint(config.package().to_string()));
Yellow.bold().paint(gconfig().package().to_string()));
Ok(())
}
Loading

0 comments on commit c1b18ae

Please sign in to comment.