rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 heartwood27ecad0f1c16f2414a577af624bb90c624e7fa2e
{
"request": "trigger",
"version": 1,
"event_type": "patch",
"repository": {
"id": "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5",
"name": "heartwood",
"description": "Radicle Heartwood Protocol & Stack",
"private": false,
"default_branch": "master",
"delegates": [
"did:key:z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT",
"did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"did:key:z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz"
]
},
"action": "Created",
"patch": {
"id": "6e8552ea74da8b66f7b5c3718ddc4f56fad87f8f",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"title": "Improve Upload Experience: Part II",
"state": {
"status": "merged",
"conflicts": []
},
"before": "d839418c56560a768455fbcdac50fce4732623eb",
"after": "27ecad0f1c16f2414a577af624bb90c624e7fa2e",
"commits": [
"27ecad0f1c16f2414a577af624bb90c624e7fa2e",
"01eed26cc2144f9eae04e98f1b4a5c5eb582f438",
"0f6328a547eee377810f560a15ec5b2d22d2f77a"
],
"target": "3fc92f83fe089263c470b328414d923227cf0e4a",
"labels": [],
"assignees": [],
"revisions": [
{
"id": "6e8552ea74da8b66f7b5c3718ddc4f56fad87f8f",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Further improvements to the upload experience. See commits inlined below.\n\n### cli: upload-pack events and spinners\n\nIntroduce a set of types for initialising and updating a set of spinners for\nupload-pack events. The spinners react to the events that are emitted from the\nupload-pack process in the worker.\n\nEach set of spinners are for a given `remote` since the protocol may interact\nwith multiple remotes at the same time when announcing.\n\nThe spinners are lazy when first constructed and are only constructed once an\nupload-pack event is provided for a given remote.\n\nThe events expected for the spinners are: enumerating objects, counting objects,\ncompressing objects, and the throughput of sending the bytes to the receiving\nend.\n\n### node: simplify upload-pack reader joining\n\nThe `join` method is equivalent to the custom loop that was written for joining\nthe `reader` thread in upload-pack.\n\n### node: reduce default upload-pack timeout\n\nThe default upload-pack timeout can be reduced by noticing the fact that if the\nstdout stream is still sending data, then the reader thread can stay alive.\n\nThis is achieved by keeping a timer that gets set everytime the upload-pack\nwriter sends data. If the reader then runs into a timeout, it can check if this\ntimer has elapsed the timeout as well, otherwise it can loop back and read\nagain. This will either lead to waiting long enough that the EOF message is\nreceived, or both streams timeout.\n\nFrom empirical testing, this can result in the timeout being exceeded but the\nEOF message not being received, if the pack data is large enough that the\nreceiving side takes more time to write it to disk. However, this means that the\nupload should, in theory, have completed successfully either way.\n\nThe `--timeout` option is also used in the `git-upload-pack` process for good\nmeasure.",
"base": "85aeb2e0052ee25d565265665f0d86eafb15cd7e",
"oid": "531305421ca05223835e2be91d81b544dfda6444",
"timestamp": 1714575152
},
{
"id": "a7717f12450ba3e1c5ae8ee598ad974b698c8fa2",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- rebased\n- handle errors in the upload pack spinners",
"base": "6dd52c94fb1472e251110e439fe656aaed74019f",
"oid": "e9dd20a82362f94d58af4b56d179fa2da80e774b",
"timestamp": 1714985068
},
{
"id": "00d7162db2943f1990bd3d87333c303d4d83f131",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- rebased",
"base": "6ab3bfcba0577fabdcb84498441c6605391290f4",
"oid": "0efa771351088ead070d8f5e5d69c69f66c0b60f",
"timestamp": 1715094992
},
{
"id": "5c1d22f89734ab4c774432ebaadcfddc4db681c7",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- rebased on 55e905613ddf81fb04c69ab6fa6f4fd99622d789",
"base": "25c6660a59c621de1ec406e4f9b2ccfbd21e1b90",
"oid": "8c296a5513435f47add2fc8fb60dd1746461362c",
"timestamp": 1715701029
},
{
"id": "ee7c137da511354fc94272a179208caf81427300",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- rebased on 3b342ab385810fd3e67ec1fb05f45b27ba50bc36",
"base": "3b342ab385810fd3e67ec1fb05f45b27ba50bc36",
"oid": "428515a43d054cdc900440873596b992247b1e6b",
"timestamp": 1715770188
},
{
"id": "a3e8270eeffb9a1c11a8b6216189128dc875e982",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Remove extra spinners since they didn't work as the author expected :(\n- Simplify the terminal output to display the number of remotes being uploaded\n to and the transmitted bytes/throughput, as well as when an upload is finished\n for a peer.",
"base": "cb2cbf014a123a1cd0cd0f188f093e014f93647b",
"oid": "6c3d987de9800ee6392138fb5de11a56aa0ecbdf",
"timestamp": 1715861565
},
{
"id": "6997e3a6a3cd20d3dffb320e5fe79749d81f28c3",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Changes:\n- Removed inner timeout semantics between upload-pack threads",
"base": "d839418c56560a768455fbcdac50fce4732623eb",
"oid": "27ecad0f1c16f2414a577af624bb90c624e7fa2e",
"timestamp": 1715958149
},
{
"id": "712829e6545201aa9c93d365b158c95272df1833",
"author": {
"id": "did:key:z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM",
"alias": null
},
"description": "Rebase, small fix.",
"base": "3fe6bbe1cfe220295c3cb376b504089af4715f31",
"oid": "064ece32ac0a0bd0efe4f459dcb0462bafc236e6",
"timestamp": 1715972772
}
]
}
}
{
"response": "triggered",
"run_id": {
"id": "f6679a68-8675-4d2e-b9e8-f24c6ca437d0"
},
"info_url": "https://cci.rad.levitte.org//f6679a68-8675-4d2e-b9e8-f24c6ca437d0.html"
}
Started at: 2026-09-07 22:53:32.643819+02:00
Commands:
$ rad clone rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5 .
✓ Creating checkout in ./...
✓ Remote cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT added
✓ Remote-tracking branch cloudhead@z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT/master created for z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
✓ Remote fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM added
✓ Remote-tracking branch fintohaps@z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM/master created for z6MkireRatUThvd3qzfKht1S44wpm4FEWSSa4PRMTSQZ3voM
✓ Remote lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz added
✓ Remote-tracking branch lorenz@z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz/master created for z6MkkPvBfjP4bQmco5Dm7UGsX2ruDBieEHi8n9DVJWX5sTEz
✓ Repository successfully cloned under /opt/radcis/ci.rad.levitte.org/cci/state/f6679a68-8675-4d2e-b9e8-f24c6ca437d0/w/
╭────────────────────────────────────╮
│ heartwood │
│ Radicle Heartwood Protocol & Stack │
│ 189 issues · 38 patches │
╰────────────────────────────────────╯
Run `cd ./.` to go to the repository directory.
Exit code: 0
$ rad patch checkout 6e8552ea74da8b66f7b5c3718ddc4f56fad87f8f
✓ Switched to branch patch/6e8552e at revision 6997e3a
✓ Branch patch/6e8552e setup to track rad/patches/6e8552ea74da8b66f7b5c3718ddc4f56fad87f8f
Exit code: 0
$ git config advice.detachedHead false
Exit code: 0
$ git checkout 27ecad0f1c16f2414a577af624bb90c624e7fa2e
HEAD is now at 27ecad0f node: reduce default upload-pack timeout
Exit code: 0
$ rad patch show 6e8552ea74da8b66f7b5c3718ddc4f56fad87f8f -p
╭──────────────────────────────────────────────────────────────────────────────────╮
│ Title Improve Upload Experience: Part II │
│ Patch 6e8552ea74da8b66f7b5c3718ddc4f56fad87f8f │
│ Author fintohaps z6Mkire…SQZ3voM │
│ Head 27ecad0f1c16f2414a577af624bb90c624e7fa2e │
│ Base d839418c56560a768455fbcdac50fce4732623eb │
│ Target master │
│ Branches patch/6e8552e │
│ Commits ahead 3, behind 1341 │
│ Status merged │
│ │
│ Further improvements to the upload experience. See commits inlined below. │
│ │
│ ### cli: upload-pack events and spinners │
│ │
│ Introduce a set of types for initialising and updating a set of spinners for │
│ upload-pack events. The spinners react to the events that are emitted from the │
│ upload-pack process in the worker. │
│ │
│ Each set of spinners are for a given `remote` since the protocol may interact │
│ with multiple remotes at the same time when announcing. │
│ │
│ The spinners are lazy when first constructed and are only constructed once an │
│ upload-pack event is provided for a given remote. │
│ │
│ The events expected for the spinners are: enumerating objects, counting objects, │
│ compressing objects, and the throughput of sending the bytes to the receiving │
│ end. │
│ │
│ ### node: simplify upload-pack reader joining │
│ │
│ The `join` method is equivalent to the custom loop that was written for joining │
│ the `reader` thread in upload-pack. │
│ │
│ ### node: reduce default upload-pack timeout │
│ │
│ The default upload-pack timeout can be reduced by noticing the fact that if the │
│ stdout stream is still sending data, then the reader thread can stay alive. │
│ │
│ This is achieved by keeping a timer that gets set everytime the upload-pack │
│ writer sends data. If the reader then runs into a timeout, it can check if this │
│ timer has elapsed the timeout as well, otherwise it can loop back and read │
│ again. This will either lead to waiting long enough that the EOF message is │
│ received, or both streams timeout. │
│ │
│ From empirical testing, this can result in the timeout being exceeded but the │
│ EOF message not being received, if the pack data is large enough that the │
│ receiving side takes more time to write it to disk. However, this means that the │
│ upload should, in theory, have completed successfully either way. │
│ │
│ The `--timeout` option is also used in the `git-upload-pack` process for good │
│ measure. │
├──────────────────────────────────────────────────────────────────────────────────┤
│ 27ecad0 node: reduce default upload-pack timeout │
│ 01eed26 node: simplify upload-pack reader joining │
│ 0f6328a cli: upload-pack events │
├──────────────────────────────────────────────────────────────────────────────────┤
│ ● Revision 6e8552e @ 85aeb2e..5313054 by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision a7717f1 @ 6dd52c9..e9dd20a by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision 00d7162 @ 6ab3bfc..0efa771 by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision 5c1d22f @ 25c6660..8c296a5 by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision ee7c137 @ 3b342ab..428515a by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision a3e8270 @ cb2cbf0..6c3d987 by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision 6997e3a @ d839418..27ecad0 by fintohaps z6Mkire…SQZ3voM 2 years ago │
│ ↑ Revision 712829e @ 3fe6bbe..064ece3 by cloudhead z6MksFq…bS9wzpT 2 years ago │
│ └─ ✓ merged by cloudhead z6MksFq…bS9wzpT │
╰──────────────────────────────────────────────────────────────────────────────────╯
commit 27ecad0f1c16f2414a577af624bb90c624e7fa2e
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Wed May 1 12:56:24 2024 +0100
node: reduce default upload-pack timeout
From empirical testing, the `FETCH_TIMEOUT` can be set to a lower value of 3s.
For a repository that is about 300MiB, the process will successfully send all
packfile bytes and the references will be written.
The `--timeout` option is also used in the `git-upload-pack` process for good
measure.
Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
diff --git a/radicle-node/src/service.rs b/radicle-node/src/service.rs
index 9be8c60f6..c6e693e06 100644
--- a/radicle-node/src/service.rs
+++ b/radicle-node/src/service.rs
@@ -98,8 +98,8 @@ pub const MIN_RECONNECTION_DELTA: LocalDuration = LocalDuration::from_secs(3);
pub const MAX_RECONNECTION_DELTA: LocalDuration = LocalDuration::from_mins(60);
/// Connection retry delta used for ephemeral peers that failed to connect previously.
pub const CONNECTION_RETRY_DELTA: LocalDuration = LocalDuration::from_mins(10);
-/// How long to wait for a fetch to stall before aborting, default is 1 hour.
-pub const FETCH_TIMEOUT: time::Duration = time::Duration::from_secs(3600);
+/// How long to wait for a fetch to stall before aborting, default is 3s.
+pub const FETCH_TIMEOUT: time::Duration = time::Duration::from_secs(3);
/// Maximum external address limit imposed by message size limits.
pub use message::ADDRESS_LIMIT;
diff --git a/radicle-node/src/worker.rs b/radicle-node/src/worker.rs
index f2340fa88..eff250f99 100644
--- a/radicle-node/src/worker.rs
+++ b/radicle-node/src/worker.rs
@@ -238,6 +238,7 @@ impl Worker {
FetchRequest::Responder { remote, emitter } => {
log::debug!(target: "worker", "Worker processing incoming fetch for {remote} on stream {stream}..");
+ let timeout = channels.timeout();
let (mut stream_r, stream_w) = channels.split();
let header = match upload_pack::pktline::git_request(&mut stream_r) {
Ok(header) => header,
@@ -265,6 +266,7 @@ impl Worker {
&header,
stream_r,
stream_w,
+ timeout,
)
.map(|_| ())
.map_err(|e| e.into());
diff --git a/radicle-node/src/worker/channels.rs b/radicle-node/src/worker/channels.rs
index a48fbb815..01cab9f8d 100644
--- a/radicle-node/src/worker/channels.rs
+++ b/radicle-node/src/worker/channels.rs
@@ -34,6 +34,10 @@ impl ChannelsFlush {
pub fn split(&mut self) -> (&mut ChannelReader, &mut ChannelFlushWriter) {
(&mut self.receiver, &mut self.sender)
}
+
+ pub fn timeout(&self) -> time::Duration {
+ self.sender.writer.timeout.max(self.receiver.timeout)
+ }
}
impl radicle_fetch::transport::ConnectionStream for ChannelsFlush {
diff --git a/radicle-node/src/worker/upload_pack.rs b/radicle-node/src/worker/upload_pack.rs
index 4d60e43c4..5a4ce3ca6 100644
--- a/radicle-node/src/worker/upload_pack.rs
+++ b/radicle-node/src/worker/upload_pack.rs
@@ -1,7 +1,7 @@
use std::io;
use std::io::Write;
use std::process::{Command, ExitStatus, Stdio};
-use std::time::Instant;
+use std::time::{Duration, Instant};
use gix_protocol::transport::bstr::ByteSlice;
use radicle::identity::RepoId;
@@ -27,6 +27,7 @@ pub fn upload_pack<R, W>(
header: &pktline::GitRequest,
mut recv: R,
send: W,
+ timeout: Duration,
) -> io::Result<ExitStatus>
where
R: io::Read + Send,
@@ -72,6 +73,7 @@ where
"lsrefs.unborn=ignore",
"upload-pack",
"--strict",
+ format!("--timeout={}", timeout.as_secs()).as_str(),
".",
])
.stdout(Stdio::piped())
@@ -113,6 +115,12 @@ where
log::debug!(target: "worker", "Exiting upload-pack reader thread for {}", header.repo);
break;
}
+ // N.b. if the read timed out, ensure that the sender isn't
+ // still sending messages.
+ Err(e) if e.kind() == io::ErrorKind::TimedOut => {
+ log::warn!(target: "worker", "Read channel timed out for upload-pack {}", header.repo);
+ break;
+ }
Err(e) => {
log::error!(target: "worker", "Error on upload-pack channel read for {}: {e}", header.repo);
emitter.emit(events::UploadPack::error(header.repo, remote, e).into());
@@ -167,7 +175,7 @@ impl<W> Reporter<W> {
}
};
log::trace!(target: "worker", "upload-pack progress: {event:?}");
- self.emitter.emit(event.into())
+ self.emitter.emit(event.into());
}
fn as_upload_pack_progress(buf: &[u8]) -> Option<events::upload_pack::Progress> {
commit 01eed26cc2144f9eae04e98f1b4a5c5eb582f438
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Wed May 1 08:53:53 2024 +0100
node: simplify upload-pack reader joining
The `join` method is equivalent to the custom loop that was written for joining
the `reader` thread in upload-pack.
Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
diff --git a/radicle-node/src/worker/upload_pack.rs b/radicle-node/src/worker/upload_pack.rs
index d448bd8cd..4d60e43c4 100644
--- a/radicle-node/src/worker/upload_pack.rs
+++ b/radicle-node/src/worker/upload_pack.rs
@@ -124,14 +124,10 @@ where
// N.b. we only care if the `reader` is finished. We then kill
// the child which will end the thread for the sender.
- loop {
- if reader.is_finished() {
- child.kill()?;
- break;
- } else {
- std::thread::sleep(std::time::Duration::from_millis(100));
- }
+ if let Err(e) = reader.join() {
+ log::warn!(target: "worker", "Upload pack thread panicked: {e:?}");
}
+ child.kill()?;
Ok::<_, io::Error>(())
})?;
commit 0f6328a547eee377810f560a15ec5b2d22d2f77a
Author: Fintan Halpenny <fintan.halpenny@gmail.com>
Date: Mon Apr 29 12:19:08 2024 +0100
cli: upload-pack events
Introduce an `UploadPack` type for keeping track of relevant data from
upload-pack events and displaying progress to the terminal.
It keeps track of the number of remotes that are being uploaded to as well as
the throughput of transmitted data.
The `Progress` events are logged rather than being displayed since they don't
provide any useful details to the user.
Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com>
X-Clacks-Overhead: GNU Terry Pratchett
diff --git a/radicle-cli/src/commands/init.rs b/radicle-cli/src/commands/init.rs
index f6b8cc662..2c8e3918d 100644
--- a/radicle-cli/src/commands/init.rs
+++ b/radicle-cli/src/commands/init.rs
@@ -395,6 +395,8 @@ fn sync(
spinner.message("Syncing..");
let mut replicas = HashSet::new();
+ // Start upload pack as None and set it if we encounter an event
+ let mut upload_pack = term::upload_pack::UploadPack::new();
for e in events {
match e {
@@ -413,21 +415,27 @@ fn sync(
remote,
progress,
})) if rid == rid_ => {
- spinner.message(format!("Uploading {rid} to {remote} {progress}"));
+ log::debug!("Upload progress for {remote}: {progress}");
}
+ Ok(Event::UploadPack(UploadPack::PackProgress {
+ rid: rid_,
+ remote,
+ transmitted,
+ })) if rid == rid_ => spinner.message(upload_pack.transmitted(remote, transmitted)),
Ok(Event::UploadPack(UploadPack::Done {
rid: rid_,
remote,
status,
})) if rid == rid_ => {
- spinner.message(format!("Upload done for {rid} to {remote}: {status}"));
+ log::debug!("Upload done for {rid} to {remote} with status: {status}");
+ spinner.message(upload_pack.done(&remote));
}
Ok(Event::UploadPack(UploadPack::Error {
rid: rid_,
remote,
err,
})) if rid == rid_ => {
- spinner.message(format!("Upload error for {rid} to {remote}: {err}"));
+ term::warning(format!("Upload error for {rid} to {remote}: {err}"));
}
Ok(_) => {
// Some other irrelevant event received.
diff --git a/radicle-cli/src/terminal.rs b/radicle-cli/src/terminal.rs
index a708e0025..5e351ec33 100644
--- a/radicle-cli/src/terminal.rs
+++ b/radicle-cli/src/terminal.rs
@@ -8,6 +8,7 @@ pub mod highlight;
pub mod issue;
pub mod json;
pub mod patch;
+pub mod upload_pack;
use std::ffi::OsString;
use std::process;
diff --git a/radicle-cli/src/terminal/format.rs b/radicle-cli/src/terminal/format.rs
index 746677199..a8bf16905 100644
--- a/radicle-cli/src/terminal/format.rs
+++ b/radicle-cli/src/terminal/format.rs
@@ -82,6 +82,22 @@ pub fn timestamp(time: impl Into<LocalTime>) -> Paint<String> {
Paint::new(fmt.convert(duration.into()))
}
+pub fn bytes(size: usize) -> Paint<String> {
+ const KB: usize = 1024;
+ const MB: usize = 1024usize.pow(2);
+ const GB: usize = 1024usize.pow(3);
+ let size = if size < KB {
+ format!("{size} B")
+ } else if size < MB {
+ format!("{} KiB", size / KB)
+ } else if size < GB {
+ format!("{} MiB", size / MB)
+ } else {
+ format!("{} GiB", size / GB)
+ };
+ Paint::new(size)
+}
+
/// Format a ref update.
pub fn ref_update(update: &RefUpdate) -> Paint<&'static str> {
match update {
@@ -358,4 +374,14 @@ mod test {
let res = strip_comments(test);
assert_eq!(exp, res);
}
+
+ #[test]
+ fn test_bytes() {
+ assert_eq!(bytes(1023).to_string(), "1023 B");
+ assert_eq!(bytes(1024).to_string(), "1 KiB");
+ assert_eq!(bytes(1024 * 9).to_string(), "9 KiB");
+ assert_eq!(bytes(1024usize.pow(2)).to_string(), "1 MiB");
+ assert_eq!(bytes(1024usize.pow(2) * 56).to_string(), "56 MiB");
+ assert_eq!(bytes(1024usize.pow(3) * 1024).to_string(), "1024 GiB");
+ }
}
diff --git a/radicle-cli/src/terminal/upload_pack.rs b/radicle-cli/src/terminal/upload_pack.rs
new file mode 100644
index 000000000..99129721b
--- /dev/null
+++ b/radicle-cli/src/terminal/upload_pack.rs
@@ -0,0 +1,50 @@
+use std::collections::BTreeSet;
+use std::time::Instant;
+
+use crate::terminal::format;
+use radicle::node::NodeId;
+
+/// Keeps track of upload-pack progress for displaying to the terminal.
+pub struct UploadPack {
+ /// Keep track of which remotes are being uploaded to, removing any that
+ /// have completed.
+ remotes: BTreeSet<NodeId>,
+ /// Keep track of how long we've been transmitting for to calculate
+ /// throughput.
+ timer: Instant,
+}
+
+impl Default for UploadPack {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl UploadPack {
+ /// Construct an empty set of spinners.
+ pub fn new() -> Self {
+ Self {
+ remotes: BTreeSet::new(),
+ timer: Instant::now(),
+ }
+ }
+
+ /// Display the number of peers, the total transmitted bytes, and the
+ /// throughput.
+ pub fn transmitted(&mut self, remote: NodeId, transmitted: usize) -> String {
+ self.remotes.insert(remote);
+ let throughput = transmitted as f64 / self.timer.elapsed().as_secs_f64();
+ let throughput = format::bytes(throughput.floor() as usize);
+ let n = self.remotes.len();
+ let transmitted = format::bytes(transmitted);
+ format!("Uploading to {n} peers ({transmitted} | {throughput:.2}/s)")
+ }
+
+ /// Display which remote has completed upload-pack and how many are
+ /// remaining.
+ pub fn done(&mut self, remote: &NodeId) -> String {
+ self.remotes.remove(remote);
+ let n = self.remotes.len();
+ format!("Uploaded to {remote}, {n} peers remaining..")
+ }
+}
diff --git a/radicle-node/src/worker/upload_pack.rs b/radicle-node/src/worker/upload_pack.rs
index 731b278da..d448bd8cd 100644
--- a/radicle-node/src/worker/upload_pack.rs
+++ b/radicle-node/src/worker/upload_pack.rs
@@ -83,12 +83,8 @@ where
let mut stdin = child.stdin.take().unwrap();
let mut stdout = io::BufReader::new(child.stdout.take().unwrap());
- let mut reporter = Reporter {
- rid: header.repo,
- remote,
- emitter: emitter.clone(),
- send,
- };
+ let mut reporter = Reporter::new(header.repo, remote, emitter.clone(), send);
+
thread::scope(|s| {
thread::spawn_scoped(nid, "upload-pack", s, || {
// N.b. we indefinitely copy stdout to the sender,
@@ -152,17 +148,32 @@ struct Reporter<W> {
remote: NodeId,
emitter: Emitter<Event>,
send: W,
+ total: usize,
}
impl<W> Reporter<W> {
- fn emit(&self, buf: &[u8]) {
- if let Some(progress) = Self::as_upload_pack_progress(buf) {
- log::trace!(target: "worker", "upload-pack progress: {progress}");
- self.emitter
- .emit(events::UploadPack::write(self.rid, self.remote, progress).into());
+ fn new(rid: RepoId, remote: NodeId, emitter: Emitter<Event>, send: W) -> Self {
+ Self {
+ rid,
+ remote,
+ emitter,
+ send,
+ total: 0,
}
}
+ fn emit(&mut self, buf: &[u8]) {
+ let event = match Self::as_upload_pack_progress(buf) {
+ Some(progress) => events::UploadPack::write(self.rid, self.remote, progress),
+ None => {
+ self.total += buf.len();
+ events::UploadPack::pack_progress(self.rid, self.remote, self.total)
+ }
+ };
+ log::trace!(target: "worker", "upload-pack progress: {event:?}");
+ self.emitter.emit(event.into())
+ }
+
fn as_upload_pack_progress(buf: &[u8]) -> Option<events::upload_pack::Progress> {
use events::upload_pack::Progress::*;
let gix_protocol::RemoteProgress {
diff --git a/radicle/src/node/events/upload_pack.rs b/radicle/src/node/events/upload_pack.rs
index 284947fe1..c9d162754 100644
--- a/radicle/src/node/events/upload_pack.rs
+++ b/radicle/src/node/events/upload_pack.rs
@@ -29,6 +29,7 @@ pub enum UploadPack {
/// The progress metadata of the upload-pack.
progress: Progress,
},
+ /// An error occurred during the upload-pack process.
Error {
/// The repository being fetched.
rid: RepoId,
@@ -37,9 +38,27 @@ pub enum UploadPack {
/// The error that occurred during the upload-pack.
err: String,
},
+ /// The upload-pack packfile transmission progress.
+ PackProgress {
+ /// The repository being fetched.
+ rid: RepoId,
+ /// The node being fetched from.
+ remote: NodeId,
+ /// The total number of bytes transmitted.
+ transmitted: usize,
+ },
}
impl UploadPack {
+ /// Construct a `UploadPack::PackProgress` event.
+ pub fn pack_progress(rid: RepoId, remote: NodeId, transmitted: usize) -> Self {
+ Self::PackProgress {
+ rid,
+ remote,
+ transmitted,
+ }
+ }
+
/// Construct a `UploadPack::Write` event.
pub fn write(rid: RepoId, remote: NodeId, progress: Progress) -> Self {
Self::Write {
Exit code: 0
shell: 'cargo --version rustc --version cargo fmt --check cargo clippy --all-targets --workspace -- --deny clippy::all cargo build --all-targets --workspace cargo doc --workspace cargo test --workspace --no-fail-fast '
Commands:
$ podman run --name f6679a68-8675-4d2e-b9e8-f24c6ca437d0 -v /opt/radcis/ci.rad.levitte.org/cci/state/f6679a68-8675-4d2e-b9e8-f24c6ca437d0/s:/f6679a68-8675-4d2e-b9e8-f24c6ca437d0/s:ro -v /opt/radcis/ci.rad.levitte.org/cci/state/f6679a68-8675-4d2e-b9e8-f24c6ca437d0/w:/f6679a68-8675-4d2e-b9e8-f24c6ca437d0/w -w /f6679a68-8675-4d2e-b9e8-f24c6ca437d0/w -v /opt/radcis/ci.rad.levitte.org/.radicle:/${id}/.radicle:ro -e RAD_HOME=/${id}/.radicle rust:trixie bash /f6679a68-8675-4d2e-b9e8-f24c6ca437d0/s/script.sh
+ cargo --version
info: syncing channel updates for '1.77-x86_64-unknown-linux-gnu'
info: latest update on 2024-04-09, rust version 1.77.2 (25ef9e3d8 2024-04-09)
info: downloading component 'cargo'
info: downloading component 'rust-std'
info: downloading component 'rustc'
info: installing component 'cargo'
info: installing component 'rust-std'
info: installing component 'rustc'
cargo 1.77.2 (e52e36006 2024-03-26)
+ rustc --version
rustc 1.77.2 (25ef9e3d8 2024-04-09)
+ cargo fmt --check
error: 'cargo-fmt' is not installed for the toolchain '1.77-x86_64-unknown-linux-gnu'.
To install, run `rustup component add --toolchain 1.77-x86_64-unknown-linux-gnu rustfmt`
Exit code: 1
{
"response": "finished",
"result": "failure"
}