Files
iLSP/ilsp/server.py

96 lines
2.8 KiB
Python
Raw Normal View History

"""
iLSP main entrypoint.
Starts three services concurrently:
- pylsp server on TCP :2087 (Python LSP + i80 completions)
- Bicep proxy on TCP :2088 (Bicep LS wrapper + LRU modules)
- Health HTTP on TCP :2089 (for Consul/Nomad health checks)
"""
import asyncio
import logging
import os
import signal
import threading
from aiohttp import web
from .python_lsp.catalog import PypiCatalog
from .bicep_lsp.proxy import serve_bicep
logger = logging.getLogger(__name__)
PYTHON_LSP_PORT = int(os.getenv("PYTHON_LSP_PORT", "2087"))
BICEP_LSP_PORT = int(os.getenv("BICEP_LSP_PORT", "2088"))
HEALTH_PORT = int(os.getenv("HEALTH_PORT", "2089"))
async def _health_app() -> web.Application:
app = web.Application()
async def health(_: web.Request) -> web.Response:
pypi_count = len(PypiCatalog._packages)
from .bicep_lsp.modules import BicepModuleCatalog
bicep_count = len(BicepModuleCatalog._modules)
return web.json_response({
"status": "ok",
"pypi_packages": pypi_count,
"bicep_modules": bicep_count,
})
app.router.add_get("/health", health)
return app
async def _serve_python_lsp(port: int) -> None:
"""Start pylsp in TCP server mode."""
import subprocess, sys
proc = await asyncio.create_subprocess_exec(
sys.executable, "-m", "pylsp",
"--tcp", "--host", "0.0.0.0", "--port", str(port),
)
logger.info("Python LSP (pylsp) listening on TCP :%d PID=%d", port, proc.pid)
await proc.wait()
logger.warning("pylsp exited — restarting")
async def main_async() -> None:
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logging.getLogger("ilsp.bicep_lsp.proxy").setLevel(logging.DEBUG)
# Pre-warm caches
logger.info("Pre-warming catalogs…")
from .bicep_lsp.modules import BicepModuleCatalog
await asyncio.gather(
PypiCatalog.start_background_refresh(),
BicepModuleCatalog.start_background_refresh(),
)
# Start Bicep LSP proxy in a daemon thread (blocking socket server)
threading.Thread(
target=serve_bicep,
args=(BICEP_LSP_PORT,),
daemon=True,
).start()
# Build health app
health_app = await _health_app()
runner = web.AppRunner(health_app)
await runner.setup()
site = web.TCPSite(runner, "0.0.0.0", HEALTH_PORT)
await site.start()
logger.info("Health endpoint on http://0.0.0.0:%d/health", HEALTH_PORT)
# Run remaining asyncio services
await _serve_python_lsp(PYTHON_LSP_PORT)
def main() -> None:
loop = asyncio.new_event_loop()
for sig in (signal.SIGTERM, signal.SIGINT):
loop.add_signal_handler(sig, loop.stop)
loop.run_until_complete(main_async())