Skip to content

Commit

Permalink
refact(project): add connection to struct
Browse files Browse the repository at this point in the history
This was a lot more complicated than I expected, because you cannot share a
rusqlite `Connection` object across unwrap boundaries (you can share it across
threads, but our `Async` does automatic unwrapping …).

So what I do here is that whenever we want to clone a `Project`, I create
something I call “skeleton”, which is a clone of the project without the
connection object (just the sqlite file path), then on the other side of the
boundary I turn the skeleton back into a Project by connecting to the database
again. I think this is quite an okay solution.
  • Loading branch information
Profpatsch committed Apr 20, 2024
1 parent d44b6fc commit 3db14ab
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 70 deletions.
10 changes: 5 additions & 5 deletions src/build_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ pub enum Reason {
/// If a build is ongoing, it will finish the build first.
/// If there was intermediate requests for new builds, it will schedule a build to be run right after.
/// Additionally, we create GC roots for the build results.
pub struct BuildLoop<'a> {
pub struct BuildLoop {
/// Project to be built.
project: &'a Project,
project: Project,
/// Extra options to pass to each nix invocation
extra_nix_options: NixOptions,
/// Watches all input files for changes.
Expand Down Expand Up @@ -102,19 +102,19 @@ impl BuildState {
}
}

impl<'a> BuildLoop<'a> {
impl BuildLoop {
/// Instatiate a new BuildLoop. Uses an internal filesystem
/// watching implementation.
///
/// Will start by only watching the project’s nix file,
/// and then add new files after each nix run.
pub fn new(
project: &'a Project,
project: Project,
extra_nix_options: NixOptions,
nix_gc_root_user_dir: project::NixGcRootUserDir,
cas: ContentAddressable,
logger: slog::Logger,
) -> anyhow::Result<BuildLoop<'a>> {
) -> anyhow::Result<BuildLoop> {
let mut watch = Watch::try_new(logger.clone()).map_err(|err| anyhow!(err))?;
watch
.extend(vec![WatchPathBuf::Normal(
Expand Down
33 changes: 19 additions & 14 deletions src/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::ops::error::ExitError;
use crate::socket::communicate;
use crate::socket::path::SocketPath;
use crate::sqlite::Sqlite;
use crate::thread::Pool;
use crate::{project, AbsPathBuf, NixFile};
use crossbeam_channel as chan;
use slog::debug;
Expand Down Expand Up @@ -83,7 +84,7 @@ impl Daemon {
chan::Receiver<IndicateActivity>,
) = chan::unbounded();

let mut pool = crate::thread::Pool::new(logger.clone());
let mut pool: Pool<ExitError> = crate::thread::Pool::new(logger.clone());
let tx_build_events = self.tx_build_events.clone();

let server = server::Server::new(tx_activity, tx_build_events);
Expand All @@ -94,7 +95,7 @@ impl Daemon {
let logger3 = logger.clone();

pool.spawn("accept-loop", move || {
server.listen(&socket_path, &logger).map(|n| n.never())
server.listen(&socket_path, &logger).map(|n| n.never())?
})?;

let rx_build_events = self.rx_build_events.clone();
Expand All @@ -109,17 +110,17 @@ impl Daemon {
let gc_root_dir = gc_root_dir.clone();
let sqlite_path = sqlite_path.clone();
pool.spawn("build-instruction-handler", move || {
let mut conn = Sqlite::new_connection(&sqlite_path);
let conn = Sqlite::new_connection(&sqlite_path)?;
Self::build_instruction_handler(
tx_build_events,
extra_nix_options,
rx_activity,
&gc_root_dir,
&mut conn,
conn,
cas,
nix_gc_root_user_dir,
&logger3,
);
)?;
Ok(())
})?;

Expand Down Expand Up @@ -182,21 +183,23 @@ impl Daemon {
extra_nix_options: NixOptions,
rx_activity: chan::Receiver<IndicateActivity>,
gc_root_dir: &AbsPathBuf,
conn: &mut Sqlite,
conn: Sqlite,
cas: crate::cas::ContentAddressable,
nix_gc_root_user_dir: project::NixGcRootUserDir,
logger: &slog::Logger,
) {
) -> Result<(), ExitError> {
// A thread for each `BuildLoop`, keyed by the nix files listened on.
let mut handler_threads: HashMap<NixFile, chan::Sender<()>> = HashMap::new();

// For each build instruction, add the corresponding file
// to the watch list.
for IndicateActivity { nix_file, rebuild } in rx_activity {
let project =
crate::project::Project::new_and_gc_nix_files(conn, nix_file, gc_root_dir)
// TODO: the project needs to create its gc root dir
.unwrap();
let project = crate::project::Project::new_and_gc_nix_files(
conn.clone()?,
nix_file.clone(),
gc_root_dir,
)
.map_err(|err| ExitError::panic(err))?;

let key = project.nix_file.clone();
let project_is_watched = handler_threads.get(&key);
Expand All @@ -223,6 +226,7 @@ impl Daemon {
let logger = logger.clone();
let logger2 = logger.clone();
let cas2 = cas.clone();
let nix_file2 = nix_file.clone();
// TODO: how to use the pool here?
// We cannot just spawn new threads once messages come in,
// because then then pool objects is stuck in this loop
Expand All @@ -233,7 +237,7 @@ impl Daemon {
// pool.spawn(format!("build_loop for {}", nix_file.display()),
let _ = std::thread::spawn(move || {
match BuildLoop::new(
&project,
project,
extra_nix_options,
nix_gc_root_user_dir,
cas2,
Expand All @@ -247,12 +251,12 @@ impl Daemon {
{
tx_build_events
.send(LoopHandlerEvent::BuildEvent(Event::Failure {
nix_file: project.nix_file.clone(),
nix_file: nix_file2,
failure: crate::builder::BuildError::Io {
msg: err
.context(format!(
"could not start the watcher for {}",
&project.nix_file.display()
nix_file.display()
))
.to_string(),
},
Expand All @@ -274,5 +278,6 @@ impl Daemon {
}
}
}
Ok(())
}
}
10 changes: 5 additions & 5 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,16 @@ fn find_nix_file(shellfile: &Path) -> Result<NixFile, ExitError> {
/// Run the main function of the relevant command.
fn run_command(orig_logger: &slog::Logger, opts: Arguments) -> Result<(), ExitError> {
let paths = lorri::ops::get_paths()?;
let mut conn = Sqlite::new_connection(&paths.sqlite_db);
let conn = Sqlite::new_connection(&paths.sqlite_db)?;

// TODO: TMP
conn.migrate_gc_roots(&orig_logger, &paths).unwrap();

let mut with_project_resolved =
|nix_file| -> std::result::Result<(Project, slog::Logger), ExitError> {
let with_project_resolved =
|nix_file: NixFile| -> std::result::Result<(Project, slog::Logger), ExitError> {
let project = {
let paths = &lorri::ops::get_paths()?;
Project::new_and_gc_nix_files(&mut conn, nix_file, paths.gc_root_dir()).map_err(
Project::new_and_gc_nix_files(conn, nix_file.clone(), paths.gc_root_dir()).map_err(
|err| {
ExitError::temporary(
anyhow::anyhow!(err).context("Could not set up project paths"),
Expand All @@ -110,7 +110,7 @@ fn run_command(orig_logger: &slog::Logger, opts: Arguments) -> Result<(), ExitEr
let logger = orig_logger.new(o!("nix_file" => project.nix_file.clone()));
Ok((project, logger))
};
let mut with_project = |nix_file| -> std::result::Result<(Project, slog::Logger), ExitError> {
let with_project = |nix_file| -> std::result::Result<(Project, slog::Logger), ExitError> {
with_project_resolved(find_nix_file(nix_file)?)
};

Expand Down
15 changes: 8 additions & 7 deletions src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,14 +423,14 @@ fn build_root(
logger: &slog::Logger,
) -> Result<OutputPath, ExitError> {
let logger2 = logger.clone();
let project2 = project.clone();
let nix_file = project.nix_file.clone();
let cas2 = cas.clone();

let run_result = crate::thread::race(
logger,
move |_ignored_stop| {
Ok(builder::instantiate_and_build(
&project2.nix_file,
&nix_file,
&cas2,
&crate::nix::options::NixOptions::empty(),
&logger2,
Expand Down Expand Up @@ -938,7 +938,7 @@ fn main_run_once(
) -> Result<(), ExitError> {
// TODO: add the ability to pass extra_nix_options to watch
let mut build_loop = BuildLoop::new(
&project,
project,
NixOptions::empty(),
nix_gc_root_user_dir,
cas.clone(),
Expand Down Expand Up @@ -968,8 +968,8 @@ pub fn op_gc(
paths: &Paths,
opts: crate::cli::GcOptions,
) -> Result<(), ExitError> {
let infos = Project::list_roots(logger, paths)?;
let mut conn = Sqlite::new_connection(&paths.sqlite_db);
let conn = Sqlite::new_connection(&paths.sqlite_db)?;
let infos = Project::list_roots(logger, paths, conn)?;
match opts.action {
cli::GcSubcommand::Info => {
if opts.json {
Expand Down Expand Up @@ -1027,7 +1027,7 @@ pub fn op_gc(
}
} else {
for (info, project) in to_remove {
match project.remove_project(&mut conn) {
match project.remove_project() {
Ok(()) => result.push(Ok(info)),
Err(e) => result.push(Err((info, e.to_string()))),
}
Expand Down Expand Up @@ -1095,11 +1095,12 @@ fn main_run_forever(
let (tx_ping, rx_ping) = chan::unbounded();
let logger2 = logger.clone();
let cas2 = cas.clone();
let project2 = project.into_skeleton();
// TODO: add the ability to pass extra_nix_options to watch
let build_thread = {
Async::run(logger, move || {
match BuildLoop::new(
&project,
project2.into_project()?,
NixOptions::empty(),
nix_gc_root_user_dir,
cas2,
Expand Down
22 changes: 22 additions & 0 deletions src/ops/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,25 @@ where
}
}
}

/// Spit up sqlite errors into programming errors, setup/environment issues and temporary things like full disk
impl ExitAs for rusqlite::Error {
fn exit_as(&self) -> ExitErrorType {
match self {
rusqlite::Error::SqliteFailure(err, _msg) => match err.code {
rusqlite::ErrorCode::CannotOpen
| rusqlite::ErrorCode::PermissionDenied
| rusqlite::ErrorCode::ReadOnly
| rusqlite::ErrorCode::NoLargeFileSupport
| rusqlite::ErrorCode::NotADatabase => ExitErrorType::EnvironmentProblem,
rusqlite::ErrorCode::OutOfMemory
| rusqlite::ErrorCode::SystemIoFailure
| rusqlite::ErrorCode::DatabaseCorrupt
| rusqlite::ErrorCode::DiskFull => ExitErrorType::Temporary,

_ => ExitErrorType::Panic,
},
_ => ExitErrorType::Panic,
}
}
}
Loading

0 comments on commit 3db14ab

Please sign in to comment.