Commit Graph
2729 Commits
Author SHA1 Message Date
Wez Furlong 8029d6051f throttle: more redis limit diagnostics 2025-01-06 08:39:08 -07:00
Wez Furlong aedf744edd throttle: another pass to try to figure out the integer/range issue 2025-01-06 08:18:34 -07:00
Wez Furlong 4064b4d6e5 throttle: more redis limit tweaks
trying to hone in on this, which I think is an edge case when we try to
acquire just as element(s) are expiring and producing an empty set:

```
thread 'limit::test::test_redis' panicked at crates/throttle/src/limit.rs:469:14:
called `Result::unwrap()` on an `Err` value: AnyHow(error invoking redis
lease acquisition script
key=test_redis-4e24b3a0-81f8-4cbe-abfb-16c63ee3e7cf now=1736174679.13151
expires=1736174681.13151 limit=2
uuid=346b46d2-00d6-4109-9775-a7559d484eba

Caused by:
    An error was signalled by the server - ResponseError: Error running
    script (call to f_2e0bdd7bbe1fa07f6cc6eebfba9ef415044c97a5):
    @user_script:13: ERR value is not an integer or out of range)
```
2025-01-06 08:00:07 -07:00
Wez Furlong e837e9b4ae throttle: add some diagnostics around redis limit
Debugging a failure that shows up in CI
2025-01-06 07:38:11 -07:00
Wez Furlong 313a084c29 mailparsing: fix construction of Mailbox name containing @
We need to ensure that we quote the name portion of a mailbox
if it contains an @-sign in order for the resulting mailbox to
be valid.

quoting here means that the name is enclosed in quotes, but
not that the @-sign is itself quoted with a backslash, so
we have a little hard-coded special case in the string
quoting function for this.
2025-01-06 07:06:53 -07:00
Wez Furlong 038abd459c ready_queue: break out of limit lease acquire on shutdown/low mem
With the recent changes to acquire_lease, the maintain() method
can now block for a couple of minutes pending acquisition of a lease.

This commit should knock it out of that wait when the system
is either shutting down, or starts running low on memory.
2025-01-06 06:53:13 -07:00
Wez Furlong 26981e2c97 refactor: ready_queue: early return from maintainer
If we don't need to open any new connections, return early.

This just reduces the indentation level in the maintainer
method, and has not functional impact.
2025-01-06 06:53:13 -07:00
Wez Furlong a159c4ea52 throttle: allow waiting for a limit lease
Previously, the lease acquire operation was a non-blocking instantaneous
operation, relying on the maintainer to check back and acquire again
later on.

Because that maintainer time interval could be relatively long, this
could lead to multiple queues hanging around not doing very much,
especially when the limit leases were under heavy contention.

This commit introduces a deadline parameter to the lease acquire method,
causing the acquire operation to wait until that deadline before giving
up with a TooManyLeases error.

For local/memory-backed mode that wait operation is provided by the
tokio Notify object; the calling task goes to sleep until awoken or the
timeout expires.

For redis-backed mode there isn't a suitable wakeup mechansim (well,
there is a redis pubsub channel, but the plumbing for this across the
various modes of redis client make integration awkward, and it would
mean that every lease acquire would ping every kumomta instance. That
traffic may not be desirable, so at this time we're not using that
mechanism), so the strategy is a simple
poll-redis-every-3-seconds-while-we-wait.

This commit encompasses the wait inside the throttle crate, and its API
fan-out, but there likely needs to be some additional smarts in the
ready queue so that that wait can be interrupted for low memory and
shutdown.
2025-01-06 06:53:12 -07:00
Wez Furlong 44c0c029e4 throttle: introduce LimitSpec and force_local mode
This brings parity with ThrottleSpec in allowing for explicitly
local limits, even when redis sharing is enabled.
2025-01-06 06:53:12 -07:00
Wez Furlong 359d15ca3f refactor: s/LimitSpec/LimitSpecWithDuration/
I plan to make LimitSpec the type we use for the path config
with some options that do not require a duration, so rename
this particular type in preparation for that.

