[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Feasibility of an async HTTP server engine in Gambas (uvicorn-style) + question about JIT
[Thread Prev] | [Thread Next]
- Subject: Re: Feasibility of an async HTTP server engine in Gambas (uvicorn-style) + question about JIT
- From: Alarch <alarch@xxxxxxxxx>
- Date: Thu, 10 Sep 2026 01:54:46 +0200
- To: user@xxxxxxxxxxxxxxxxxxxxxx
On Fri, 7 Aug 2026 19:56:21 +0200
Benoît Minisini <benoit.minisini@xxxxxxxxxxxxxxxx> wrote:
> Le 07/08/2026 à 00:05, alarch@xxxxxxxxx a écrit :
> > Hello everyone,
> >
> > I've been using Gambas for years for internal tools and server
> > administration scripts, and I'm currently thinking about a project
> > idea I'd like your opinion on.
> >
> > Many Gambas users (myself included) end up relying on PHP for
> > anything web-related, mostly by default rather than by choice. I
> > was wondering whether it would be feasible to build a Gambas-native
> > equivalent of Python's uvicorn — i.e. a standalone application
> > server capable of handling HTTP connections directly (not through
> > CGI/FastCGI), using an event-driven model built on gb.net's Socket
> > class and its ReadyForRead/ ReadyForWrite events.
> >
>
> I think that implementing an HTTP server in Gambas can be a great
> project to do.
>
> Note that you already have the 'gb.httpd' component that transforms
> your project into an HTTP server as soon as you check it.
>
> > The rough architecture I have in mind has three layers:
> >
> > 1. Transport layer: a Socket-based listener handling multiple
> > connections, doing HTTP/1.1 parsing (request line, headers,
> > Content- Length/chunked body, keep-alive) by hand. This is clearly
> > the hardest part to get right, and the one I'd want to validate
> > first.
>
> OK.
>
> >
> > 2. Application contract: a stable interface (Request/Response
> > objects) that application code implements — something like a
> > minimal ASGI/WSGI equivalent, so that once it's defined,
> > application developers don't need to touch the transport layer at
> > all.
> >
> > 3. Routing + middleware: routes table, static file serving, basic
> > logging/auth hooks — the layer that would make it feel like an
> > actual framework rather than a raw socket server.
> >
>
> As all these things an internal API of your HTTP server or an
> external one? I mean: is the application logic implemented inside the
> HTTP server by extending it, or by external processes?
>
> > For concurrency, my current thinking is a single event-loop process
> > per core (à la Node.js/asyncio single worker), with multiple
> > processes behind a reverse proxy (HAProxy/nginx) for multi-core
> > scaling, since Gambas doesn't have true lightweight coroutines.
> >
> > Before going further, I have two questions for people more familiar
> > with the internals:
> >
> > Has anyone already attempted something like this (a persistent
> > event- driven HTTP server in pure Gambas, not CGI-based)? I know
> > gb.web exists but as far as I understand it's oriented towards the
> > CGI/FastCGI model rather than a standalone server process.
>
> Its name is 'gb.web.gui', and I admit it's a curious implementation
> of web applications that I have never seen anywhere else. But I think
> it makes its job.
>
> >
> > About JIT compilation: I recall reading that Benoît implemented
> > some form of JIT in the Gambas runtime. Is this JIT exposed or
> > usable from application code at all, specifically, is there any way
> > to generate and execute code dynamically at runtime (similar to
> > what Smarty/Jinja2 do when compiling templates into native code)?
> >
> > Or is it strictly an internal optimization of gbx3, with no way to
> > leverage it for dynamic codegen from a Gambas application?
> >
> > If dynamic execution isn't possible, I'll go with a classic
> > interpreted template engine (walking a parsed node tree), which
> > works fine but is obviously slower.
> >
>
> I'm not sure to understand what you mean. But 'gb.highlight'
> implements syntax highlighging by transforming a syntax description
> file into a Gambas component that is loaded dynamically, and that is
> JIT optimized.
>
> > Any feedback — even "this is a bad idea because X" — would be very
> > welcome. I'd rather know the real constraints now than discover
> > them halfway through.
> >
> > Thanks in advance,
> >
> > Marc
> >
> >
>
Hello everyone,
Back in August I asked here whether a Gambas-native HTTP application
server was feasible, uvicorn-style. I promised to report back once I
had something worth sharing - here it is.
What I ended up building, based on the feedback from this thread, is
deliberately simpler than what I originally proposed: instead of a
single event-loop process per core, it's a reverse proxy (Apache in my
actual tests, nginx config written but not yet exercised for real) in
front of a static pool of persistent Gambas worker processes - the same
model PHP-FPM uses in "pm = static" mode. Each worker handles one
connection at a time, blocking, using gb.net's Socket class. For
HTTP/1.1 parsing I wrote a small binding around llhttp (the C parser
Node.js itself uses) rather than parsing by hand - a new component, not
part of Gambas today, happy to share the source if anyone's interested.
Config (socket path, backlog, DB credentials, worker-recycling limits)
is read from an INI file via gb.settings, one file per worker instance,
closer to a pool.conf than anything hardcoded.
The main question I wanted an answer to wasn't "can this work" but "can
this hold real, unforgiving load" - so I put together a proper test
rather than a toy benchmark:
- A real production-scale MariaDB dataset (~750k and ~1.1M row tables,
imported from an actual client database, business specifics anonymized
here), joined on indexed foreign keys.
- A worker route returning JSON from that join, with a random parameter
on every single request specifically to defeat any query-cache
shortcut - every request does the real join, every time.
- An external load generator (loader.io) rather than running the load
tool on the same box as the server, to avoid the client and server
fighting over the same CPU (I made that mistake on a first,
single-core test VPS, and the results were meaningless because of it).
- An 8-core VPS, with the exact same worker code and the exact same
query, compared at 1, 4 and 8 worker processes - only the pool size
changes between runs.
Results, sustained for a full minute each time, no caching layer
anywhere, query time measured server-side:
1 worker, 1000 req/s : ~99.2% success, ~0.5-1 ms query time
4 workers, 2500 req/s : 100% success, ~0.8 ms query time
4 workers, 3000-4000 req/s : the pool starts failing (down to ~50-87%
success depending on an Apache-side
misconfiguration on my end I found and fixed
along the way - a leftover legacy MaxClients
directive silently capping Apache itself well
below its intended concurrency, nothing to do
with the Gambas side)
8 workers, 4000 req/s : 100% success, 0.87 ms average, 20 ms max
So 4 workers found their ceiling somewhere between 2500 and 3000-4000
req/s, and doubling to 8 workers pushed that same 4000 req/s point back
to a clean 100% - the scaling behaved exactly the way you'd want it to.
At 8 workers the box itself was sitting at roughly 5% overall CPU
during the 4000 req/s run. For a sense of scale, that's already well
beyond what a typical internal business application would ever see in
real traffic - this was very deliberately pushed far past any realistic
need, to find where it actually breaks rather than where I assumed it
would.
Two real Gambas gotchas came out of getting the worker itself right,
which I reported separately (one thread each, per the list's
convention): a native component's object struct must start with GB_BASE
or writes silently corrupt gbx3's own bookkeeping with a crash that
looks unrelated, and Object.Attach silently no-ops on a plain .class
observer (it requires the built-in Observer class or an Event of your
own). Neither was obvious from the outside, and both cost real
debugging time before the root cause turned out to be simple.
What this does and doesn't prove, since I'd rather be precise than
enthusiastic: this is a JSON/API workload - GET requests, no
templating, no forms, no file uploads, no cache. Serving actual HTML
pages the way a "complete" web framework would still needs a templating
layer, a caching strategy and real form/session handling, none of which
exist yet - that's a separate, unstarted piece of work, not something
this test says anything about. It also doesn't say anything about
workloads with lots of idle connections (WebSocket, SSE, long-polling),
which was the original reason I was considering an async design in the
first place - that question is still completely open.
But for the specific question of "can Gambas serve a real API under
serious load without an async runtime" - the answer, at least for this
workload, is a clear yes. I found that genuinely encouraging, and
wanted to close the loop on the thread that started it.
Thanks again to everyone who chimed in back in August.
Best regards,
--
Marc