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:
@@ -7,6 +7,8 @@ import io
|
|||||||
import contextlib
|
import contextlib
|
||||||
import imaplib
|
import imaplib
|
||||||
import mailbox
|
import mailbox
|
||||||
|
import gc
|
||||||
|
import ctypes
|
||||||
from email.header import decode_header
|
from email.header import decode_header
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from email.utils import formatdate
|
from email.utils import formatdate
|
||||||
@@ -47,6 +49,22 @@ mcp = FastMCP("Mail", stateless_http=True,
|
|||||||
transport_security={"enable_dns_rebinding_protection": False})
|
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):
|
def _safe_decode(payload, charset):
|
||||||
"""Decode bytes robust gegen unbekannte/kaputte Charsets (z.B. 'x-unknown')."""
|
"""Decode bytes robust gegen unbekannte/kaputte Charsets (z.B. 'x-unknown')."""
|
||||||
if not isinstance(payload, bytes):
|
if not isinstance(payload, bytes):
|
||||||
@@ -207,7 +225,9 @@ def search_mail(
|
|||||||
continue
|
continue
|
||||||
results.append(f"[{date_str}] {frm} -> {to}\n Subject: {subj}\n Account: {acct_name}, Folder: {fld}, Key: {key}")
|
results.append(f"[{date_str}] {frm} -> {to}\n Subject: {subj}\n Account: {acct_name}, Folder: {fld}, Key: {key}")
|
||||||
if len(results) >= limit:
|
if len(results) >= limit:
|
||||||
|
_trim_memory()
|
||||||
return "\n\n".join(results)
|
return "\n\n".join(results)
|
||||||
|
_trim_memory()
|
||||||
return "\n\n".join(results) if results else "No results found"
|
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)")],
|
attachment_index: Annotated[int, Field(description="Attachment number from the read_mail attachment list (1-based)")],
|
||||||
) -> list[TextContent | ImageContent | EmbeddedResource]:
|
) -> 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."""
|
"""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()
|
user = get_current_user()
|
||||||
if not user:
|
if not user:
|
||||||
return [TextContent(type="text", text="Error: not authenticated")]
|
return [TextContent(type="text", text="Error: not authenticated")]
|
||||||
|
|||||||
Reference in New Issue
Block a user