Parallel activity in the web-server for a game

I'm still working on the minimal multiplayer game to be run from the Racket web-server.
I have a few questions in the following big post, most of which is to establish context.
I'm hung up on the various forms of parallelism in Racket (and (I suppose) in javascript, although I'm not there yet).

I'm planning to maintain a single copy of game state in the Racket web-server. I don't (at present) plan to place it in permanent long-term storage, because I don't expect any of hese games to last long enough for crash-recovery to be relevant.

The players will be connecting from various machines across the internet, using various browsers, likely versions of firefox, chrome, chromium, edge, and maybe even internet explorer. They will probably be using different operating systems, likely Linux and Windows.

The players will also be using an out-of-band voice communications channel, likely jitsi or discord. At least I don't have to worry about that for now.

Everything went smoothly until I realised that for continual updating of relevant game state on the player machines I could not rely on http (or https) providing multiple responses to one request, repeatedly updating the game display as things changed. I would have to use a timer and make repeated update requests every second or so. With current browser technology, this requires client-side javascript, which I have asked about elsewhere. I'm not asking for advice on javascript now, though if anyone has any to offer, I won't mind reading it.

This is not a massively multiplayer game. I'll have enough hardware resources even if the server is a very old computer (which it is).

Here are the parallelisms I may need to worry about:

  1. Processes running on completely separate computers, connected by http or https over the wider internet. (i.e., not just a LAN)

  2. OS processes. (I hope I don't have to deal with these in the server. I'm not planning to use CGI scripts.)

  3. OS threads, which may be on different CPU's within one OS process. Doesn't Racket have some way of talking about these?

  4. Racket threads, running within in OS thread.

  5. The form of parallelism involved in the web-server for incoming requests, which is likely one of 3) or 4).

  6. And of course, parallelism within the client machines, regulated by javascript. Mentioned here for completeness, though it is not the subject of this posting.

I understand that when requests come in from client machines, they are processes in parallel. Which form of parallelism is this?

My conceptual model is to have the entire web server run in one OS process and to use Racket semaphores for mutual exclusion, thereby separating the incoming requests. So on the server, requests will come in, by serialised by waiting on a single semaphore, update the game state one at a time and respond by sending an appropriate response to the client's browser. I've already implemented cookies to tell players apart.

  • Are Racket semaphores the right tool for serialising incoming requests? Or are they operating on the wrong level(s) of parallelism?

  • Do multiple requests from the same browser instance create separate processes (of whatever layer?). If not, will semaphores fail to separate them? (Some semaphore implementations I've encountered won't let a process wait on itself.)

On the client side, (for now) I'll have mouse buttons for the various actions players can take. Each will make a request from the server and update the screen on receiving a response. Also on the client, I'll have some kind of timeout or timer-based loop to frequently request a screen update. Details to be decided after I look through more Javascript documentation. In the interim, I can have an extra button on the screen to explicitly request an update (essentially an explicit player request to do nothing in the game -- not very player-friendly, but it will enable the rest to be written and tested.). I may eventually have to worry about synchronising time-outs with explicit player requests, but that's a javascript problem for another day. In the server, these will all be serialised on that one semaphore.

-- hendrik

1 Like

I didn’t totally digest the question, but instead of polling the state client side you might want to consider server-sent events. I’ve implemented them with some success for one of my Racket projects (one of my friend’s mobile browsers seems to struggle with them, but all else is well).

One thing to consider is the distinction between concurrency and parallelism. Very informally, concurrency is interleaving work on multiple tasks, such as a web server handling multiple requests without the second having to wait for the first to be completely finished. Parallelism, on the other hand, means multiple units of work actually being done simultaneously. Racket's threads provide concurrency, but not parallelism. Places and futures provide parallelism, but notably futures do not necessarily provide concurrency. Racket's Concurrent ML based system of events and sync are primarily about concurrency, though some kinds of concurrent events are about communicating with parallelism, like place chanels or waiting for OS-level subprocesses.

While the clients themselves run in parallel (each laptop has its own CPU), from the server's perspective, you will handle them concurrently, not in parallel. (If you wanted parallelism there would be other things to talk about, but I'm ignoring that for now.)

The basic model of the Racket web server is to run in a single OS process, like most Racket programs. (This model is also used by Node.js and other successful servers.) So, the answer to "are Racket semaphors the right tool" is yes in the sense that they are part of Racket's Concurrent ML toolbox, and that toolbox is the right one for managing this kind of concurrency. On the other hand, semaphores are a relatively low-level tool: there are a variety of powerful tools in this box, and it's possible that one of the others might be better (or not). In addition to the Racket Guide chapter, I always recommend Matthew and Robby's paper “Kill-Safe Synchronization Abstractions”, which presents a very accessible walk-through of the implementation of racket/async-channel.

The Racket web server does create a new Racket-level thread for each request it receives.

I haven't used it myself, but I think WebSockets as provided by net/rfc6455 might be useful for this sort of thing. It uses parts of the web-server library internally, and I think there's some way to integrate them.

1 Like

I haven't used it myself, but I think WebSockets as provided by net/rfc6455 might be useful for this sort of thing. It uses parts of the web-server library internally, and I think there's some way to integrate them.

Thank you. I hadn't known of websockets. It does look as if they would solve a few problems.
I was starting to wonder if I would have to build a transport layer on top of http in order to make sure game actions arrive in consistent order. Probably better to use websockets. I have implemented the OSI tansport layer a few decades ago. I know how, but Better not to have to.

But having the game run multiplayer (albeit with explicit null actions) is almost ready to try out and discover what doesn't work. I think I'll try get some experiece with that, and its problems, before going to websockets. But websockets will likely be next.

-- hendrik

What mechanism did you use to implement server-sent events?

Essentially response/output and following the spec closely: https://github.com/benknoble/frosthaven-manager/blob/main/server.rkt#L638 You can see I wait for an event or a timeout and send either the event or a « keep alive » string by writing to the port. The code to write the events is pretty manual and repetitive right now, and I could imagine a nicer interface using, say, (struct event [name data]). I setup a route for this response handler.

The event handler is bog standard javascript, following the spec again: https://github.com/benknoble/frosthaven-manager/blob/main/static/events.js The JS is included via a script tag, and constructs an event source using the route.

Keep in mind testing is back to inserting printlns and console.outs, since web dev tools don’t have show the events route or event code execution well (at least on safari). Server restarts usually required a page refresh to make sense (otherwise it was possible for a stale page to receive events from a fresh server, which is asking for trouble).