Connection Draining: Graceful Close¶
Overview¶
When closing HTTP connections from the server side, there are two methods:
conn.close(): Immediate close (may lose buffered data)conn.drain(): Graceful close (flushes data first) [x] Recommended
The Problem¶
If you close a connection immediately after sending a response, the client may not receive the full data:
# [X] BAD: May not send complete response
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
conn.reply(200, b"Large response..." * 1000)
conn.close() # Closes immediately!
The issue: close() immediately tears down the connection, even if there's data in the send buffer.
The Solution: drain()¶
Use conn.drain() to mark the connection for graceful shutdown:
# [x] GOOD: Ensures response is fully sent
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
conn.reply(200, b"Large response..." * 1000)
conn.drain() # Closes after send buffer empties
What drain() Does¶
- Sets
conn.is_draining = 1- Marks connection for closure - Stops reading - No more data accepted from client
- Flushes send buffer - Continues sending buffered data
- Closes when empty - Connection closes after all data sent
This is the Mongoose-recommended pattern for server-initiated closes.
API Reference¶
conn.drain()¶
def drain(self):
"""Mark connection for graceful closure.
Sets is_draining=1, which tells Mongoose to:
1. Stop reading from the socket
2. Flush any buffered outgoing data
3. Close the connection after send buffer is empty
This is the recommended way to close connections from the server side.
"""
When to use:
- [x] After sending HTTP response
- [x] After sending last WebSocket message
- [x] When you want client to receive all data
conn.close()¶
def close(self):
"""Immediately close the connection.
For graceful shutdown, use drain() instead.
"""
When to use:
- [!] Handling protocol violations
- [!] Malicious connections (timeout/abuse)
- [!] Emergency shutdown
Avoid for normal responses - use drain() instead.
conn.is_draining (property)¶
Read-only property to check if connection is marked for drainage.
Usage Patterns¶
HTTP Server: One-shot Response¶
Most common pattern - send response and close:
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
conn.reply(200, b"Hello, World!")
conn.drain() # Close after response sent
HTTP Server: Keep-Alive¶
Don't call drain() if you want connection reuse:
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
conn.reply(200, b"Hello, World!")
# No drain() - connection stays open for next request
HTTP/1.1 keep-alive will automatically manage the connection.
WebSocket: Graceful Disconnect¶
Send close frame, then drain:
def handler(conn, ev, data):
if ev == MG_EV_WS_MSG:
if should_disconnect():
conn.ws_send("Goodbye!", WEBSOCKET_OP_TEXT)
conn.drain() # Close after message sent
Checking Drain State¶
Monitor connection state:
def handler(conn, ev, data):
if ev == MG_EV_POLL:
if conn.is_draining:
print("Connection is draining...")
Examples¶
Example 1: HTTP Server with Drain¶
from cymongoose import Manager, MG_EV_HTTP_MSG
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
# Log request
print(f"{data.method} {data.uri}")
# Send response
conn.reply(
200,
b'{"status":"ok"}',
headers={"Content-Type": "application/json"}
)
# Graceful close after response
conn.drain()
manager = Manager(handler)
manager.listen('http://0.0.0.0:8000', http=True)
while True:
manager.poll(100)
Example 2: Conditional Drain¶
Close connection only for specific paths:
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
if data.uri == "/quit":
conn.reply(200, b"Goodbye!")
conn.drain() # Close this connection
else:
conn.reply(200, b"Hello!")
# Keep connection alive
Example 3: Drain All Connections on Shutdown¶
Gracefully close all connections during server shutdown:
shutdown_requested = False
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
if shutdown_requested:
conn.reply(503, b"Server shutting down")
conn.drain()
else:
conn.reply(200, b"OK")
# ... shutdown logic sets shutdown_requested = True
Common Mistakes¶
[X] DON'T: Drain after every response (HTTP/1.1)¶
# BAD: Prevents connection reuse
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
conn.reply(200, b"OK")
conn.drain() # [X] Closes connection every time
This disables HTTP keep-alive and forces new connections for each request.
[X] DON'T: Use close() for normal responses¶
# BAD: May lose data
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
conn.reply(200, large_response)
conn.close() # [X] Immediate close may truncate response
[x] DO: Use drain() when closing from server¶
# GOOD: Ensures complete data delivery
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
conn.reply(200, large_response)
conn.drain() # [x] Graceful close
[x] DO: Let HTTP keep-alive manage connections¶
# GOOD: Connection reuse enabled
def handler(conn, ev, data):
if ev == MG_EV_HTTP_MSG:
conn.reply(200, b"OK")
# No drain() - connection stays open
Performance Considerations¶
Drain vs Close Performance¶
drain(): Adds ~1-10ms latency (time to flush buffers)close(): Instant, but may lose data
For most HTTP responses (< 10KB), the difference is negligible (< 1ms).
Connection Reuse¶
HTTP/1.1 keep-alive reuses connections:
- Without drain: ~60,000 req/sec (benchmark result)
- With drain every request: ~45,000 req/sec (more TCP overhead)
Best practice: Only drain when actually closing the connection (e.g., WebSocket disconnect, server shutdown, or explicit "Connection: close" header).
Under the Hood¶
What Mongoose does when is_draining = 1:
MG_EV_POLLevents continue - Connection stays in event loopmg_iobuf_del(&c->recv, c->recv.len)- Clear receive buffermg_send()still works - Can still send data- When
c->send.len == 0: Callsmg_close_conn()
This ensures buffered data is flushed before closing.
Summary¶
| Method | Use Case | Data Loss Risk | Latency |
|---|---|---|---|
conn.drain() |
[x] Normal server-initiated close | None | +1-10ms |
conn.close() |
[!] Emergency/protocol violation | Possible | Instant |
| (no call) | [x] HTTP keep-alive | N/A | N/A |
Default recommendation: Use drain() when you need to close a connection after sending data. Otherwise, let HTTP keep-alive manage connections automatically.
See also:
- Cleanup & Shutdown - Manager cleanup
- Graceful Shutdown - Graceful server shutdown