node: Refactor fetch response code
Only user-requested fetches require a response. Signed-off-by: Alexis Sellier <self@cloudhead.io>
This commit is contained in:
parent
e6f56ff3bc
commit
ec2e7d7ee1
|
|
@ -13,7 +13,6 @@ use crate::client;
|
||||||
use crate::identity::Id;
|
use crate::identity::Id;
|
||||||
use crate::node;
|
use crate::node;
|
||||||
use crate::service::FetchLookup;
|
use crate::service::FetchLookup;
|
||||||
use crate::service::FetchResult;
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
|
@ -242,19 +241,19 @@ fn fetch<W: Write, H: Handle<Error = client::handle::Error, FetchLookup = FetchL
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for result in results.iter() {
|
for result in results.iter() {
|
||||||
match result {
|
match result.result {
|
||||||
FetchResult::Fetched { from, updated } => {
|
Ok(updated) => {
|
||||||
writeln!(writer, "ok: {} fetched from {}", &id, from)?;
|
writeln!(writer, "ok: {} fetched from {}", &id, result.remote)?;
|
||||||
|
|
||||||
for update in updated {
|
for update in updated {
|
||||||
writeln!(writer, "{}", update)?;
|
writeln!(writer, "{}", update)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FetchResult::Error { from, error } => {
|
Err(err) => {
|
||||||
writeln!(
|
writeln!(
|
||||||
writer,
|
writer,
|
||||||
"error: {} failed to fetch from {}: {}",
|
"error: {} failed to fetch from {}: {}",
|
||||||
&id, from, error
|
&id, result.remote, err
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -131,24 +131,10 @@ pub enum FetchLookup {
|
||||||
/// Result of a fetch request from a specific seed.
|
/// Result of a fetch request from a specific seed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
pub enum FetchResult {
|
pub struct FetchResult {
|
||||||
/// Successful fetch from a seed.
|
pub rid: Id,
|
||||||
Fetched {
|
pub remote: NodeId,
|
||||||
from: NodeId,
|
pub result: Result<Vec<RefUpdate>, FetchError>,
|
||||||
updated: Vec<RefUpdate>,
|
|
||||||
},
|
|
||||||
/// Error fetching the resource from a seed.
|
|
||||||
Error { from: NodeId, error: FetchError },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FetchResult {
|
|
||||||
/// Get the remote node id.
|
|
||||||
pub fn remote(&self) -> &NodeId {
|
|
||||||
match self {
|
|
||||||
Self::Fetched { from, .. } => from,
|
|
||||||
Self::Error { from, .. } => from,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function used to query internal service state.
|
/// Function used to query internal service state.
|
||||||
|
|
@ -226,6 +212,8 @@ pub struct Service<R, A, S, G> {
|
||||||
rng: Rng,
|
rng: Rng,
|
||||||
/// Whether our local inventory no long represents what we have announced to the network.
|
/// Whether our local inventory no long represents what we have announced to the network.
|
||||||
out_of_sync: bool,
|
out_of_sync: bool,
|
||||||
|
/// Fetch requests initiated by user, which are waiting for results.
|
||||||
|
fetch_reqs: HashMap<Id, chan::Sender<FetchResult>>,
|
||||||
/// Current tracked repository bloom filter.
|
/// Current tracked repository bloom filter.
|
||||||
filter: Filter,
|
filter: Filter,
|
||||||
/// Last time the service was idle.
|
/// Last time the service was idle.
|
||||||
|
|
@ -289,6 +277,7 @@ where
|
||||||
reactor: Reactor::default(),
|
reactor: Reactor::default(),
|
||||||
sessions,
|
sessions,
|
||||||
out_of_sync: false,
|
out_of_sync: false,
|
||||||
|
fetch_reqs: HashMap::new(),
|
||||||
filter: Filter::empty(),
|
filter: Filter::empty(),
|
||||||
last_idle: LocalTime::default(),
|
last_idle: LocalTime::default(),
|
||||||
last_sync: LocalTime::default(),
|
last_sync: LocalTime::default(),
|
||||||
|
|
@ -456,8 +445,14 @@ where
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Ok(seeds) = self.routing.get(&id) else {
|
let seeds = match self.routing.get(&id) {
|
||||||
todo!();
|
Ok(seeds) => seeds,
|
||||||
|
Err(err) => {
|
||||||
|
log::error!("Error reading routing table for {id}: {err}");
|
||||||
|
resp.send(FetchLookup::NotFound).ok();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let Some(seeds) = NonEmpty::from_vec(seeds.into_iter().collect()) else {
|
let Some(seeds) = NonEmpty::from_vec(seeds.into_iter().collect()) else {
|
||||||
log::warn!("No seeds found for {}", id);
|
log::warn!("No seeds found for {}", id);
|
||||||
|
|
@ -474,17 +469,12 @@ where
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
|
self.fetch_reqs.insert(id, results_send);
|
||||||
|
|
||||||
// TODO: Limit the number of seeds we fetch from? Randomize?
|
// TODO: Limit the number of seeds we fetch from? Randomize?
|
||||||
for seed in seeds {
|
for seed in seeds {
|
||||||
let session = self.sessions.get_mut(&seed).unwrap();
|
if let Err(err) = self.fetch(id, seed) {
|
||||||
if let Some(fetch) = session.fetch(id, results_send.clone()) {
|
log::error!("Error initiating fetch for {id} from {seed}: {err}");
|
||||||
self.reactor.write(session.id, fetch);
|
|
||||||
self.reactor
|
|
||||||
.fetch(session.id, id, Namespaces::default(), true);
|
|
||||||
} else {
|
|
||||||
// TODO: If we can't fetch, it's because we're already fetching from
|
|
||||||
// this peer. So we need to queue the request, or find another peer.
|
|
||||||
todo!();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -525,18 +515,38 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fetch(&mut self, rid: Id, seed: NodeId) -> Result<(), Error> {
|
||||||
|
let session = self.sessions.get_mut(&seed).unwrap();
|
||||||
|
if let Some(fetch) = session.fetch(rid) {
|
||||||
|
self.reactor.write(session.id, fetch);
|
||||||
|
self.reactor
|
||||||
|
.fetch(session.id, rid, Namespaces::default(), true);
|
||||||
|
} else {
|
||||||
|
// TODO: If we can't fetch, it's because we're already fetching from
|
||||||
|
// this peer. So we need to queue the request, or find another peer.
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn repo_fetched(&mut self, result: FetchResult) {
|
pub fn repo_fetched(&mut self, result: FetchResult) {
|
||||||
// TODO(cloudhead): handle completed job with service business logic
|
let remote = result.remote;
|
||||||
// TODO: Downgrade session to gossip protocol.
|
let rid = result.rid;
|
||||||
if let Some(session) = self.sessions.get_mut(result.remote()) {
|
|
||||||
if let session::State::Connected { protocol, .. } = &session.state {
|
if let Some(results) = self.fetch_reqs.get(&rid) {
|
||||||
if let session::Protocol::Fetch {
|
if results.send(result).is_err() {
|
||||||
results: Some(results),
|
self.fetch_reqs.remove(&rid);
|
||||||
} = protocol
|
}
|
||||||
{
|
}
|
||||||
results.send(result).unwrap();
|
if let Some(session) = self.sessions.get_mut(&remote) {
|
||||||
|
if let session::State::Connected { protocol, .. } = &mut session.state {
|
||||||
|
if *protocol == session::Protocol::Fetch {
|
||||||
|
*protocol = session::Protocol::Gossip;
|
||||||
} else {
|
} else {
|
||||||
// Fetch initiated by remote, we don't need to report back.
|
panic!(
|
||||||
|
"Unexpected session state for {}: expected 'fetch' protocol, got 'gossip'",
|
||||||
|
session.id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -924,7 +934,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(session::State::Connected { protocol, .. }, Message::Fetch { repo }) => {
|
(session::State::Connected { protocol, .. }, Message::Fetch { repo }) => {
|
||||||
*protocol = Protocol::Fetch { results: None };
|
*protocol = Protocol::Fetch;
|
||||||
// Instruct the transport to handover the socket to the worker.
|
// Instruct the transport to handover the socket to the worker.
|
||||||
self.reactor
|
self.reactor
|
||||||
.fetch(*remote, repo, Namespaces::default(), false);
|
.fetch(*remote, repo, Namespaces::default(), false);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::service::chan;
|
|
||||||
use crate::service::message;
|
use crate::service::message;
|
||||||
use crate::service::message::Message;
|
use crate::service::message::Message;
|
||||||
use crate::service::{storage, FetchResult};
|
use crate::service::storage;
|
||||||
use crate::service::{Id, LocalTime, NodeId, Reactor, Rng};
|
use crate::service::{Id, LocalTime, NodeId, Reactor, Rng};
|
||||||
use crate::Link;
|
use crate::Link;
|
||||||
|
|
||||||
|
|
@ -17,7 +16,7 @@ pub enum PingState {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Session protocol.
|
/// Session protocol.
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Copy, PartialEq, Eq, Clone)]
|
||||||
pub enum Protocol {
|
pub enum Protocol {
|
||||||
/// The default message-based gossip protocol.
|
/// The default message-based gossip protocol.
|
||||||
#[default]
|
#[default]
|
||||||
|
|
@ -25,12 +24,7 @@ pub enum Protocol {
|
||||||
/// Git smart protocol. Used for fetching repository data.
|
/// Git smart protocol. Used for fetching repository data.
|
||||||
/// This protocol is used after a connection upgrade via the
|
/// This protocol is used after a connection upgrade via the
|
||||||
/// [`Message::Fetch`] message.
|
/// [`Message::Fetch`] message.
|
||||||
Fetch {
|
Fetch,
|
||||||
/// Channel to send fetch results on. Set to `Some` when the fetch
|
|
||||||
/// is locally initiated. Otherwise, no results need to be communicated
|
|
||||||
/// back.
|
|
||||||
results: Option<chan::Sender<FetchResult>>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -145,12 +139,10 @@ impl Session {
|
||||||
self.attempts += 1;
|
self.attempts += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch(&mut self, repo: Id, results: chan::Sender<FetchResult>) -> Option<Message> {
|
pub fn fetch(&mut self, repo: Id) -> Option<Message> {
|
||||||
if let State::Connected { protocol, .. } = &mut self.state {
|
if let State::Connected { protocol, .. } = &mut self.state {
|
||||||
if let Protocol::Gossip = protocol {
|
if let Protocol::Gossip = protocol {
|
||||||
*protocol = Protocol::Fetch {
|
*protocol = Protocol::Fetch;
|
||||||
results: Some(results),
|
|
||||||
};
|
|
||||||
return Some(Message::Fetch { repo });
|
return Some(Message::Fetch { repo });
|
||||||
} else {
|
} else {
|
||||||
log::error!(
|
log::error!(
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ use radicle::Storage;
|
||||||
use radicle::{assert_matches, rad};
|
use radicle::{assert_matches, rad};
|
||||||
|
|
||||||
use crate::node::NodeId;
|
use crate::node::NodeId;
|
||||||
use crate::service::{FetchLookup, FetchResult};
|
use crate::service::FetchLookup;
|
||||||
use crate::storage::git::transport;
|
use crate::storage::git::transport;
|
||||||
use crate::test::logger;
|
use crate::test::logger;
|
||||||
use crate::{client, client::handle::Handle, client::Runtime, service};
|
use crate::{client, client::handle::Handle, client::Runtime, service};
|
||||||
|
|
@ -343,16 +343,17 @@ fn test_replication() {
|
||||||
};
|
};
|
||||||
assert_eq!(seeds, nonempty::NonEmpty::new(bob.id));
|
assert_eq!(seeds, nonempty::NonEmpty::new(bob.id));
|
||||||
|
|
||||||
let (from, updated) = match results.recv_timeout(Duration::from_secs(6)).unwrap() {
|
let result = results.recv_timeout(Duration::from_secs(6)).unwrap();
|
||||||
FetchResult::Fetched { from, updated } => (from, updated),
|
let updated = match result.result {
|
||||||
FetchResult::Error { from, error } => {
|
Ok(updated) => updated,
|
||||||
panic!("Fetch failed from {from}: {error}");
|
Err(err) => {
|
||||||
|
panic!("Fetch failed from {}: {err}", result.remote);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
assert_eq!(from, bob.id);
|
assert_eq!(result.remote, bob.id);
|
||||||
assert_eq!(updated, vec![]);
|
assert_eq!(updated, vec![]);
|
||||||
|
|
||||||
log::debug!(target: "test", "Fetch complete with {}", from);
|
log::debug!(target: "test", "Fetch complete with {}", result.remote);
|
||||||
|
|
||||||
let inventory = alice.handle.inventory().unwrap();
|
let inventory = alice.handle.inventory().unwrap();
|
||||||
let alice_refs = alice
|
let alice_refs = alice
|
||||||
|
|
|
||||||
|
|
@ -57,15 +57,10 @@ impl<G: Signer + EcSign + 'static> Worker<G> {
|
||||||
} = task;
|
} = task;
|
||||||
|
|
||||||
let (session, result) = self._process(&fetch, drain, session);
|
let (session, result) = self._process(&fetch, drain, session);
|
||||||
let result = match result {
|
let result = FetchResult {
|
||||||
Ok(updated) => FetchResult::Fetched {
|
rid: fetch.repo,
|
||||||
from: fetch.remote,
|
remote: fetch.remote,
|
||||||
updated,
|
result,
|
||||||
},
|
|
||||||
Err(error) => FetchResult::Error {
|
|
||||||
from: fetch.remote,
|
|
||||||
error,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
log::debug!(target: "worker", "Sending response back to service..");
|
log::debug!(target: "worker", "Sending response back to service..");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue