0
# Exception Handling
1
2
Comprehensive exception system for handling Engine.IO-specific errors and connection issues. All exceptions inherit from the base `EngineIOError` class for consistent error handling patterns.
3
4
## Capabilities
5
6
### Base Exception Class
7
8
Foundation exception class for all Engine.IO-related errors with standard Python exception behavior.
9
10
```python { .api }
11
class EngineIOError(Exception):
12
"""
13
Base exception class for Engine.IO errors.
14
15
All Engine.IO-specific exceptions inherit from this class,
16
allowing for broad exception handling when needed.
17
"""
18
```
19
20
### Content Size Errors
21
22
Exception raised when message content exceeds configured size limitations.
23
24
```python { .api }
25
class ContentTooLongError(EngineIOError):
26
"""
27
Raised when message content exceeds size limits.
28
29
This error occurs when:
30
- Message data is larger than max_http_buffer_size
31
- Packet payload exceeds transport limitations
32
- Binary data exceeds configured thresholds
33
"""
34
```
35
36
### Protocol Errors
37
38
Exception for handling unknown or malformed packet types during communication.
39
40
```python { .api }
41
class UnknownPacketError(EngineIOError):
42
"""
43
Raised when an unknown packet type is encountered.
44
45
This error indicates:
46
- Received packet with invalid type identifier
47
- Protocol version mismatch between client and server
48
- Corrupted packet data during transmission
49
"""
50
```
51
52
### Queue Management Errors
53
54
Exception for empty queue operations in multi-threaded or async environments.
55
56
```python { .api }
57
class QueueEmpty(EngineIOError):
58
"""
59
Raised when attempting to get from an empty queue.
60
61
This error occurs when:
62
- Non-blocking queue operations find no items
63
- Background task queue is exhausted
64
- Message buffer is empty during read operations
65
"""
66
```
67
68
### Socket State Errors
69
70
Exception for operations attempted on closed or invalid socket connections.
71
72
```python { .api }
73
class SocketIsClosedError(EngineIOError):
74
"""
75
Raised when trying to operate on a closed socket.
76
77
This error indicates:
78
- Attempting to send data through closed connection
79
- Socket was disconnected during operation
80
- Connection was terminated by client or server
81
"""
82
```
83
84
### Connection Errors
85
86
Exception for connection establishment and maintenance failures.
87
88
```python { .api }
89
class ConnectionError(EngineIOError):
90
"""
91
Raised when connection fails or is lost.
92
93
This error occurs during:
94
- Initial connection establishment failures
95
- Network connectivity issues
96
- Server unavailability or rejection
97
- Transport-specific connection problems
98
"""
99
```
100
101
## Exception Handling Patterns
102
103
### Basic Exception Handling
104
105
```python
106
import engineio
107
108
eio = engineio.Client()
109
110
try:
111
eio.connect('http://localhost:5000')
112
eio.send('Hello Server!')
113
except engineio.ConnectionError as e:
114
print(f'Connection failed: {e}')
115
except engineio.SocketIsClosedError as e:
116
print(f'Socket closed during operation: {e}')
117
except engineio.EngineIOError as e:
118
print(f'Engine.IO error occurred: {e}')
119
except Exception as e:
120
print(f'Unexpected error: {e}')
121
```
122
123
### Server-Side Exception Handling
124
125
```python
126
import engineio
127
128
eio = engineio.Server()
129
130
@eio.on('message')
131
def on_message(sid, data):
132
try:
133
# Process message data
134
result = process_data(data)
135
eio.send(sid, result)
136
except engineio.ContentTooLongError:
137
eio.send(sid, {'error': 'Message too large'})
138
except engineio.SocketIsClosedError:
139
print(f'Client {sid} disconnected during processing')
140
except engineio.EngineIOError as e:
141
print(f'Engine.IO error for client {sid}: {e}')
142
eio.send(sid, {'error': 'Processing failed'})
143
except Exception as e:
144
print(f'Unexpected error processing message from {sid}: {e}')
145
eio.send(sid, {'error': 'Internal server error'})
146
```
147
148
### Async Exception Handling
149
150
```python
151
import engineio
152
import asyncio
153
154
eio = engineio.AsyncClient()
155
156
@eio.on('connect')
157
async def on_connect():
158
try:
159
await eio.send('Hello Async Server!')
160
except engineio.SocketIsClosedError:
161
print('Connection closed before sending message')
162
except engineio.EngineIOError as e:
163
print(f'Failed to send initial message: {e}')
164
165
async def main():
166
try:
167
await eio.connect('http://localhost:5000')
168
await eio.wait()
169
except engineio.ConnectionError as e:
170
print(f'Connection error: {e}')
171
# Implement retry logic
172
await asyncio.sleep(5)
173
try:
174
await eio.connect('http://localhost:5000')
175
except engineio.ConnectionError:
176
print('Retry failed, giving up')
177
except engineio.EngineIOError as e:
178
print(f'Engine.IO error: {e}')
179
180
asyncio.run(main())
181
```
182
183
### Comprehensive Error Handling
184
185
```python
186
import engineio
187
import logging
188
189
# Configure logging for error tracking
190
logging.basicConfig(level=logging.INFO)
191
logger = logging.getLogger(__name__)
192
193
eio = engineio.Server(logger=True)
194
195
@eio.on('connect')
196
def on_connect(sid, environ):
197
logger.info(f'Client {sid} connected')
198
199
@eio.on('message')
200
def on_message(sid, data):
201
try:
202
# Validate message size
203
if len(str(data)) > 10000: # Custom size check
204
raise engineio.ContentTooLongError('Message exceeds custom limit')
205
206
# Process message
207
response = {'echo': data, 'timestamp': time.time()}
208
eio.send(sid, response)
209
210
except engineio.ContentTooLongError as e:
211
logger.warning(f'Content too long from {sid}: {e}')
212
try:
213
eio.send(sid, {'error': 'Message too large', 'code': 'CONTENT_TOO_LONG'})
214
except engineio.SocketIsClosedError:
215
logger.info(f'Cannot send error response, client {sid} disconnected')
216
217
except engineio.SocketIsClosedError as e:
218
logger.info(f'Client {sid} socket closed: {e}')
219
220
except engineio.UnknownPacketError as e:
221
logger.error(f'Protocol error from {sid}: {e}')
222
try:
223
eio.disconnect(sid)
224
except engineio.EngineIOError:
225
pass # Already disconnected
226
227
except engineio.EngineIOError as e:
228
logger.error(f'Engine.IO error for {sid}: {e}')
229
try:
230
eio.send(sid, {'error': 'Server error', 'code': 'SERVER_ERROR'})
231
except engineio.EngineIOError:
232
logger.error(f'Failed to send error response to {sid}')
233
234
except Exception as e:
235
logger.exception(f'Unexpected error processing message from {sid}')
236
try:
237
eio.send(sid, {'error': 'Internal error', 'code': 'INTERNAL_ERROR'})
238
except engineio.EngineIOError:
239
logger.error(f'Failed to send error response to {sid}')
240
241
@eio.on('disconnect')
242
def on_disconnect(sid):
243
logger.info(f'Client {sid} disconnected')
244
```
245
246
### Context Manager for Error Handling
247
248
```python
249
import engineio
250
from contextlib import contextmanager
251
252
@contextmanager
253
def engineio_error_handler(client_id=None):
254
"""Context manager for standardized Engine.IO error handling"""
255
try:
256
yield
257
except engineio.ConnectionError as e:
258
print(f'Connection error{f" for {client_id}" if client_id else ""}: {e}')
259
raise
260
except engineio.SocketIsClosedError as e:
261
print(f'Socket closed{f" for {client_id}" if client_id else ""}: {e}')
262
except engineio.ContentTooLongError as e:
263
print(f'Content too long{f" for {client_id}" if client_id else ""}: {e}')
264
except engineio.UnknownPacketError as e:
265
print(f'Protocol error{f" for {client_id}" if client_id else ""}: {e}')
266
except engineio.QueueEmpty as e:
267
print(f'Queue empty{f" for {client_id}" if client_id else ""}: {e}')
268
except engineio.EngineIOError as e:
269
print(f'Engine.IO error{f" for {client_id}" if client_id else ""}: {e}')
270
271
# Usage example
272
eio = engineio.Client()
273
274
with engineio_error_handler('client-1'):
275
eio.connect('http://localhost:5000')
276
eio.send('Hello!')
277
```
278
279
### Exception Handling in Event Handlers
280
281
```python
282
import engineio
283
import functools
284
285
def handle_exceptions(handler):
286
"""Decorator for event handler exception handling"""
287
@functools.wraps(handler)
288
def wrapper(*args, **kwargs):
289
try:
290
return handler(*args, **kwargs)
291
except engineio.EngineIOError as e:
292
print(f'Engine.IO error in {handler.__name__}: {e}')
293
except Exception as e:
294
print(f'Unexpected error in {handler.__name__}: {e}')
295
return wrapper
296
297
def handle_async_exceptions(handler):
298
"""Decorator for async event handler exception handling"""
299
@functools.wraps(handler)
300
async def wrapper(*args, **kwargs):
301
try:
302
return await handler(*args, **kwargs)
303
except engineio.EngineIOError as e:
304
print(f'Engine.IO error in {handler.__name__}: {e}')
305
except Exception as e:
306
print(f'Unexpected error in {handler.__name__}: {e}')
307
return wrapper
308
309
eio = engineio.Server()
310
311
@eio.on('connect')
312
@handle_exceptions
313
def on_connect(sid, environ):
314
# Handler code that might raise exceptions
315
risky_operation(sid)
316
317
@eio.on('message')
318
@handle_exceptions
319
def on_message(sid, data):
320
# Message processing that might fail
321
process_message(sid, data)
322
```
323
324
## Error Recovery Strategies
325
326
### Automatic Reconnection
327
328
```python
329
import engineio
330
import time
331
import random
332
333
class ResilientClient:
334
def __init__(self, url, max_retries=5):
335
self.url = url
336
self.max_retries = max_retries
337
self.client = engineio.Client()
338
self.setup_handlers()
339
340
def setup_handlers(self):
341
@self.client.on('disconnect')
342
def on_disconnect():
343
print('Disconnected, attempting to reconnect...')
344
self.reconnect()
345
346
def connect(self):
347
"""Initial connection with retry logic"""
348
for attempt in range(self.max_retries):
349
try:
350
self.client.connect(self.url)
351
print('Connected successfully')
352
return True
353
except engineio.ConnectionError as e:
354
print(f'Connection attempt {attempt + 1} failed: {e}')
355
if attempt < self.max_retries - 1:
356
# Exponential backoff with jitter
357
delay = (2 ** attempt) + random.uniform(0, 1)
358
time.sleep(delay)
359
return False
360
361
def reconnect(self):
362
"""Reconnection logic"""
363
time.sleep(5) # Initial delay
364
if self.connect():
365
print('Reconnected successfully')
366
else:
367
print('Reconnection failed after all attempts')
368
369
def send_safe(self, data):
370
"""Send with error handling"""
371
try:
372
self.client.send(data)
373
return True
374
except engineio.SocketIsClosedError:
375
print('Cannot send, socket is closed')
376
return False
377
except engineio.EngineIOError as e:
378
print(f'Send failed: {e}')
379
return False
380
381
# Usage
382
client = ResilientClient('http://localhost:5000')
383
if client.connect():
384
client.send_safe('Hello!')
385
```
386
387
### Circuit Breaker Pattern
388
389
```python
390
import engineio
391
import time
392
from enum import Enum
393
394
class CircuitState(Enum):
395
CLOSED = "closed"
396
OPEN = "open"
397
HALF_OPEN = "half_open"
398
399
class CircuitBreaker:
400
def __init__(self, failure_threshold=5, recovery_timeout=60):
401
self.failure_threshold = failure_threshold
402
self.recovery_timeout = recovery_timeout
403
self.failure_count = 0
404
self.last_failure_time = None
405
self.state = CircuitState.CLOSED
406
407
def call(self, func, *args, **kwargs):
408
"""Execute function with circuit breaker protection"""
409
if self.state == CircuitState.OPEN:
410
if time.time() - self.last_failure_time > self.recovery_timeout:
411
self.state = CircuitState.HALF_OPEN
412
else:
413
raise engineio.ConnectionError("Circuit breaker is OPEN")
414
415
try:
416
result = func(*args, **kwargs)
417
if self.state == CircuitState.HALF_OPEN:
418
self.state = CircuitState.CLOSED
419
self.failure_count = 0
420
return result
421
except engineio.EngineIOError:
422
self.failure_count += 1
423
self.last_failure_time = time.time()
424
425
if self.failure_count >= self.failure_threshold:
426
self.state = CircuitState.OPEN
427
raise
428
429
# Usage
430
eio = engineio.Client()
431
circuit_breaker = CircuitBreaker()
432
433
def safe_connect():
434
return circuit_breaker.call(eio.connect, 'http://localhost:5000')
435
436
def safe_send(data):
437
return circuit_breaker.call(eio.send, data)
438
```
439
440
## Logging and Monitoring
441
442
### Enhanced Error Logging
443
444
```python
445
import engineio
446
import logging
447
import traceback
448
from datetime import datetime
449
450
class EngineIOErrorLogger:
451
def __init__(self, logger_name='engineio_errors'):
452
self.logger = logging.getLogger(logger_name)
453
self.setup_logging()
454
455
def setup_logging(self):
456
handler = logging.StreamHandler()
457
formatter = logging.Formatter(
458
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
459
)
460
handler.setFormatter(formatter)
461
self.logger.addHandler(handler)
462
self.logger.setLevel(logging.INFO)
463
464
def log_exception(self, exception, context=None):
465
"""Log Engine.IO exception with context"""
466
error_type = type(exception).__name__
467
timestamp = datetime.now().isoformat()
468
469
log_data = {
470
'timestamp': timestamp,
471
'error_type': error_type,
472
'error_message': str(exception),
473
'context': context or {}
474
}
475
476
if isinstance(exception, engineio.ConnectionError):
477
self.logger.error(f'Connection Error: {log_data}')
478
elif isinstance(exception, engineio.SocketIsClosedError):
479
self.logger.warning(f'Socket Closed: {log_data}')
480
elif isinstance(exception, engineio.ContentTooLongError):
481
self.logger.warning(f'Content Too Long: {log_data}')
482
elif isinstance(exception, engineio.EngineIOError):
483
self.logger.error(f'Engine.IO Error: {log_data}')
484
self.logger.debug(traceback.format_exc())
485
else:
486
self.logger.error(f'Unexpected Error: {log_data}')
487
self.logger.debug(traceback.format_exc())
488
489
# Usage
490
error_logger = EngineIOErrorLogger()
491
eio = engineio.Server()
492
493
@eio.on('message')
494
def on_message(sid, data):
495
try:
496
# Process message
497
pass
498
except engineio.EngineIOError as e:
499
error_logger.log_exception(e, {'sid': sid, 'data_type': type(data).__name__})
500
```