Refactored local delivery to avoid mpsc channel
This commit is contained in:
@@ -17,6 +17,7 @@ utils = { path = "../utils" }
|
||||
nlp = { path = "../nlp" }
|
||||
directory = { path = "../directory" }
|
||||
common = { path = "../common" }
|
||||
email = { path = "../email" }
|
||||
spam-filter = { path = "../spam-filter" }
|
||||
trc = { path = "../trc" }
|
||||
mail-auth = { version = "0.6" }
|
||||
|
||||
@@ -7,18 +7,14 @@
|
||||
use std::{
|
||||
hash::Hash,
|
||||
net::IpAddr,
|
||||
sync::{Arc, LazyLock},
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use common::{
|
||||
auth::AccessToken,
|
||||
config::smtp::auth::VerifyStrategy,
|
||||
listener::{
|
||||
asn::AsnGeoLookupResult,
|
||||
limiter::{ConcurrencyLimiter, InFlight},
|
||||
ServerInstance,
|
||||
},
|
||||
listener::{asn::AsnGeoLookupResult, limiter::InFlight, ServerInstance},
|
||||
Inner, Server,
|
||||
};
|
||||
use directory::Directory;
|
||||
@@ -27,7 +23,6 @@ use smtp_proto::request::receiver::{
|
||||
BdatReceiver, DataReceiver, DummyDataReceiver, DummyLineReceiver, LineReceiver, RequestReceiver,
|
||||
};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use utils::snowflake::SnowflakeIdGenerator;
|
||||
|
||||
use crate::{
|
||||
inbound::auth::SaslToken,
|
||||
@@ -219,18 +214,6 @@ impl PartialOrd for SessionAddress {
|
||||
}
|
||||
}
|
||||
|
||||
static SIEVE: LazyLock<Arc<ServerInstance>> = LazyLock::new(|| {
|
||||
Arc::new(ServerInstance {
|
||||
id: "sieve".to_string(),
|
||||
protocol: common::config::server::ServerProtocol::Lmtp,
|
||||
acceptor: common::listener::TcpAcceptor::Plain,
|
||||
limiter: ConcurrencyLimiter::new(0),
|
||||
shutdown_rx: tokio::sync::watch::channel(false).1,
|
||||
proxy_networks: vec![],
|
||||
span_id_gen: Arc::new(SnowflakeIdGenerator::new()),
|
||||
})
|
||||
});
|
||||
|
||||
impl Session<common::listener::stream::NullIo> {
|
||||
pub fn local(
|
||||
server: Server,
|
||||
@@ -268,20 +251,6 @@ impl Session<common::listener::stream::NullIo> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sieve(
|
||||
server: Server,
|
||||
mail_from: SessionAddress,
|
||||
rcpt_to: Vec<SessionAddress>,
|
||||
message: Vec<u8>,
|
||||
session_id: u64,
|
||||
) -> Self {
|
||||
Self::local(
|
||||
server,
|
||||
SIEVE.clone(),
|
||||
SessionData::local(mail_from.into(), rcpt_to, message, session_id),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn has_failed(&mut self) -> Option<String> {
|
||||
if self.stream.tx_buf.first().map_or(true, |&c| c == b'2') {
|
||||
self.stream.tx_buf.clear();
|
||||
|
||||
@@ -68,12 +68,6 @@ impl SessionManager for SmtpSessionManager {
|
||||
.report_tx
|
||||
.send(common::ipc::ReportingEvent::Stop)
|
||||
.await;
|
||||
let _ = self
|
||||
.inner
|
||||
.ipc
|
||||
.delivery_tx
|
||||
.send(common::ipc::DeliveryEvent::Stop)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ impl DeliveryAttempt {
|
||||
let delivery_result = message
|
||||
.deliver_local(
|
||||
recipients.iter_mut().filter(|r| r.domain_idx == domain_idx),
|
||||
&server.inner.ipc.delivery_tx,
|
||||
&server,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -4,20 +4,24 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||
*/
|
||||
|
||||
use common::ipc::{DeliveryEvent, DeliveryResult, IngestMessage};
|
||||
use common::Server;
|
||||
use email::delivery::{IngestMessage, LocalDeliveryStatus, MailDelivery};
|
||||
use smtp_proto::Response;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use trc::ServerEvent;
|
||||
use trc::SieveEvent;
|
||||
|
||||
use crate::queue::{
|
||||
Error, ErrorDetails, HostResponse, Message, Recipient, Status, RCPT_STATUS_CHANGED,
|
||||
use crate::{
|
||||
queue::{
|
||||
quota::HasQueueQuota, spool::SmtpSpool, DomainPart, Error, ErrorDetails, HostResponse,
|
||||
Message, MessageSource, Recipient, Status, RCPT_STATUS_CHANGED,
|
||||
},
|
||||
reporting::SmtpReporting,
|
||||
};
|
||||
|
||||
impl Message {
|
||||
pub async fn deliver_local(
|
||||
&self,
|
||||
recipients: impl Iterator<Item = &mut Recipient>,
|
||||
delivery_tx: &mpsc::Sender<DeliveryEvent>,
|
||||
server: &Server,
|
||||
) -> Status<(), Error> {
|
||||
// Prepare recipients list
|
||||
let mut total_rcpt = 0;
|
||||
@@ -37,54 +41,22 @@ impl Message {
|
||||
pending_recipients.push(rcpt);
|
||||
}
|
||||
|
||||
// Create oneshot channel
|
||||
let (result_tx, result_rx) = oneshot::channel();
|
||||
|
||||
// Deliver message to JMAP server
|
||||
let delivery_result = match delivery_tx
|
||||
.send(DeliveryEvent::Ingest {
|
||||
message: IngestMessage {
|
||||
sender_address: self.return_path_lcase.clone(),
|
||||
recipients: recipient_addresses,
|
||||
message_blob: self.blob_hash.clone(),
|
||||
message_size: self.size,
|
||||
session_id: self.span_id,
|
||||
},
|
||||
result_tx,
|
||||
// Deliver message
|
||||
let delivery_result = server
|
||||
.deliver_message(IngestMessage {
|
||||
sender_address: self.return_path_lcase.clone(),
|
||||
recipients: recipient_addresses,
|
||||
message_blob: self.blob_hash.clone(),
|
||||
message_size: self.size,
|
||||
session_id: self.span_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
// Wait for result
|
||||
match result_rx.await {
|
||||
Ok(delivery_result) => delivery_result,
|
||||
Err(_) => {
|
||||
trc::event!(
|
||||
Server(ServerEvent::ThreadError),
|
||||
CausedBy = trc::location!(),
|
||||
SpanId = self.span_id,
|
||||
Reason = "Result channel closed",
|
||||
);
|
||||
return Status::local_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
trc::event!(
|
||||
Server(ServerEvent::ThreadError),
|
||||
CausedBy = trc::location!(),
|
||||
SpanId = self.span_id,
|
||||
Reason = "TX channel closed",
|
||||
);
|
||||
return Status::local_error();
|
||||
}
|
||||
};
|
||||
.await;
|
||||
|
||||
// Process delivery results
|
||||
for (rcpt, result) in pending_recipients.into_iter().zip(delivery_result) {
|
||||
for (rcpt, result) in pending_recipients.into_iter().zip(delivery_result.status) {
|
||||
rcpt.flags |= RCPT_STATUS_CHANGED;
|
||||
match result {
|
||||
DeliveryResult::Success => {
|
||||
LocalDeliveryStatus::Success => {
|
||||
rcpt.status = Status::Completed(HostResponse {
|
||||
hostname: "localhost".to_string(),
|
||||
response: Response {
|
||||
@@ -95,7 +67,7 @@ impl Message {
|
||||
});
|
||||
total_completed += 1;
|
||||
}
|
||||
DeliveryResult::TemporaryFailure { reason } => {
|
||||
LocalDeliveryStatus::TemporaryFailure { reason } => {
|
||||
rcpt.status = Status::TemporaryFailure(HostResponse {
|
||||
hostname: ErrorDetails {
|
||||
entity: "localhost".to_string(),
|
||||
@@ -108,7 +80,7 @@ impl Message {
|
||||
},
|
||||
});
|
||||
}
|
||||
DeliveryResult::PermanentFailure { code, reason } => {
|
||||
LocalDeliveryStatus::PermanentFailure { code, reason } => {
|
||||
total_completed += 1;
|
||||
rcpt.status = Status::PermanentFailure(HostResponse {
|
||||
hostname: ErrorDetails {
|
||||
@@ -125,6 +97,56 @@ impl Message {
|
||||
}
|
||||
}
|
||||
|
||||
// Process autogenerated messages
|
||||
for autogenerated in delivery_result.autogenerated {
|
||||
let from_addr_lcase = autogenerated.sender_address.to_lowercase();
|
||||
let from_addr_domain = from_addr_lcase.domain_part().to_string();
|
||||
|
||||
let mut message = server.new_message(
|
||||
autogenerated.sender_address,
|
||||
from_addr_lcase,
|
||||
from_addr_domain,
|
||||
self.span_id,
|
||||
);
|
||||
for rcpt in autogenerated.recipients {
|
||||
message.add_recipient(rcpt, server).await;
|
||||
}
|
||||
|
||||
// Sign message
|
||||
let signature = server
|
||||
.sign_message(
|
||||
&mut message,
|
||||
&server.core.sieve.sign,
|
||||
&autogenerated.message,
|
||||
)
|
||||
.await;
|
||||
|
||||
// Queue Message
|
||||
message.size = autogenerated.message.len() + signature.as_ref().map_or(0, |s| s.len());
|
||||
if server.has_quota(&mut message).await {
|
||||
message
|
||||
.queue(
|
||||
signature.as_deref(),
|
||||
&autogenerated.message,
|
||||
self.span_id,
|
||||
server,
|
||||
MessageSource::Autogenerated,
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
trc::event!(
|
||||
Sieve(SieveEvent::QuotaExceeded),
|
||||
SpanId = self.span_id,
|
||||
From = message.return_path_lcase,
|
||||
To = message
|
||||
.recipients
|
||||
.into_iter()
|
||||
.map(|r| trc::Value::from(r.address_lcase))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if total_completed == total_rcpt {
|
||||
Status::Completed(())
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user