-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathrun_eventlet.py
More file actions
151 lines (128 loc) · 5.59 KB
/
Copy pathrun_eventlet.py
File metadata and controls
151 lines (128 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env python
"""
Eventlet launcher for WilmerAI.
Reads configuration from WilmerAI config files and launches Eventlet WSGI server.
"""
# CRITICAL: Monkey-patch MUST happen before ANY other standard library imports (like sys, argparse, logging).
try:
import eventlet
eventlet.monkey_patch()
except ImportError:
print("Error: Eventlet is not installed. Please install it via pip install eventlet.")
import sys
sys.exit(1)
import sys
import logging
# Import socket AFTER monkey_patch
import socket
import traceback
# Define a function to parse arguments and set globals
def initialize_globals():
# Safe only after monkey_patch: these pull in stdlib and
# Middleware.common/utilities, but not server.py (which initializes the
# whole app at import time).
from Middleware.common import instance_global_variables
from Middleware.common.launch_arguments import parse_and_apply_launch_arguments
parse_and_apply_launch_arguments("Launch WilmerAI with Eventlet")
return instance_global_variables
# Initialize globals
globals_vars = initialize_globals()
# Import the Flask app (server.py will now run with patched stdlib and configure logging)
# We must import this after all initialization.
from server import application, resolve_port
try:
port = resolve_port()
print(f"Listening port: {port}")
print(f"Config Directory: {globals_vars.CONFIG_DIRECTORY}")
if globals_vars.USERS and len(globals_vars.USERS) > 1:
print(f"Users: {', '.join(globals_vars.USERS)}")
elif globals_vars.USERS:
print(f"User: {globals_vars.USERS[0]}")
print(f"Logging Directory: {globals_vars.LOGGING_DIRECTORY}")
# Confirmation log
if eventlet.patcher.is_monkey_patched('socket'):
print("Eventlet monkey patching confirmed active.")
else:
print("CRITICAL WARNING: Eventlet monkey patching failed. Streaming and cancellation may be unreliable.")
except Exception as e:
print(f"Error resolving port: {e}")
traceback.print_exc()
print("Using default port 5000")
port = 5000
# Get logger AFTER server.py has configured logging
logger = logging.getLogger(__name__)
class TCPNoDelayListener:
"""
Wrapper for Eventlet listener that sets TCP_NODELAY on each accepted connection.
This ensures low-latency streaming by disabling Nagle's algorithm per connection.
"""
def __init__(self, listener):
self._listener = listener
def accept(self):
"""Accept a connection and immediately set TCP_NODELAY on the socket."""
conn, addr = self._listener.accept()
try:
# Eventlet wraps sockets in GreenSocket. Access the underlying socket if needed.
if hasattr(conn, '_sock'):
# GreenSocket: set on the wrapped socket
sock = conn._sock
else:
# Regular socket
sock = conn
# Set TCP_NODELAY to disable Nagle's algorithm (reduces latency)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
# Reduce send buffer to minimize OS-level buffering
# This prevents the OS from batching small packets
sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 4096)
logger.debug(f"TCP_NODELAY set on connection from {addr}")
except Exception as e:
# Best-effort: log but don't fail the connection
logger.warning(f"Could not set TCP_NODELAY on connection from {addr}: {e}")
return conn, addr
def __getattr__(self, name):
"""Delegate all other methods/attributes to the wrapped listener."""
return getattr(self._listener, name)
host = globals_vars.LISTEN_ADDRESS
print(f"\n{'=' * 60}")
print("Starting WilmerAI with Eventlet WSGI Server")
print(f"Listening on {host}:{port}")
print("TCP_NODELAY wrapper installed (applied per connection)")
if host == "127.0.0.1":
print("\n\033[32mUPDATE: WilmerAI now defaults to 127.0.0.1 (localhost")
print("only). It previously defaulted to 0.0.0.0. To listen on")
print("all interfaces, use: --listen\033[0m")
print(f"{'=' * 60}\n")
try:
# Start Eventlet WSGI server
import eventlet.wsgi
# Configure the listener socket
listener = eventlet.listen((host, port))
# Wrap listener to set TCP_NODELAY on each accepted connection
listener = TCPNoDelayListener(listener)
eventlet.wsgi.server(
listener,
application,
log_output=False, # Disable WSGI access logs
debug=False,
# Note: max_size in eventlet.wsgi.server refers to the maximum number of concurrent connections.
max_size=100,
# CRITICAL: Set minimum_chunk_size to 1 to disable WSGI-level batching
# This ensures individual tokens are sent immediately instead of being buffered
minimum_chunk_size=1,
# Disable HTTP keep-alive to force connection teardown after every response.
# Without this, some front-ends (notably Node.js-based apps like SillyTavern)
# can have their HTTP connection pool corrupted by keep-alive connections that
# outlive a streaming response. This ensures the server sends Connection: close
# at the protocol level and closes the socket after the response completes.
keepalive=False,
# Safety net: time out idle client sockets after 60 seconds. Prevents zombie
# connections from accumulating if a client fails to close its end.
socket_timeout=60,
)
except KeyboardInterrupt:
print("\nServer stopped by user")
sys.exit(0)
except Exception as e:
print(f"Eventlet server failed: {e}")
traceback.print_exc()
sys.exit(1)