A whoooole lotta 4.0.x fixes.
This commit is contained in:
289
minimal_flask_crash.py
Normal file
289
minimal_flask_crash.py
Normal file
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Minimal Flask app to isolate the crash.
|
||||
Run with: python minimal_flask_crash.py
|
||||
|
||||
Then test with:
|
||||
curl -X POST -F "carrier=@xx_2.jpg" http://localhost:5001/test1
|
||||
curl -X POST -F "carrier=@xx_2.jpg" http://localhost:5001/test2
|
||||
curl -X POST -F "carrier=@xx_2.jpg" http://localhost:5001/test3
|
||||
"""
|
||||
|
||||
import io
|
||||
import gc
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
# Minimal imports first
|
||||
from flask import Flask, request, jsonify
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB
|
||||
|
||||
# Check for jpegio
|
||||
try:
|
||||
import jpegio as jio
|
||||
HAS_JPEGIO = True
|
||||
print("jpegio: available")
|
||||
except ImportError:
|
||||
HAS_JPEGIO = False
|
||||
print("jpegio: NOT available")
|
||||
|
||||
|
||||
@app.route('/test1', methods=['POST'])
|
||||
def test1_pil_only():
|
||||
"""Test 1: PIL only, no jpegio, no scipy"""
|
||||
carrier = request.files.get('carrier')
|
||||
if not carrier:
|
||||
return jsonify({'error': 'No carrier'}), 400
|
||||
|
||||
data = carrier.read()
|
||||
print(f"[test1] Read {len(data)} bytes")
|
||||
|
||||
img = Image.open(io.BytesIO(data))
|
||||
width, height = img.size
|
||||
fmt = img.format
|
||||
img.close()
|
||||
print(f"[test1] Image: {width}x{height} {fmt}")
|
||||
|
||||
gc.collect()
|
||||
print("[test1] Returning response...")
|
||||
|
||||
return jsonify({
|
||||
'test': 'pil_only',
|
||||
'width': width,
|
||||
'height': height,
|
||||
'format': fmt,
|
||||
})
|
||||
|
||||
|
||||
@app.route('/test2', methods=['POST'])
|
||||
def test2_multiple_opens():
|
||||
"""Test 2: Open image multiple times like compare_modes does"""
|
||||
carrier = request.files.get('carrier')
|
||||
if not carrier:
|
||||
return jsonify({'error': 'No carrier'}), 400
|
||||
|
||||
data = carrier.read()
|
||||
print(f"[test2] Read {len(data)} bytes")
|
||||
|
||||
# First open
|
||||
img1 = Image.open(io.BytesIO(data))
|
||||
width, height = img1.size
|
||||
img1.close()
|
||||
print(f"[test2] Open 1: {width}x{height}")
|
||||
|
||||
# Second open
|
||||
img2 = Image.open(io.BytesIO(data))
|
||||
pixels = img2.size[0] * img2.size[1]
|
||||
img2.close()
|
||||
print(f"[test2] Open 2: {pixels} pixels")
|
||||
|
||||
# Third open
|
||||
img3 = Image.open(io.BytesIO(data))
|
||||
blocks = (img3.size[0] // 8) * (img3.size[1] // 8)
|
||||
img3.close()
|
||||
print(f"[test2] Open 3: {blocks} blocks")
|
||||
|
||||
gc.collect()
|
||||
print("[test2] Returning response...")
|
||||
|
||||
return jsonify({
|
||||
'test': 'multiple_opens',
|
||||
'width': width,
|
||||
'height': height,
|
||||
'pixels': pixels,
|
||||
'blocks': blocks,
|
||||
})
|
||||
|
||||
|
||||
@app.route('/test3', methods=['POST'])
|
||||
def test3_with_jpegio():
|
||||
"""Test 3: Include jpegio operations"""
|
||||
if not HAS_JPEGIO:
|
||||
return jsonify({'error': 'jpegio not available'}), 501
|
||||
|
||||
carrier = request.files.get('carrier')
|
||||
if not carrier:
|
||||
return jsonify({'error': 'No carrier'}), 400
|
||||
|
||||
data = carrier.read()
|
||||
print(f"[test3] Read {len(data)} bytes")
|
||||
|
||||
# Check if JPEG
|
||||
img = Image.open(io.BytesIO(data))
|
||||
is_jpeg = img.format == 'JPEG'
|
||||
width, height = img.size
|
||||
img.close()
|
||||
print(f"[test3] Image: {width}x{height}, JPEG: {is_jpeg}")
|
||||
|
||||
if not is_jpeg:
|
||||
return jsonify({'error': 'Not a JPEG'}), 400
|
||||
|
||||
# Write to temp file
|
||||
fd, temp_path = tempfile.mkstemp(suffix='.jpg')
|
||||
os.write(fd, data)
|
||||
os.close(fd)
|
||||
print(f"[test3] Temp file: {temp_path}")
|
||||
|
||||
try:
|
||||
# Read with jpegio
|
||||
jpeg = jio.read(temp_path)
|
||||
print(f"[test3] jpegio.read() OK")
|
||||
|
||||
coef = jpeg.coef_arrays[0]
|
||||
coef_shape = coef.shape
|
||||
print(f"[test3] Coef shape: {coef_shape}")
|
||||
|
||||
# Count positions like the real code does
|
||||
positions = 0
|
||||
h, w = coef.shape
|
||||
for row in range(h):
|
||||
for col in range(w):
|
||||
if (row % 8 == 0) and (col % 8 == 0):
|
||||
continue
|
||||
if abs(coef[row, col]) >= 2:
|
||||
positions += 1
|
||||
print(f"[test3] Usable positions: {positions}")
|
||||
|
||||
# Cleanup
|
||||
del coef
|
||||
del jpeg
|
||||
print(f"[test3] Deleted jpegio objects")
|
||||
|
||||
finally:
|
||||
os.unlink(temp_path)
|
||||
print(f"[test3] Removed temp file")
|
||||
|
||||
gc.collect()
|
||||
print("[test3] Returning response...")
|
||||
|
||||
return jsonify({
|
||||
'test': 'with_jpegio',
|
||||
'width': width,
|
||||
'height': height,
|
||||
'coef_shape': list(coef_shape),
|
||||
'positions': positions,
|
||||
})
|
||||
|
||||
|
||||
@app.route('/test4', methods=['POST'])
|
||||
def test4_numpy_array_from_pil():
|
||||
"""Test 4: Create numpy array from PIL image (like DCT does)"""
|
||||
carrier = request.files.get('carrier')
|
||||
if not carrier:
|
||||
return jsonify({'error': 'No carrier'}), 400
|
||||
|
||||
data = carrier.read()
|
||||
print(f"[test4] Read {len(data)} bytes")
|
||||
|
||||
img = Image.open(io.BytesIO(data))
|
||||
width, height = img.size
|
||||
print(f"[test4] Image: {width}x{height}")
|
||||
|
||||
# Convert to grayscale and numpy array
|
||||
gray = img.convert('L')
|
||||
arr = np.array(gray, dtype=np.float64, copy=True)
|
||||
print(f"[test4] Array: {arr.shape} {arr.dtype}")
|
||||
|
||||
# Close PIL images
|
||||
gray.close()
|
||||
img.close()
|
||||
print(f"[test4] PIL closed")
|
||||
|
||||
# Do some numpy operations
|
||||
mean_val = float(np.mean(arr))
|
||||
std_val = float(np.std(arr))
|
||||
print(f"[test4] Stats: mean={mean_val:.2f}, std={std_val:.2f}")
|
||||
|
||||
# Clear array
|
||||
del arr
|
||||
gc.collect()
|
||||
print("[test4] Returning response...")
|
||||
|
||||
return jsonify({
|
||||
'test': 'numpy_from_pil',
|
||||
'width': width,
|
||||
'height': height,
|
||||
'mean': mean_val,
|
||||
'std': std_val,
|
||||
})
|
||||
|
||||
|
||||
@app.route('/test5', methods=['POST'])
|
||||
def test5_file_read_keep_reference():
|
||||
"""Test 5: Keep reference to file data in request scope"""
|
||||
carrier = request.files.get('carrier')
|
||||
if not carrier:
|
||||
return jsonify({'error': 'No carrier'}), 400
|
||||
|
||||
# Don't read into local variable - read directly each time
|
||||
# This mimics potential issues with Flask's file handling
|
||||
|
||||
print(f"[test5] File object: {carrier}")
|
||||
|
||||
# Read once
|
||||
carrier.seek(0)
|
||||
data1 = carrier.read()
|
||||
print(f"[test5] First read: {len(data1)} bytes")
|
||||
|
||||
img = Image.open(io.BytesIO(data1))
|
||||
width, height = img.size
|
||||
img.close()
|
||||
|
||||
# Try to read again (should be empty or need seek)
|
||||
data2 = carrier.read()
|
||||
print(f"[test5] Second read (no seek): {len(data2)} bytes")
|
||||
|
||||
carrier.seek(0)
|
||||
data3 = carrier.read()
|
||||
print(f"[test5] Third read (after seek): {len(data3)} bytes")
|
||||
|
||||
gc.collect()
|
||||
print("[test5] Returning response...")
|
||||
|
||||
return jsonify({
|
||||
'test': 'file_handling',
|
||||
'width': width,
|
||||
'height': height,
|
||||
'read1': len(data1),
|
||||
'read2': len(data2),
|
||||
'read3': len(data3),
|
||||
})
|
||||
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
"""Log after each request"""
|
||||
print(f"[after_request] Response status: {response.status}")
|
||||
return response
|
||||
|
||||
|
||||
@app.teardown_request
|
||||
def teardown_request(exception):
|
||||
"""Log during teardown"""
|
||||
if exception:
|
||||
print(f"[teardown] Exception: {exception}")
|
||||
else:
|
||||
print("[teardown] Clean teardown")
|
||||
gc.collect()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("\n" + "=" * 60)
|
||||
print("MINIMAL FLASK CRASH TEST")
|
||||
print("=" * 60)
|
||||
print("\nTest endpoints:")
|
||||
print(" /test1 - PIL only")
|
||||
print(" /test2 - Multiple PIL opens")
|
||||
print(" /test3 - With jpegio")
|
||||
print(" /test4 - NumPy array from PIL")
|
||||
print(" /test5 - File handling test")
|
||||
print("\nUsage:")
|
||||
print(' curl -X POST -F "carrier=@xx_2.jpg" http://localhost:5001/test1')
|
||||
print("=" * 60 + "\n")
|
||||
|
||||
app.run(host='0.0.0.0', port=5001, debug=False, threaded=False)
|
||||
Reference in New Issue
Block a user