DIY Gemini Servers...
I am kind of tired of bending over backwards to code CGI games running behind a server built for an entirely different purpose.
I am just going to write my own. This is easier than what I am doing now, opening and closing users and game state databases and loading the same stupid files for every click.
The game logic is really, really fast with the game pre-loaded. User data is likewise in RAM. The only variable is the network.
I could write a simple async server which could handle like 10,000 in-flight concurrent requests. It's likely an overkill, since I doubt I've had any concurrent requests so far. Or maybe a couple.
This has the added advantage of being a single process -- and avoiding all kinds of concurrency issues should I ever get concurrent requests. Otherwise I would just spin up maybe 5 processes and be done, but then I have to do some kind of shared memory and locks, and have the limit of 5 instead of thousands of requests...
I did start that way and have a working test daemon with multiple processes. It's 150 lines of C (just a hello world thing).
An async server is a bit more involved, but a lot of the complexity (connection management) is merged with the per-user game state anyway.
It should work. And I will never touch CGI again. Assuming I can keep my own machine going.
Sep 04 · 3 months ago · 👍 norayr, Breebee
11 Comments ↓
Sounds like a fun project. Do you have a place you're putting the code? I'd love to see it once it's ready.
I will keep it at tildegit under 'stack'.
There is another project there which is a domain socket server which keeps user state in memory for CGI games to connect to, to be used as a basis for a game server. But it got pretty convoluted to work with.
A trivial Gemini server should be better. It doesn't have to parse URLs or deal with the filesystem. The only challenge is dealing with slow connections and potential DOS
I am not totally sure about the best direction yet.
An async single process/thread server is ideal for not worrying about synchronizing access to game data, but is a little more complicated than I would prefer an an overkill for a game with maybe 20 daily users.
A simple responder is sufficient except a slow client would delay everyone else.
Maybe I could partition the players into 4 threads/processes based on 2 low bits of client certificate hash. There is no cross-player interaction so each would keep its own in-memory data, no worries about stepping kn each other. A slow client would affect 1/4 of the users.
On the other hand, the async complexity is a couple of hundred extra lines of C that I would never look at again, keeps things simple with a single process/thread for game logic and gives me five orders of magitude of slack. Also much better for dealing with multiple slow connections.
it would be amazing if you implement something like apache dso, so that your server would load shared object and that shared object would be your gemini program.
then other people can write gemini programs as shared objects for your server.
I was thinking the other way around -- the server is just a tiny library that links into your application.
I don't want other people to write shared objects on my server. In fact that sounds like a nightmare!
So, that's what the original intent of the 'libpxd' portion of my libpxd/polluxd server is - to allow you to build a gemini server into other applications. Then I ended up writing polluxd to exercise the capabilities. I am in the process of also building gemini functionality into the lorien program (maintained by kona over at hashnix) with libpxd to do exactly the kind of thing you're talking about. If that helps give you any ideas...
It is probably also worth mentioning that the documentation for that part of the libpxd project is kind of under-developed so far, but I am always happy to help if anyone is interested in using it that way.
@ingrix, I finally had a chance to look at libpxd a bit, and it's pretty close to my original thinking.
I've been trying to come up with a minimal solution for adding server capability to a minimal game and trying out some tests...
I would love to keep it down to a single process to avoid synchronization issues, and keep all data in RAM. This rules out CGI. To keep slow connections from DOSing the game, there are two options: traditional non-blocking events and NBIO via KTLS, which is pretty new but should work.
libpxd appears to be non-blocking, and I put together a simpler version of it as well. However KTLS seems like the way to go if I can get it to work.
It would avoid all the clumsiness with WANT_READ/WANT_WRITE stuff. Also move a lot of stuff into the kernel and avoid some copying, hopefully making the thing more lightweight.
I will try to slap together a proof-of-concept to see if it works.
Unfortunately it looks like I will have to recompile OpenSSL to enable it as stock Ubuntu one seems to not have enable-ktls...
KTLS/async in kernel is not really an option.
I thought it would be -- it's been around like a couple of years! But on my AWS Ubuntu 24.04 instance, OpenSSL is compile without ktls, and the kernel is not configured to do kTLS.
Took me half a day to figure it out -- after a ton of looking around headers and kernel modules I even recompiled OpenSSL with ktls only to find out it did not actually compile because the kernels is stubbed -- reporting that it does support ktls but actually doing nothing. Why? I don't know.
So back to non-blocking sockets and old event loop.
@stack darn, that's a shame. I had read about KTLS a while back and forgot about it. If you had gotten it to work that would have been awesome. I guess good luck with the old-fashioned method in the meantime though, I'm happy to bounce ideas around with you if you need, too.
@ingrix -- thanks, I may bother you with some questions. And your code is pretty good too.
I think I could probably get ktls to work on my dev box, but with all the hosting issues it's probably better to wait till it's more mainstreamed.