mirror of https://github.com/mastodon/flodgatt
Fix edge case bug for long messages (#144)
This commit is contained in:
parent
4a13412f98
commit
66553408fb
|
@ -416,7 +416,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flodgatt"
|
name = "flodgatt"
|
||||||
version = "0.9.6"
|
version = "0.9.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "flodgatt"
|
name = "flodgatt"
|
||||||
description = "A blazingly fast drop-in replacement for the Mastodon streaming api server"
|
description = "A blazingly fast drop-in replacement for the Mastodon streaming api server"
|
||||||
version = "0.9.6"
|
version = "0.9.7"
|
||||||
authors = ["Daniel Long Sockwell <daniel@codesections.com", "Julian Laubstein <contact@julianlaubstein.de>"]
|
authors = ["Daniel Long Sockwell <daniel@codesections.com", "Julian Laubstein <contact@julianlaubstein.de>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|
|
@ -41,20 +41,19 @@ impl Stream for Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
while let Async::Ready(msg_len) = self.redis_conn.poll_redis(self.unread_idx.1)? {
|
while let Async::Ready(msg_len) = self.redis_conn.poll_redis(self.unread_idx.1)? {
|
||||||
self.unread_idx.1 += msg_len;
|
self.unread_idx = (0, self.unread_idx.1 + msg_len);
|
||||||
|
|
||||||
let input = &self.redis_conn.input[..self.unread_idx.1];
|
let input = &self.redis_conn.input[..self.unread_idx.1];
|
||||||
let mut unread = str::from_utf8(input).unwrap_or_else(|e| {
|
let mut unread = str::from_utf8(input).unwrap_or_else(|e| {
|
||||||
str::from_utf8(input.split_at(e.valid_up_to()).0).expect("guaranteed by `split_at`")
|
str::from_utf8(input.split_at(e.valid_up_to()).0).expect("guaranteed by `split_at`")
|
||||||
});
|
});
|
||||||
|
let tag_id_cache = &mut self.redis_conn.tag_id_cache;
|
||||||
|
let redis_namespace = &self.redis_conn.namespace;
|
||||||
while !unread.is_empty() {
|
while !unread.is_empty() {
|
||||||
let tag_id_cache = &mut self.redis_conn.tag_id_cache;
|
use RedisParseOutput::*;
|
||||||
let redis_namespace = &self.redis_conn.namespace;
|
match RedisParseOutput::try_from(unread) {
|
||||||
|
|
||||||
use {Error::InvalidId, RedisParseOutput::*};
|
|
||||||
unread = match RedisParseOutput::try_from(unread) {
|
|
||||||
Ok(Msg(msg)) => {
|
Ok(Msg(msg)) => {
|
||||||
let trimmed_tl = match redis_namespace {
|
let tl_matching_ns = match redis_namespace {
|
||||||
Some(ns) if msg.timeline_txt.starts_with(ns) => {
|
Some(ns) if msg.timeline_txt.starts_with(ns) => {
|
||||||
Some(&msg.timeline_txt[ns.len() + ":timeline:".len()..])
|
Some(&msg.timeline_txt[ns.len() + ":timeline:".len()..])
|
||||||
}
|
}
|
||||||
|
@ -62,39 +61,44 @@ impl Stream for Manager {
|
||||||
Some(_non_matching_ns) => None,
|
Some(_non_matching_ns) => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(trimmed_tl) = trimmed_tl {
|
if let Some(trimmed_tl) = tl_matching_ns {
|
||||||
let tl = Timeline::from_redis_text(trimmed_tl, tag_id_cache)?;
|
let tl = Timeline::from_redis_text(trimmed_tl, tag_id_cache)?;
|
||||||
let event: Arc<Event> = Arc::new(msg.event_txt.try_into()?);
|
let event: Arc<Event> = Arc::new(msg.event_txt.try_into()?);
|
||||||
let channels = self.timelines.get_mut(&tl).ok_or(InvalidId)?;
|
if let Some(channels) = self.timelines.get_mut(&tl) {
|
||||||
for (_id, channel) in channels {
|
for (_id, channel) in channels {
|
||||||
if let Ok(Async::NotReady) = channel.poll_ready() {
|
if let Ok(Async::NotReady) = channel.poll_ready() {
|
||||||
log::warn!("{:?} channel full", tl);
|
log::warn!("{:?} channel full", tl);
|
||||||
return Ok(Async::NotReady);
|
return Ok(Async::NotReady);
|
||||||
|
}
|
||||||
|
let _ = channel.try_send(event.clone()); // err just means channel will be closed
|
||||||
}
|
}
|
||||||
let _ = channel.try_send(event.clone()); // err just means channel will be closed
|
}
|
||||||
|
}
|
||||||
|
unread = msg.leftover_input;
|
||||||
|
}
|
||||||
|
Ok(NonMsg(leftover_input)) => unread = leftover_input,
|
||||||
|
Err(RedisParseErr::Incomplete) => {
|
||||||
|
if self.unread_idx.0 == 0 {
|
||||||
|
// msg already first; no copying needed
|
||||||
|
} else if self.unread_idx.0 >= (self.unread_idx.1 - self.unread_idx.0) {
|
||||||
|
let (read, unread) = self.redis_conn.input[..self.unread_idx.1]
|
||||||
|
.split_at_mut(self.unread_idx.0);
|
||||||
|
for (i, b) in unread.iter().enumerate() {
|
||||||
|
read[i] = *b;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// skip messages for different Redis namespaces
|
// Less efficient, but should never occur in production
|
||||||
|
log::warn!("Moving partial input requires heap allocation");
|
||||||
|
self.redis_conn.input =
|
||||||
|
self.redis_conn.input[self.unread_idx.0..].into();
|
||||||
}
|
}
|
||||||
msg.leftover_input
|
self.unread_idx = (0, self.unread_idx.1 - self.unread_idx.0);
|
||||||
|
unread = "";
|
||||||
}
|
}
|
||||||
Ok(NonMsg(leftover_input)) => leftover_input,
|
Err(e) => Err(Error::RedisParseErr(e, unread.to_string()))?,
|
||||||
Err(RedisParseErr::Incomplete) => {
|
|
||||||
log::info!("Copying partial message");
|
|
||||||
let (read, unread) = self.redis_conn.input[..self.unread_idx.1]
|
|
||||||
.split_at_mut(self.unread_idx.0);
|
|
||||||
for (i, b) in unread.iter().enumerate() {
|
|
||||||
read[i] = *b;
|
|
||||||
}
|
|
||||||
self.unread_idx = (0, unread.len());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
Err(e) => Err(e)?,
|
|
||||||
};
|
};
|
||||||
self.unread_idx.0 = self.unread_idx.1 - unread.len();
|
self.unread_idx.0 = self.unread_idx.1 - unread.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.unread_idx = (0, 0) // reaching here means last msg was complete; reuse the full buffer
|
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(Some(())))
|
Ok(Async::Ready(Some(())))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub enum Error {
|
||||||
InvalidId,
|
InvalidId,
|
||||||
TimelineErr(TimelineErr),
|
TimelineErr(TimelineErr),
|
||||||
EventErr(EventErr),
|
EventErr(EventErr),
|
||||||
RedisParseErr(RedisParseErr),
|
RedisParseErr(RedisParseErr, String),
|
||||||
RedisConnErr(RedisConnErr),
|
RedisConnErr(RedisConnErr),
|
||||||
ChannelSendErr(tokio::sync::mpsc::error::TrySendError<Arc<Event>>),
|
ChannelSendErr(tokio::sync::mpsc::error::TrySendError<Arc<Event>>),
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ impl fmt::Display for Error {
|
||||||
"tried to access a timeline/channel subscription that does not exist"
|
"tried to access a timeline/channel subscription that does not exist"
|
||||||
),
|
),
|
||||||
EventErr(inner) => write!(f, "{}", inner),
|
EventErr(inner) => write!(f, "{}", inner),
|
||||||
RedisParseErr(inner) => write!(f, "{}", inner),
|
RedisParseErr(inner, input) => write!(f, "error parsing {}\n{}", input, inner),
|
||||||
RedisConnErr(inner) => write!(f, "{}", inner),
|
RedisConnErr(inner) => write!(f, "{}", inner),
|
||||||
TimelineErr(inner) => write!(f, "{}", inner),
|
TimelineErr(inner) => write!(f, "{}", inner),
|
||||||
ChannelSendErr(inner) => write!(f, "{}", inner),
|
ChannelSendErr(inner) => write!(f, "{}", inner),
|
||||||
|
@ -59,8 +59,8 @@ impl From<TimelineErr> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<RedisParseErr> for Error {
|
// impl From<RedisParseErr> for Error {
|
||||||
fn from(e: RedisParseErr) -> Self {
|
// fn from(e: RedisParseErr) -> Self {
|
||||||
Self::RedisParseErr(e)
|
// Self::RedisParseErr(e)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
Loading…
Reference in New Issue