feat: implement asyncio exception handler and update entrypoint script for process management

This commit is contained in:
David Montero Crespo 2026-04-16 00:57:30 -03:00
parent df37fdf6a4
commit f5ef107eaa
6 changed files with 49 additions and 5 deletions

View File

@ -26,8 +26,29 @@ import app.models.user # noqa: F401
import app.models.project # noqa: F401
logger = logging.getLogger(__name__)
def _asyncio_exception_handler(loop: asyncio.AbstractEventLoop, context: dict) -> None:
"""Prevent unhandled asyncio task exceptions from killing the uvicorn process.
Normally uvicorn re-raises unhandled task exceptions at the event-loop level,
which can crash the whole process. The main culprit is a race condition in
websockets <12.0 (legacy/protocol.py AssertionError during keepalive ping).
Upgrading websockets>=12.0 is the primary fix; this handler is a safety net.
"""
exc = context.get("exception")
msg = context.get("message", "")
if exc is not None:
logger.error("Unhandled asyncio task exception (swallowed): %s%r", msg, exc)
else:
# No exception object — let default handler deal with it
loop.default_exception_handler(context)
@asynccontextmanager
async def lifespan(_app: FastAPI):
asyncio.get_event_loop().set_exception_handler(_asyncio_exception_handler)
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
# Add is_admin column to existing databases that predate this feature

View File

@ -1,5 +1,6 @@
fastapi==0.115.0
uvicorn[standard]==0.32.0
websockets>=12.0
sqlalchemy==2.0.36
aiosqlite==0.20.0
pydantic>=2.11.0

View File

@ -39,10 +39,22 @@ fi
# Start FastAPI backend in the background on port 8001
echo "🚀 Starting Velxio Backend..."
uvicorn app.main:app --host 127.0.0.1 --port 8001 &
UVICORN_PID=$!
# Wait for backend to be healthy (optional but good practice)
# Wait for backend to be healthy before starting nginx
sleep 2
# Start Nginx in the foreground to keep the container running
# Start Nginx in the background (not exec — we need to monitor both)
echo "🌐 Starting Nginx Web Server on port 80..."
exec nginx -g "daemon off;"
nginx -g "daemon off;" &
NGINX_PID=$!
# Exit as soon as either process dies so Docker can restart the container.
# wait -n requires bash 4.3+ (standard on Debian Bullseye / Ubuntu 20.04+).
wait -n $UVICORN_PID $NGINX_PID
EXIT_CODE=$?
echo "⚠️ A process exited (code $EXIT_CODE) — shutting down container"
kill $UVICORN_PID $NGINX_PID 2>/dev/null || true
wait $UVICORN_PID $NGINX_PID 2>/dev/null || true
exit $EXIT_CODE

View File

@ -1,6 +1,6 @@
{
"version": "1.0.0",
"generatedAt": "2026-04-15T22:57:24.963Z",
"generatedAt": "2026-04-16T03:35:24.515Z",
"components": [
{
"thumbnail": "<svg width=\"64\" height=\"64\" xmlns=\"http://www.w3.org/2000/svg\">\n <rect width=\"64\" height=\"64\" fill=\"#e0e0e0\" rx=\"4\"/>\n <text x=\"50%\" y=\"50%\" text-anchor=\"middle\" dy=\".3em\" font-size=\"10\" fill=\"#666\">\n DIODE-1N4007\n </text>\n </svg>",

View File

@ -332,10 +332,19 @@ class MetadataGenerator {
const type = member.type?.getText() || 'any';
const defaultValue = member.initializer?.getText();
let resolvedDefault: unknown;
if (defaultValue) {
try {
resolvedDefault = eval(defaultValue);
} catch {
// Initializer references an identifier not in scope (e.g. imported constant)
resolvedDefault = undefined;
}
}
properties.push({
name,
type,
defaultValue: defaultValue ? eval(defaultValue) : undefined,
defaultValue: resolvedDefault,
});
}
}

@ -0,0 +1 @@
Subproject commit a0c3b1ab9a4e83ebeff4081b74ecef89bc9ff83c