forked from mirrors/jj
backend: make read functions async
The commit backend at Google is cloud-based (and so are the other backends); it reads and writes commits from/to a server, which stores them in a database. That makes latency much higher than for disk-based backends. To reduce the latency, we have a local daemon process that caches and prefetches objects. There are still many cases where latency is high, such as when diffing two uncached commits. We can improve that by changing some of our (jj's) algorithms to read many objects concurrently from the backend. In the case of tree-diffing, we can fetch one level (depth) of the tree at a time. There are several ways of doing that: * Make the backend methods `async` * Use many threads for reading from the backend * Add backend methods for batch reading I don't think we typically need CPU parallelism, so it's wasteful to have hundreds of threads running in order to fetch hundreds of objects in parallel (especially when using a synchronous backend like the Git backend). Batching would work well for the tree-diffing case, but it's not as composable as `async`. For example, if we wanted to fetch some commits at the same time as we were doing a diff, it's hard to see how to do that with batching. Using async seems like our best bet. I didn't make the backend interface's write functions async because writes are already async with the daemon we have at Google. That daemon will hash the object and immediately return, and then send the object to the server in the background. I think any cloud-based solution will need a similar daemon process. However, we may need to reconsider this if/when jj gets used on a server with a custom backend that writes directly to a database (i.e. no async daemon in between). I've tried to measure the performance impact. That's the largest difference I've been able to measure was on `jj diff --ignore-working-copy -s --from v5.0 --to v6.0` in the Linux repo, which increases from 749 ms to 773 ms (3.3%). In most cases I've tested, there's no measurable difference. I've tried diffing from the root commit, as well as `jj --ignore-working-copy log --no-graph -r '::v3.0 & author(torvalds)' -T 'commit_id ++ "\n"'` (to test a commit-heavy load).
This commit is contained in:
parent
bd5eef9c5e
commit
5174489959
11 changed files with 131 additions and 70 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -990,6 +990,7 @@ dependencies = [
|
|||
"anyhow",
|
||||
"assert_cmd",
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"cargo_metadata",
|
||||
"chrono",
|
||||
"clap",
|
||||
|
@ -1036,6 +1037,7 @@ name = "jj-lib"
|
|||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"async-trait",
|
||||
"backoff",
|
||||
"blake2",
|
||||
"byteorder",
|
||||
|
@ -1046,6 +1048,7 @@ dependencies = [
|
|||
"digest",
|
||||
"either",
|
||||
"esl01-renderdag",
|
||||
"futures 0.3.28",
|
||||
"git2",
|
||||
"hex",
|
||||
"insta",
|
||||
|
@ -2083,6 +2086,7 @@ dependencies = [
|
|||
name = "testutils"
|
||||
version = "0.10.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"config",
|
||||
"git2",
|
||||
"itertools 0.11.0",
|
||||
|
|
|
@ -20,6 +20,7 @@ keywords = ["VCS", "DVCS", "SCM", "Git", "Mercurial"]
|
|||
anyhow = "1.0.75"
|
||||
assert_cmd = "2.0.8"
|
||||
assert_matches = "1.5.0"
|
||||
async-trait = "0.1.73"
|
||||
backoff = "0.4.0"
|
||||
blake2 = "0.10.6"
|
||||
byteorder = "1.5.0"
|
||||
|
@ -39,6 +40,7 @@ digest = "0.10.7"
|
|||
dirs = "5.0.1"
|
||||
either = "1.9.0"
|
||||
esl01-renderdag = "0.3.0"
|
||||
futures = "0.3.28"
|
||||
glob = "0.3.1"
|
||||
git2 = "0.17.2"
|
||||
hex = "0.4.3"
|
||||
|
|
|
@ -69,6 +69,7 @@ libc = { workspace = true }
|
|||
|
||||
[dev-dependencies]
|
||||
anyhow = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
assert_cmd = { workspace = true }
|
||||
assert_matches = { workspace = true }
|
||||
insta = { workspace = true }
|
||||
|
|
|
@ -16,6 +16,7 @@ use std::any::Any;
|
|||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use jj_cli::cli_util::{CliRunner, CommandError, CommandHelper};
|
||||
use jj_cli::ui::Ui;
|
||||
use jj_lib::backend::{
|
||||
|
@ -86,6 +87,7 @@ impl JitBackend {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Backend for JitBackend {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
|
@ -115,40 +117,40 @@ impl Backend for JitBackend {
|
|||
self.inner.empty_tree_id()
|
||||
}
|
||||
|
||||
fn read_file(&self, path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
self.inner.read_file(path, id)
|
||||
async fn read_file(&self, path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
self.inner.read_file(path, id).await
|
||||
}
|
||||
|
||||
fn write_file(&self, path: &RepoPath, contents: &mut dyn Read) -> BackendResult<FileId> {
|
||||
self.inner.write_file(path, contents)
|
||||
}
|
||||
|
||||
fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String> {
|
||||
self.inner.read_symlink(path, id)
|
||||
async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String> {
|
||||
self.inner.read_symlink(path, id).await
|
||||
}
|
||||
|
||||
fn write_symlink(&self, path: &RepoPath, target: &str) -> BackendResult<SymlinkId> {
|
||||
self.inner.write_symlink(path, target)
|
||||
}
|
||||
|
||||
fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
self.inner.read_tree(path, id)
|
||||
async fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
self.inner.read_tree(path, id).await
|
||||
}
|
||||
|
||||
fn write_tree(&self, path: &RepoPath, contents: &Tree) -> BackendResult<TreeId> {
|
||||
self.inner.write_tree(path, contents)
|
||||
}
|
||||
|
||||
fn read_conflict(&self, path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
|
||||
self.inner.read_conflict(path, id)
|
||||
async fn read_conflict(&self, path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
|
||||
self.inner.read_conflict(path, id).await
|
||||
}
|
||||
|
||||
fn write_conflict(&self, path: &RepoPath, contents: &Conflict) -> BackendResult<ConflictId> {
|
||||
self.inner.write_conflict(path, contents)
|
||||
}
|
||||
|
||||
fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
|
||||
self.inner.read_commit(id)
|
||||
async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
|
||||
self.inner.read_commit(id).await
|
||||
}
|
||||
|
||||
fn write_commit(&self, contents: Commit) -> BackendResult<(CommitId, Commit)> {
|
||||
|
|
|
@ -19,6 +19,7 @@ harness = false
|
|||
version_check = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true}
|
||||
backoff = { workspace = true }
|
||||
blake2 = { workspace = true }
|
||||
byteorder = { workspace = true }
|
||||
|
@ -26,6 +27,7 @@ bytes = { workspace = true }
|
|||
chrono = { workspace = true }
|
||||
config = { workspace = true }
|
||||
digest = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
either = { workspace = true }
|
||||
git2 = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
|
@ -63,6 +65,7 @@ num_cpus = { workspace = true }
|
|||
pretty_assertions = { workspace = true }
|
||||
test-case = { workspace = true }
|
||||
testutils = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -21,6 +21,7 @@ use std::io::Read;
|
|||
use std::result::Result;
|
||||
use std::vec::Vec;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::content_hash::ContentHash;
|
||||
|
@ -465,6 +466,7 @@ pub fn make_root_commit(root_change_id: ChangeId, empty_tree_id: TreeId) -> Comm
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Backend: Send + Sync + Debug {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
|
@ -484,23 +486,23 @@ pub trait Backend: Send + Sync + Debug {
|
|||
|
||||
fn empty_tree_id(&self) -> &TreeId;
|
||||
|
||||
fn read_file(&self, path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>>;
|
||||
async fn read_file(&self, path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>>;
|
||||
|
||||
fn write_file(&self, path: &RepoPath, contents: &mut dyn Read) -> BackendResult<FileId>;
|
||||
|
||||
fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String>;
|
||||
async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String>;
|
||||
|
||||
fn write_symlink(&self, path: &RepoPath, target: &str) -> BackendResult<SymlinkId>;
|
||||
|
||||
fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree>;
|
||||
async fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree>;
|
||||
|
||||
fn write_tree(&self, path: &RepoPath, contents: &Tree) -> BackendResult<TreeId>;
|
||||
|
||||
fn read_conflict(&self, path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict>;
|
||||
async fn read_conflict(&self, path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict>;
|
||||
|
||||
fn write_conflict(&self, path: &RepoPath, contents: &Conflict) -> BackendResult<ConflictId>;
|
||||
|
||||
fn read_commit(&self, id: &CommitId) -> BackendResult<Commit>;
|
||||
async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit>;
|
||||
|
||||
/// Writes a commit and returns its ID and the commit itself. The commit
|
||||
/// should contain the data that was actually written, which may differ
|
||||
|
|
|
@ -22,6 +22,7 @@ use std::ops::Deref;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use git2::Oid;
|
||||
use itertools::Itertools;
|
||||
use prost::Message;
|
||||
|
@ -479,6 +480,7 @@ impl Debug for GitBackend {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Backend for GitBackend {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
|
@ -508,7 +510,7 @@ impl Backend for GitBackend {
|
|||
&self.empty_tree_id
|
||||
}
|
||||
|
||||
fn read_file(&self, _path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
async fn read_file(&self, _path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
let git_blob_id = validate_git_object_id(id)?;
|
||||
let locked_repo = self.repo.lock().unwrap();
|
||||
let blob = locked_repo
|
||||
|
@ -531,7 +533,7 @@ impl Backend for GitBackend {
|
|||
Ok(FileId::new(oid.as_bytes().to_vec()))
|
||||
}
|
||||
|
||||
fn read_symlink(&self, _path: &RepoPath, id: &SymlinkId) -> Result<String, BackendError> {
|
||||
async fn read_symlink(&self, _path: &RepoPath, id: &SymlinkId) -> Result<String, BackendError> {
|
||||
let git_blob_id = validate_git_object_id(id)?;
|
||||
let locked_repo = self.repo.lock().unwrap();
|
||||
let blob = locked_repo
|
||||
|
@ -558,7 +560,7 @@ impl Backend for GitBackend {
|
|||
Ok(SymlinkId::new(oid.as_bytes().to_vec()))
|
||||
}
|
||||
|
||||
fn read_tree(&self, _path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
async fn read_tree(&self, _path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
if id == &self.empty_tree_id {
|
||||
return Ok(Tree::default());
|
||||
}
|
||||
|
@ -653,11 +655,13 @@ impl Backend for GitBackend {
|
|||
Ok(TreeId::from_bytes(oid.as_bytes()))
|
||||
}
|
||||
|
||||
fn read_conflict(&self, _path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
|
||||
let mut file = self.read_file(
|
||||
&RepoPath::from_internal_string("unused"),
|
||||
&FileId::new(id.to_bytes()),
|
||||
)?;
|
||||
async fn read_conflict(&self, _path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
|
||||
let mut file = self
|
||||
.read_file(
|
||||
&RepoPath::from_internal_string("unused"),
|
||||
&FileId::new(id.to_bytes()),
|
||||
)
|
||||
.await?;
|
||||
let mut data = String::new();
|
||||
file.read_to_string(&mut data)
|
||||
.map_err(|err| BackendError::ReadObject {
|
||||
|
@ -690,7 +694,7 @@ impl Backend for GitBackend {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
|
||||
async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
|
||||
if *id == self.root_commit_id {
|
||||
return Ok(make_root_commit(
|
||||
self.root_change_id().clone(),
|
||||
|
@ -1005,7 +1009,7 @@ mod tests {
|
|||
.collect_vec();
|
||||
assert_eq!(git_refs, vec![git_commit_id2]);
|
||||
|
||||
let commit = backend.read_commit(&commit_id).unwrap();
|
||||
let commit = futures::executor::block_on(backend.read_commit(&commit_id)).unwrap();
|
||||
assert_eq!(&commit.change_id, &change_id);
|
||||
assert_eq!(commit.parents, vec![CommitId::from_bytes(&[0; 20])]);
|
||||
assert_eq!(commit.predecessors, vec![]);
|
||||
|
@ -1034,12 +1038,11 @@ mod tests {
|
|||
);
|
||||
assert_eq!(commit.committer.timestamp.tz_offset, -480);
|
||||
|
||||
let root_tree = backend
|
||||
.read_tree(
|
||||
&RepoPath::root(),
|
||||
&TreeId::from_bytes(root_tree_id.as_bytes()),
|
||||
)
|
||||
.unwrap();
|
||||
let root_tree = futures::executor::block_on(backend.read_tree(
|
||||
&RepoPath::root(),
|
||||
&TreeId::from_bytes(root_tree_id.as_bytes()),
|
||||
))
|
||||
.unwrap();
|
||||
let mut root_entries = root_tree.entries();
|
||||
let dir = root_entries.next().unwrap();
|
||||
assert_eq!(root_entries.next(), None);
|
||||
|
@ -1049,12 +1052,11 @@ mod tests {
|
|||
&TreeValue::Tree(TreeId::from_bytes(dir_tree_id.as_bytes()))
|
||||
);
|
||||
|
||||
let dir_tree = backend
|
||||
.read_tree(
|
||||
&RepoPath::from_internal_string("dir"),
|
||||
&TreeId::from_bytes(dir_tree_id.as_bytes()),
|
||||
)
|
||||
.unwrap();
|
||||
let dir_tree = futures::executor::block_on(backend.read_tree(
|
||||
&RepoPath::from_internal_string("dir"),
|
||||
&TreeId::from_bytes(dir_tree_id.as_bytes()),
|
||||
))
|
||||
.unwrap();
|
||||
let mut entries = dir_tree.entries();
|
||||
let file = entries.next().unwrap();
|
||||
let symlink = entries.next().unwrap();
|
||||
|
@ -1073,7 +1075,7 @@ mod tests {
|
|||
&TreeValue::Symlink(SymlinkId::from_bytes(blob2.as_bytes()))
|
||||
);
|
||||
|
||||
let commit2 = backend.read_commit(&commit_id2).unwrap();
|
||||
let commit2 = futures::executor::block_on(backend.read_commit(&commit_id2)).unwrap();
|
||||
assert_eq!(commit2.parents, vec![commit_id.clone()]);
|
||||
assert_eq!(commit.predecessors, vec![]);
|
||||
assert_eq!(
|
||||
|
@ -1112,9 +1114,10 @@ mod tests {
|
|||
|
||||
// read_commit() without import_head_commits() works as of now. This might be
|
||||
// changed later.
|
||||
assert!(backend
|
||||
.read_commit(&CommitId::from_bytes(git_commit_id.as_bytes()))
|
||||
.is_ok());
|
||||
assert!(futures::executor::block_on(
|
||||
backend.read_commit(&CommitId::from_bytes(git_commit_id.as_bytes()))
|
||||
)
|
||||
.is_ok());
|
||||
assert!(
|
||||
backend
|
||||
.cached_extra_metadata_table()
|
||||
|
@ -1202,7 +1205,7 @@ mod tests {
|
|||
// Only root commit as parent
|
||||
commit.parents = vec![backend.root_commit_id().clone()];
|
||||
let first_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let first_commit = backend.read_commit(&first_id).unwrap();
|
||||
let first_commit = futures::executor::block_on(backend.read_commit(&first_id)).unwrap();
|
||||
assert_eq!(first_commit, commit);
|
||||
let first_git_commit = git_repo.find_commit(git_id(&first_id)).unwrap();
|
||||
assert_eq!(first_git_commit.parent_ids().collect_vec(), vec![]);
|
||||
|
@ -1210,7 +1213,7 @@ mod tests {
|
|||
// Only non-root commit as parent
|
||||
commit.parents = vec![first_id.clone()];
|
||||
let second_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let second_commit = backend.read_commit(&second_id).unwrap();
|
||||
let second_commit = futures::executor::block_on(backend.read_commit(&second_id)).unwrap();
|
||||
assert_eq!(second_commit, commit);
|
||||
let second_git_commit = git_repo.find_commit(git_id(&second_id)).unwrap();
|
||||
assert_eq!(
|
||||
|
@ -1221,7 +1224,7 @@ mod tests {
|
|||
// Merge commit
|
||||
commit.parents = vec![first_id.clone(), second_id.clone()];
|
||||
let merge_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let merge_commit = backend.read_commit(&merge_id).unwrap();
|
||||
let merge_commit = futures::executor::block_on(backend.read_commit(&merge_id)).unwrap();
|
||||
assert_eq!(merge_commit, commit);
|
||||
let merge_git_commit = git_repo.find_commit(git_id(&merge_id)).unwrap();
|
||||
assert_eq!(
|
||||
|
@ -1271,7 +1274,8 @@ mod tests {
|
|||
// When writing a tree-level conflict, the root tree on the git side has the
|
||||
// individual trees as subtrees.
|
||||
let read_commit_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let read_commit = backend.read_commit(&read_commit_id).unwrap();
|
||||
let read_commit =
|
||||
futures::executor::block_on(backend.read_commit(&read_commit_id)).unwrap();
|
||||
assert_eq!(read_commit, commit);
|
||||
let git_commit = git_repo
|
||||
.find_commit(Oid::from_bytes(read_commit_id.as_bytes()).unwrap())
|
||||
|
@ -1300,7 +1304,8 @@ mod tests {
|
|||
// regular git tree.
|
||||
commit.root_tree = MergedTreeId::resolved(create_tree(5));
|
||||
let read_commit_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let read_commit = backend.read_commit(&read_commit_id).unwrap();
|
||||
let read_commit =
|
||||
futures::executor::block_on(backend.read_commit(&read_commit_id)).unwrap();
|
||||
assert_eq!(read_commit, commit);
|
||||
let git_commit = git_repo
|
||||
.find_commit(Oid::from_bytes(read_commit_id.as_bytes()).unwrap())
|
||||
|
@ -1365,7 +1370,10 @@ mod tests {
|
|||
// committer timestamp of the commit it actually writes.
|
||||
let (commit_id2, mut actual_commit2) = backend.write_commit(commit2.clone()).unwrap();
|
||||
// The returned matches the ID
|
||||
assert_eq!(backend.read_commit(&commit_id2).unwrap(), actual_commit2);
|
||||
assert_eq!(
|
||||
futures::executor::block_on(backend.read_commit(&commit_id2)).unwrap(),
|
||||
actual_commit2
|
||||
);
|
||||
assert_ne!(commit_id2, commit_id1);
|
||||
// The committer timestamp should differ
|
||||
assert_ne!(
|
||||
|
|
|
@ -21,6 +21,7 @@ use std::fs::File;
|
|||
use std::io::{Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use blake2::{Blake2b512, Digest};
|
||||
use prost::Message;
|
||||
use tempfile::NamedTempFile;
|
||||
|
@ -114,6 +115,7 @@ impl LocalBackend {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Backend for LocalBackend {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
|
@ -143,7 +145,7 @@ impl Backend for LocalBackend {
|
|||
&self.empty_tree_id
|
||||
}
|
||||
|
||||
fn read_file(&self, _path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
async fn read_file(&self, _path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
let path = self.file_path(id);
|
||||
let file = File::open(path).map_err(|err| map_not_found_err(err, id))?;
|
||||
Ok(Box::new(zstd::Decoder::new(file).map_err(to_other_err)?))
|
||||
|
@ -171,7 +173,7 @@ impl Backend for LocalBackend {
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_symlink(&self, _path: &RepoPath, id: &SymlinkId) -> Result<String, BackendError> {
|
||||
async fn read_symlink(&self, _path: &RepoPath, id: &SymlinkId) -> Result<String, BackendError> {
|
||||
let path = self.symlink_path(id);
|
||||
let target = fs::read_to_string(path).map_err(|err| map_not_found_err(err, id))?;
|
||||
Ok(target)
|
||||
|
@ -191,7 +193,7 @@ impl Backend for LocalBackend {
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_tree(&self, _path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
async fn read_tree(&self, _path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
let path = self.tree_path(id);
|
||||
let buf = fs::read(path).map_err(|err| map_not_found_err(err, id))?;
|
||||
|
||||
|
@ -215,7 +217,7 @@ impl Backend for LocalBackend {
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_conflict(&self, _path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
|
||||
async fn read_conflict(&self, _path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
|
||||
let path = self.conflict_path(id);
|
||||
let buf = fs::read(path).map_err(|err| map_not_found_err(err, id))?;
|
||||
|
||||
|
@ -239,7 +241,7 @@ impl Backend for LocalBackend {
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
|
||||
async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
|
||||
if *id == self.root_commit_id {
|
||||
return Ok(make_root_commit(
|
||||
self.root_change_id().clone(),
|
||||
|
@ -486,25 +488,26 @@ mod tests {
|
|||
// Only root commit as parent
|
||||
commit.parents = vec![backend.root_commit_id().clone()];
|
||||
let first_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let first_commit = backend.read_commit(&first_id).unwrap();
|
||||
let first_commit = futures::executor::block_on(backend.read_commit(&first_id)).unwrap();
|
||||
assert_eq!(first_commit, commit);
|
||||
|
||||
// Only non-root commit as parent
|
||||
commit.parents = vec![first_id.clone()];
|
||||
let second_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let second_commit = backend.read_commit(&second_id).unwrap();
|
||||
let second_commit = futures::executor::block_on(backend.read_commit(&second_id)).unwrap();
|
||||
assert_eq!(second_commit, commit);
|
||||
|
||||
// Merge commit
|
||||
commit.parents = vec![first_id.clone(), second_id.clone()];
|
||||
let merge_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let merge_commit = backend.read_commit(&merge_id).unwrap();
|
||||
let merge_commit = futures::executor::block_on(backend.read_commit(&merge_id)).unwrap();
|
||||
assert_eq!(merge_commit, commit);
|
||||
|
||||
// Merge commit with root as one parent
|
||||
commit.parents = vec![first_id, backend.root_commit_id().clone()];
|
||||
let root_merge_id = backend.write_commit(commit.clone()).unwrap().0;
|
||||
let root_merge_commit = backend.read_commit(&root_merge_id).unwrap();
|
||||
let root_merge_commit =
|
||||
futures::executor::block_on(backend.read_commit(&root_merge_id)).unwrap();
|
||||
assert_eq!(root_merge_commit, commit);
|
||||
}
|
||||
|
||||
|
|
|
@ -97,18 +97,22 @@ impl Store {
|
|||
}
|
||||
|
||||
pub fn get_commit(self: &Arc<Self>, id: &CommitId) -> BackendResult<Commit> {
|
||||
let data = self.get_backend_commit(id)?;
|
||||
futures::executor::block_on(self.get_commit_async(id))
|
||||
}
|
||||
|
||||
pub async fn get_commit_async(self: &Arc<Self>, id: &CommitId) -> BackendResult<Commit> {
|
||||
let data = self.get_backend_commit(id).await?;
|
||||
Ok(Commit::new(self.clone(), id.clone(), data))
|
||||
}
|
||||
|
||||
fn get_backend_commit(&self, id: &CommitId) -> BackendResult<Arc<backend::Commit>> {
|
||||
async fn get_backend_commit(&self, id: &CommitId) -> BackendResult<Arc<backend::Commit>> {
|
||||
{
|
||||
let read_locked_cached = self.commit_cache.read().unwrap();
|
||||
if let Some(data) = read_locked_cached.get(id).cloned() {
|
||||
return Ok(data);
|
||||
}
|
||||
}
|
||||
let commit = self.backend.read_commit(id)?;
|
||||
let commit = self.backend.read_commit(id).await?;
|
||||
let data = Arc::new(commit);
|
||||
let mut write_locked_cache = self.commit_cache.write().unwrap();
|
||||
write_locked_cache.insert(id.clone(), data.clone());
|
||||
|
@ -128,11 +132,23 @@ impl Store {
|
|||
}
|
||||
|
||||
pub fn get_tree(self: &Arc<Self>, dir: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
let data = self.get_backend_tree(dir, id)?;
|
||||
futures::executor::block_on(self.get_tree_async(dir, id))
|
||||
}
|
||||
|
||||
pub async fn get_tree_async(
|
||||
self: &Arc<Self>,
|
||||
dir: &RepoPath,
|
||||
id: &TreeId,
|
||||
) -> BackendResult<Tree> {
|
||||
let data = self.get_backend_tree(dir, id).await?;
|
||||
Ok(Tree::new(self.clone(), dir.clone(), id.clone(), data))
|
||||
}
|
||||
|
||||
fn get_backend_tree(&self, dir: &RepoPath, id: &TreeId) -> BackendResult<Arc<backend::Tree>> {
|
||||
async fn get_backend_tree(
|
||||
&self,
|
||||
dir: &RepoPath,
|
||||
id: &TreeId,
|
||||
) -> BackendResult<Arc<backend::Tree>> {
|
||||
let key = (dir.clone(), id.clone());
|
||||
{
|
||||
let read_locked_cache = self.tree_cache.read().unwrap();
|
||||
|
@ -140,7 +156,8 @@ impl Store {
|
|||
return Ok(data);
|
||||
}
|
||||
}
|
||||
let data = Arc::new(self.backend.read_tree(dir, id)?);
|
||||
let data = self.backend.read_tree(dir, id).await?;
|
||||
let data = Arc::new(data);
|
||||
let mut write_locked_cache = self.tree_cache.write().unwrap();
|
||||
write_locked_cache.insert(key, data.clone());
|
||||
Ok(data)
|
||||
|
@ -175,7 +192,15 @@ impl Store {
|
|||
}
|
||||
|
||||
pub fn read_file(&self, path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
self.backend.read_file(path, id)
|
||||
futures::executor::block_on(self.read_file_async(path, id))
|
||||
}
|
||||
|
||||
pub async fn read_file_async(
|
||||
&self,
|
||||
path: &RepoPath,
|
||||
id: &FileId,
|
||||
) -> BackendResult<Box<dyn Read>> {
|
||||
self.backend.read_file(path, id).await
|
||||
}
|
||||
|
||||
pub fn write_file(&self, path: &RepoPath, contents: &mut dyn Read) -> BackendResult<FileId> {
|
||||
|
@ -183,7 +208,15 @@ impl Store {
|
|||
}
|
||||
|
||||
pub fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> BackendResult<String> {
|
||||
self.backend.read_symlink(path, id)
|
||||
futures::executor::block_on(self.read_symlink_async(path, id))
|
||||
}
|
||||
|
||||
pub async fn read_symlink_async(
|
||||
&self,
|
||||
path: &RepoPath,
|
||||
id: &SymlinkId,
|
||||
) -> BackendResult<String> {
|
||||
self.backend.read_symlink(path, id).await
|
||||
}
|
||||
|
||||
pub fn write_symlink(&self, path: &RepoPath, contents: &str) -> BackendResult<SymlinkId> {
|
||||
|
@ -195,7 +228,7 @@ impl Store {
|
|||
path: &RepoPath,
|
||||
id: &ConflictId,
|
||||
) -> BackendResult<Merge<Option<TreeValue>>> {
|
||||
let backend_conflict = self.backend.read_conflict(path, id)?;
|
||||
let backend_conflict = futures::executor::block_on(self.backend.read_conflict(path, id))?;
|
||||
Ok(Merge::from_backend_conflict(backend_conflict))
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ readme = { workspace = true }
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
config = { workspace = true }
|
||||
git2 = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
|
|
|
@ -19,6 +19,7 @@ use std::io::{Cursor, Read};
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use jj_lib::backend::{
|
||||
make_root_commit, Backend, BackendError, BackendResult, ChangeId, Commit, CommitId, Conflict,
|
||||
ConflictId, FileId, ObjectId, SymlinkId, Tree, TreeId,
|
||||
|
@ -107,6 +108,7 @@ impl Debug for TestBackend {
|
|||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Backend for TestBackend {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
|
@ -136,7 +138,7 @@ impl Backend for TestBackend {
|
|||
&self.empty_tree_id
|
||||
}
|
||||
|
||||
fn read_file(&self, path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
async fn read_file(&self, path: &RepoPath, id: &FileId) -> BackendResult<Box<dyn Read>> {
|
||||
match self
|
||||
.locked_data()
|
||||
.files
|
||||
|
@ -165,7 +167,7 @@ impl Backend for TestBackend {
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> Result<String, BackendError> {
|
||||
async fn read_symlink(&self, path: &RepoPath, id: &SymlinkId) -> Result<String, BackendError> {
|
||||
match self
|
||||
.locked_data()
|
||||
.symlinks
|
||||
|
@ -192,7 +194,7 @@ impl Backend for TestBackend {
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
async fn read_tree(&self, path: &RepoPath, id: &TreeId) -> BackendResult<Tree> {
|
||||
if id == &self.empty_tree_id {
|
||||
return Ok(Tree::default());
|
||||
}
|
||||
|
@ -222,7 +224,7 @@ impl Backend for TestBackend {
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_conflict(&self, path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
|
||||
async fn read_conflict(&self, path: &RepoPath, id: &ConflictId) -> BackendResult<Conflict> {
|
||||
match self
|
||||
.locked_data()
|
||||
.conflicts
|
||||
|
@ -249,7 +251,7 @@ impl Backend for TestBackend {
|
|||
Ok(id)
|
||||
}
|
||||
|
||||
fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
|
||||
async fn read_commit(&self, id: &CommitId) -> BackendResult<Commit> {
|
||||
if id == &self.root_commit_id {
|
||||
return Ok(make_root_commit(
|
||||
self.root_change_id.clone(),
|
||||
|
|
Loading…
Reference in a new issue