fix(vp): slice FTP passive ports per VP, drop bridge-mode RAM by 95% (#1646)

The 50000-51000 docker-compose port range spawned ~2000 docker-proxy
  host processes (~3.5 GB RSS) under Docker's default userland-proxy.
  The 1001-port pool was symptom treatment — collisions only matter for
  multi-VP-on-shared-bind, but the cost was paid by every install.

  Each VP now gets a non-overlapping 10-port slice computed from its id
  (VP 1 -> 50000-50009, VP 2 -> 50010-50019, ...). Class constants are
  gone; VirtualPrinterFTPServer takes passive_port_min/max instance args.
  Wraps modulo PASSIVE_MAX_SLOTS = 100, with the existing 10-attempt
  random retry as same-slot collision fallback.

  Compose default narrowed to 50000-50029 (3 VPs). Proxy-mode VPs forward
  the real printer's full range and stay on the separate TCPProxy
  constants. Compose comment rewritten to acknowledge Linux multi-service
  hosts as a primary bridge-mode audience and drop an over-stated
  "confirmed by reporter" claim about userland-proxy=false.
This commit is contained in:
maziggy
2026-06-07 08:39:40 +02:00
parent 47fe30c5ad
commit d597d36d5b
5 changed files with 213 additions and 30 deletions
@@ -20,7 +20,7 @@ from backend.app.models.virtual_printer import (
)
from backend.app.services.virtual_printer.bind_server import BindServer
from backend.app.services.virtual_printer.certificate import CertificateService
from backend.app.services.virtual_printer.ftp_server import VirtualPrinterFTPServer
from backend.app.services.virtual_printer.ftp_server import VirtualPrinterFTPServer, compute_passive_port_slice
from backend.app.services.virtual_printer.mqtt_bridge import MQTTBridge
from backend.app.services.virtual_printer.mqtt_server import SimpleMQTTServer
from backend.app.services.virtual_printer.ssdp_server import SSDPProxy, VirtualPrinterSSDPServer
@@ -749,7 +749,12 @@ class VirtualPrinterInstance:
self._tasks = []
# FTP server
# FTP server. Each VP gets a non-overlapping passive-mode port slice
# derived from its DB id so bridge-mode Docker users only have to
# expose a narrow range (#1646). Default slice is 10 ports per VP;
# see ftp_server.compute_passive_port_slice for the wrap-around
# behaviour on installs with very high VP ids.
passive_port_min, passive_port_max = compute_passive_port_slice(self.id)
self._ftp = VirtualPrinterFTPServer(
upload_dir=self.upload_dir,
access_code=self.access_code,
@@ -758,6 +763,8 @@ class VirtualPrinterInstance:
on_file_received=self.on_file_received,
bind_address=bind_addr,
vp_name=self.name,
passive_port_min=passive_port_min,
passive_port_max=passive_port_max,
)
self._tasks.append(
asyncio.create_task(