No functional change here.
2025-01-06 06:53:08 -07:00
Nathalie Caiandncai a3c7fe7d00 add send_batch method to kafka mod (#324)
* add send_batch method to kafka mod

* add error messages to returned value and don't log it as error

* add kafka send_batch doc

---------

Co-authored-by: ncai <ncai@chapsvision.com>
2025-01-03 09:57:40 -07:00
Wez Furlong dc133d8062 mailparsing: add unit test for multi-line CD filename
A user reported that one of their users was having trouble
with a message that seemed to point at a multi-line Content-Disposition
header with some specific content.

This commit adds a unit test that shows that the issue was not due
solely to just this header, because it successfully parses.

Their issue is something else that is yet to be understood.
2025-01-01 07:00:49 -07:00
Wez Furlong 4909fe8e55 mailparsing: implement own text wrapping function
A user reported that constructing certain UTF-8 From headers
in the HTTP injection API could produce a From header that
could not be parsed by the DKIM helper when subsequently
attempting to sign the message.

The issue was that the textwrap crate will try to fill out
the wrap, preferring to break an existing word rather than
generating a new line to accommodate one when it would
produce a line that was too long.

This commit adds our own text wrapping algorithm that is
more forgiving.
2024-12-31 10:07:02 -07:00
Wez Furlong ff8c0ccff2 Expose ready queue states via api, and in kcli queue-summary
This adds connection limit/throttle states to the readyq rows
in `kcli queue-summary`, alongside where we would show the
suspension state.

This makes it easier to understand when a given egress path
might be hitting connection limits.
2024-12-21 07:44:29 -07:00
Wez Furlong 02fc8458f9 fix connection rate being limit to 1/10min when limit hit
The issue here is:

* If a connection limit was hit (eg: TooManyLeases) then the
  readyq maintainer completes its work for that one wakeup
* readyq maintainer then goes to sleep until either 10 minutes
  have elapsed, or a new message is moved into the ready queue
* If the system either has no new messages being injected to
  that queue, or all of the messages are currently ready,
  then those messages will camp out in the ready queue until
  10 minutes have elapsed before we try to make another connection.

This hampers the rate of egress.

What this commit does is:

* Introduce a QueueState concept where we can indicate a kind of
  "status effect" that applies to a queue. The state has some
  human readable context and a time for when the effect was
  most recently observed.
* Adds a `connection_limited` state to indicate when we've hit a
  connection limit and a `connection_rate_throttled` state when we've
  hit the max_connection_rate.
* The ready queue maintainer will reduce its wakeup interval
  if it observes that connections have been limited, so that
  we can wakeup sooner.

In a separate commit, an API endpoint will be added to expose
these queue states and augment the summary command output.
2024-12-21 06:53:51 -07:00
Mike Hillyer a1c82b3618 Add resolve-shaping-domain to the Userguide and FAQ. 2024-12-20 16:19:32 -05:00
Wez Furlong 39bbc0953e switch to DeferredInjectionRebind for deferred->fully realized
It's confusing to have 2 Delivery records, even if there are other
fields that distinguish between the events.
2024-12-19 10:14:26 -07:00
Wez Furlong cc0b96814e switch opportunistic_tls_reconnect_on_failed_handshake to non-recursive
We had a report of a stack overflow while using this option in some
heavy load scenarios.

It's not clear that it is 100% correlated, but I'd feel better
not having recursion here so that we can rule that out.
2024-12-18 10:21:16 -07:00
Wez Furlong d1657258f2 Clarify what is logged in Expiration records
Make the message more human readable.

Leave the site empty instead of `localhost`; no site was responsible
for the decision to expire the message, that is due to local policy.
2024-12-18 10:06:07 -07:00
Wez Furlong cc90c278fa docs: fixup mermaidjs issue
This used to work fine, but recently started to bleed through
some control characters

closes: https://github.com/KumoCorp/kumomta/issues/323
2024-12-18 06:41:45 -07:00
Wez Furlong 832c898b75 Add unix domain socket support in smtp dispatcher
This commit allows setting the queue config to use a unix domain socket
path:

```
        protocol = {
          smtp = {
            mx_list = { '/some/path' },
          },
        },
```

and the smtp dispatcher will attempt to connect to it via a unix domain
socket.

It is anticipated that this will be used together with an egress path
config set to `use_lmtp = true`.

I've tested the ability to attempt to connect, but I don't have a
convenient implementation of SMTP/LMTP to test against here, so all I've
tested if that we can successfully fail to connect to a unix domain
socket.

refs: https://github.com/KumoCorp/kumomta/issues/267
2024-12-17 11:00:49 -07:00
Wez Furlong 4b12c4ca1c add kumo-address crate for HostAddress and SocketAddress types
One frustration I have with the Rust standard library types is
that while it has a SocketAddr type, that type doesn't include
unix domain addressing like the underlying unix OS does in the
underlying system type.

I understand why it is that way, it's still a bit of a PITA.

Since we want to enable the use of unix domain sockets for
outbound LMTP support, our internal addressing type needs to
be able to represent a unix domain address.

This commit introduces our own HostAddress (equivalent to IpAddr,
but not limited to IP addresses) and SocketAddress (equivalent to
SockAddr, but not limited to IP sockets) types.

The ResolvedAddress type has been cut over from IpAddr to HostAddress
and the fan out addressed.

This commit does not add support for establishing unix domain
connections, it is merely the ability to recognize/report on them.

refs: https://github.com/KumoCorp/kumomta/issues/267
2024-12-17 09:53:35 -07:00
Wez Furlong 8be4ad82a1 add basic outbound LMTP support
This enables LMTP over TCP by setting `use_lmtp = true` in the
egress path configuration.

refs: https://github.com/KumoCorp/kumomta/issues/267
2024-12-17 06:48:52 -07:00
Wez Furlong 05295e415a add opportunistic_tls_reconnect_on_failed_handshake option
When used together with an Opportunistic TLS mode, if the handshake
or subsequent EHLO fails, we will re-connect to the current host
and disable TLS.

This is implemented as a recursive solution, which I'm not totally
keen on, but the recursion is limited to a single level so it's
not so bad.
2024-12-16 15:20:13 -07:00
Wez Furlong 44eacf1807 providers: add HostName to enable exactly matching MX hostnames 2024-12-16 14:09:01 -07:00
Wez Furlong 48e89105a7 fix provider rule matching with MXSuffix
Given a provider with the following config:

```json
      "match": [
        {
          "MXSuffix": "mta5.am0.yahoodns.net"
        },
        {
          "MXSuffix": "mta6.am0.yahoodns.net"
        },
        {
          "MXSuffix": "mta7.am0.yahoodns.net"
        }
      ]
```

(Note that this configuration is not ideal because someone with
`notreallymta5.am0.yahoodns.net` in their MX records will match
this.  If you were using SMTP auth for such a site, then you risk
leaking your credentials to it! We should consider adding an exact
match option for this case)

we could never match this because the logic had the inner and outer
loops swapped.

For a provider to match, all of the resolved host names must match
at least one of the MXSuffixes defined in the rule.

The flipped logic prevented that from matching.

Most of this commit is adding stuff to help trace this down
and debug it.

In particular, `resolve-queue-config` will tell you what the
effective value of the get-queue-config event is for a given
queue name, and `resolve-shaping-domain` will show you the shaping
configuration for a (bogus) source.
2024-12-16 12:46:31 -07:00
Wez Furlong 5f19f7ce04 add deferred_queue option to the smtp listener
This option should be used with caution, and ideally only
for trusted networks.

The purpose is to absorb the latency of post-DATA processing
and hide it from the trust injector.

It defers processing that would normally happen in smtp_server_message_received
and instead will, at some (ideally) near-future time trigger an
smtp_server_message_deferred_inject event instead.

This will marginally increase your average injection latency but should
clamp your worst case injection latency much lower because the outliers
will not happen inline with the injecting client.
2024-12-16 11:27:40 -07:00
Wez Furlong 1037b1a11e smtp_dispatcher: expand protocol sync handling to 503
Expand the 3xx-is-impossible logic to include a 503 response,
which can only indicate a bad sequence of commands.

We should retry the message in that situation.
2024-12-13 15:25:28 -07:00
Wez Furlong 98a5479ce9 rfc5321/client: handle obscure DATA case in rfc2920
The pipelining spec allows for a server to proceed with a transaction
in which there were no valid recipients and validly enter the DATA
portion of the transaction.

I suspect that this might be responsible for the occasional protocol
synchronization issue that we've seen.

Handle that case per the recommendations in the RFC by sending
a dummy DATA in the case that some of the commands failed by
DATA succeeded.
2024-12-13 15:18:58 -07:00
Wez Furlong 40e29b28b8 cidr-map: use more compact Debug representation
It's bothered me for a while how verbose the debug print for
prohibited_hosts and skip_hosts is in diagnostics shared from
users.

This commit adopts a more succint map-like representation of
the cidr structure, and similar set-like representation for
the set equivalent.
2024-12-13 10:09:46 -07:00
Wez Furlong 8620dd56db smtp_dispatcher: include session id in protocol sync error message 2024-12-13 09:43:31 -07:00
Wez Furlong b3aa6d4154 shaping.lua: explicit gc in kumo.tsa.config.monitor
The config monitor task is a long-lived task that runs
inside the same lua context for its entire lifetime.

That task obtains a reference to the current shaping data
every 30 seconds to decide whether it needs to synthesize
an update to the config epoch.

Lua's memory management cannot see the total amount of
ram consumed by the shaping data because the shaping data
is a userdata type whose ram is managed externally.
That means that lua things that this task is generally not
using many resources (~ a handful of pointers per loop iteration)
and gc doesn't kick in very aggressively.

For sites with large TSA shaping overrides, or otherwise with
very large shaping data, this can result in an accumulation
of stale shaping data in that long-lived lua context.

This commit adds an explicit gc trigger before the task
sleeps on each iteration so that we can release those references.
2024-12-13 06:38:34 -07:00
Mike Hillyer 77832e81a6 Typo 2024-12-12 13:59:35 -05:00
Wez Furlong f19b6bc7f3 apply timerwheel batch wait to singleton wheel as well
In 4a0a4d6a1e I fixed an issue where
the bucketing used in the timewheel could trick the throttling
event handler into thinking that the message had been explicitly
throttled by the event, when in reality it was just a 20ms discrepancy.

I overlooked that this same logic should also apply to the equivalent
logic for the singleton timer wheel, so that is what this commit does.
2024-12-12 10:44:24 -07:00
Wez Furlong 81987604e9 memory: include jemalloc stats in api endpoint
The goal here was to show the lowest level of stats available from the
allocator by querying jemalloc for that info, so that we can understand
the gap between what Rust sees and what any embedded C stuff might see.

```
; curl -s 'http://127.0.0.1:8000/api/admin/memory/stats'
JemallocStats {
    allocated: 25654488 (25.65 MB),
    active: 35651584 (35.65 MB),
    metadata: 29685520 (29.69 MB),
    resident: 85446656 (85.45 MB),
    mapped: 364630016 (364.63 MB),
    retained: 252981248 (252.98 MB),
}
RSS = 142651392 (142.65 MB)
soft limit = Some(202282060800 (202.28 GB))
hard limit = Some(269709414400 (269.71 GB))
live = CountAndSize { count: 109237 (109,237), size: 12111580 (12.11 MB) }

use kumo.enable_memory_callstack_tracking(true) to enable additional stats
```
2024-12-12 09:08:50 -07:00
Wez Furlong 8aee36a3ce throttle: increase tolerance in test cases
refs: https://github.com/KumoCorp/kumomta/issues/297
2024-12-12 07:16:48 -07:00
Wez Furlong d6733198cd shaping.toml: switch to remember_broken_tls
Now that we've had a couple of users successfully running with
remember_broken_tls, let's make it the default so that we can
reduce the typical size of the TSA-generated configuration
2024-12-12 07:10:40 -07:00
Wez Furlong 335fbd96e5 summaryize-memory: show sampled range for the notable values too 2024-12-12 06:50:35 -07:00
Wez Furlong 6ce88f40ed lruttl: periodically prune out expired entries
So far, we've deferred proactively aging out expired entries from
our TTL-based LRU caches, instead relying on the next lookup operation
on the expired key to detect and replace the stale value.

Recently we have been focused on keeping the memory footprint lower.

This commit adds a periodic (every 30 seconds) background task
that will determine all cache entries that have expired, and
remove them from the cache.
2024-12-11 22:25:46 -07:00
Wez Furlong 4381995ec5 memory: another run at waiting for memory status subscriber
in 1c25a0d8a4 I only partially
handled this race condition in CI.

This commit handles it more generally.
2024-12-11 16:46:46 -07:00
Wez Furlong 88c28b62c0 memory: implement our own tracking allocator
The re_memory crate was good for a first pass at implementing call stack
tracking, but unfortunately, it was symbolicating the stack traces in
the allocation path which had terrible performance implications.

This commit implements our own somewhat leaner and meaner version of
this functionality that defers resolving the call stack symbols until
the top callstacks are computed.

This makes things run faster than they would have when using re_memory.

Shedding re_memory also allows us to avoid pulling in the various other
dependencies that it pulled in that were otherwise unused, which makes
me feel even better about this.

This commit also improves the output of the summarize-memory tool to
make it clearer that we are uncertain about the full extent of the
sampled allocations: the stochastic sampling only nominally samples
every 64 of the medium-sized allocations. That means that the reported
total size for those may be 1/64 of what we printed previously.  We now
print the range of allocations and use a ~ character to indicate that
we're uncertain about the number of allocations that were made for those
sampled allocations.
2024-12-11 15:51:26 -07:00
Wez Furlong 414a9b617d ready_queue: switch update_capacity to MessageList
Rather than build a vec of the excess, use a list since
it requires no additional heap memory.
2024-12-10 17:59:24 -07:00
Wez Furlong 68c3606420 ready_queue: use MessageList when taking ready queue
Now that we have doubly-linked-lists of Messages for these things,
it is more efficient to take the whole list when we're doing
bulk ready queue operations, so let's do that.
2024-12-10 17:55:12 -07:00
Wez Furlong 1c25a0d8a4 server-memory: allow subscribe_to_memory_status_changes to return None
In some cases in CI, we might have a race condition where we spawn
the lruttl background thread before the memory monitor thread
completes it startup.
2024-12-10 16:29:18 -07:00
Wez Furlong 5a53af04a5 logging: async-channel -> flume
The flume bounded queue doesn't eagerly allocate its full capacity
and otherwise runs fast enough for our logging that we don't notice
the difference.  This reduces the "static" footprint of kumomta
in the (hopefully) common case where logging is keeping up with
the system throughput and is not approaching the limit of the
default logging backlog.

Remove unused reference to async-channel from kumo-server-runtime.
2024-12-10 15:42:46 -07:00
Wez Furlong a0ec332115 log-types: reduce size of JsonLogRecord by ~30% in common case
One of the things that shows up in the large allocation list
is the pre-allocated channel used by the logging infrastructure.

I'd like to adjust that to not require pre-allocation (or at least,
not require so much pre-allocation), but an easy memory reduction
win is to Box out the FBL portion of the record; incoming FBL
reports are incredibly rare compared to the bulk of the messages
processed by the system so it is reasonable to use an extra heap
allocation for those if it means we can save a nice chunk of
memory for the common case.
2024-12-10 15:18:33 -07:00
Wez Furlong 59c0bc7db7 queue: shrink the singleton hashset after popping
Let's try to keep our memory usage a bit more trim by shrinking
the hashset after we've popped messages from the singleton timer
wheel.
2024-12-10 14:59:11 -07:00
Wez Furlong dbcc831b82 simple_policy: tweak my test config for more throughput 2024-12-10 14:49:12 -07:00
Wez Furlong c13dde9ecb message: introduce intrusive MessageList type, replace Fifo internals
I've been recently troubleshooting a couple of systems with high memory
usage, and in one of them there were very large amounts of
memory being allocated to ready queues.  That could be partially
mitigated by reducing `max_ready` to a more reasonable and small value,
but it is difficult to compute the right balance between large-enough
for high throughput and small-enough to keep memory usage reasonable.

This commit switches away from crossbeam's ArrayQueue, which
pre-allocates sufficient space to hold exactly `max_ready` messages for
each instantiated ready queue, and to a newly introduced MessageList,
which is an intrusive doubly-linked list.

The intrusive list, in exchange for some small additional overhead
per-Message, requires no auxilliary additional memory allocations to
track the membership of that Message in some other list.

That means that `max_ready` is no longer a pre-allocated minimum amount
of additional storage, and changes the memory overhead from
`O(number-of-queues * max_ready)` to `O(number-of-ready-messages)`,
which is typically a lot smaller.  This is independent of the individual
messages metadata and bodies that are nominally associated with being in
a ready queue.
2024-12-10 14:29:41 -07:00
Wez Furlong fa276973c4 validate-shaping: show approx memory usage of processed files 2024-12-10 11:16:16 -07:00