mail: malloc_trim nach search_mail/read_attachment gegen RSS-Bloat

search_mail dekodiert bei breiten Queries den Body jeder Mail; CPython/glibc
gibt den Peak-RSS nicht ans OS zurueck -> Prozess wuchs auf 6.2 GB und hat
zusammen mit einem 2-TB-Restic-Lauf den Server-RAM/Swap gesprengt (restic OOM).
Fix: gc.collect()+malloc_trim(0) nach den speicherintensiven Tools.
Zusaetzlich systemd MemoryMax=2G/MemoryHigh=1.5G als Sicherheitsnetz (Drop-In, nicht im Repo).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Lohmaier
2026-06-18 08:54:33 +02:00
parent c41fb89b06
commit 37a24bc5b5
+21
View File
@@ -7,6 +7,8 @@ import io
import contextlib
import imaplib
import mailbox
import gc
import ctypes
from email.header import decode_header
from email.mime.text import MIMEText
from email.utils import formatdate
@@ -47,6 +49,22 @@ mcp = FastMCP("Mail", stateless_http=True,
transport_security={"enable_dns_rebinding_protection": False})
try:
_libc = ctypes.CDLL("libc.so.6")
except OSError:
_libc = None
def _trim_memory():
"""gc + malloc_trim: Speicher nach grossen Operationen ans OS zurueckgeben."""
gc.collect()
if _libc is not None:
try:
_libc.malloc_trim(0)
except Exception:
pass
def _safe_decode(payload, charset):
"""Decode bytes robust gegen unbekannte/kaputte Charsets (z.B. 'x-unknown')."""
if not isinstance(payload, bytes):
@@ -207,7 +225,9 @@ def search_mail(
continue
results.append(f"[{date_str}] {frm} -> {to}\n Subject: {subj}\n Account: {acct_name}, Folder: {fld}, Key: {key}")
if len(results) >= limit:
_trim_memory()
return "\n\n".join(results)
_trim_memory()
return "\n\n".join(results) if results else "No results found"
@@ -254,6 +274,7 @@ def read_attachment(
attachment_index: Annotated[int, Field(description="Attachment number from the read_mail attachment list (1-based)")],
) -> list[TextContent | ImageContent | EmbeddedResource]:
"""Read an email attachment. Images shown inline, PDFs as extracted text, text directly, other documents as binary. Get the index from read_mail."""
_trim_memory()
user = get_current_user()
if not user:
return [TextContent(type="text", text="Error: not authenticated")]