Refactored local delivery to avoid mpsc channel

This commit is contained in:
mdecimus
2025-01-17 15:29:55 +01:00
parent 00ad5a5c44
commit 2eb388674d
117 changed files with 2611 additions and 2628 deletions

View File

@@ -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" }

View File

@@ -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();

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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 {