Racket application servers

234 views
Skip to first unread message

Brian Adkins

unread,
Nov 23, 2018, 3:46:27 PM11/23/18
to Racket Users
I'm porting a web application from Ruby/Rails to Racket, and I'd like something to manage the Racket server processes. 

In the Ruby world, I'm currently using Unicorn ( https://en.wikipedia.org/wiki/Unicorn_(web_server) ) prior to that I used Nginx Passenger ( https://en.wikipedia.org/wiki/Phusion_Passenger ), etc. Another popular Ruby app server is Puma ( https://en.wikipedia.org/wiki/Puma_(web_server) )

I'll use nginx as the front end web server, and it will proxy to the application server. In a nutshell, for Unicorn, I configure the location of the Rails app, configure how many processes I want, and Unicorn will spin up that number of Rails processes and handle routing requests from nginx to each of the Rails processes in some fashion. If a Rails process exists abnormally, it will spin up another one to replace it. To deploy a new version of my app, I can send a signal to the Unicorn master process to *gracefully* restart all the processes i.e. it waits for the current request to finish, and then kills the process and spins up a new one using the new version of the app.

Are there similar application servers available for Racket? Alternatively, if not, and you have long running applications in Racket, what are you using to manage them?

Thanks,
Brian

Philip McGrath

unread,
Nov 23, 2018, 4:39:09 PM11/23/18
to Brian Adkins, racket...@googlegroups.com
I'm not familiar with Ruby, so this is just some general information.

The Racket web server already supports concurrency with its built-in green threads, so handling one request won't block the concurrent handling of another. (Not all languages' built-in web servers do this; I don't know about Ruby in particular).

There isn't built-in support for parallelism, i.e. for taking advantage of multiple processor cores. This isn't built in, and I don't personally need this (my server only has two cores, and it runs a database and other processes in addition to Racket). However, it is probably possible to recreate the architecture you describe.

Essentially you would be running multiple instances of your application behind a load-balancer. If you want to use nginx as a load balancer, that's out of the equation; implementing a load-balancer in Racket would certainly be possible but probably more work. Extra-linguistically, you could just create a few systemd services or similar to run several totally separate Racket instances. Within Racket, you would want to build on "places." You can have N worker places running N instances of your application on N OS-level threads, plus a master place to control them. A mechanism for gracefully stopping the Racket web server is built in (see serve/launch/wait). Reloading isn't built in, but I've heard good things about the "reloadable" package (https://pkgs.racket-lang.org/package/reloadable), which should let you implement it if needed.

While I only run one instance of our application, I do use places to redirect HTTP to HTTPS.

One caveat is that all of this assumes that, if you are using continuations at all, you are using serializable continuations with `#lang web-server`. Making places work well with native continuations would probably be a lot of work, and it would probably be better to build that functionality into the Racket web server than to try to fake it as a client.

-Philip


--
You received this message because you are subscribed to the Google Groups "Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to racket-users...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

George Neuner

unread,
Nov 23, 2018, 5:44:41 PM11/23/18
to racket...@googlegroups.com


On 11/23/2018 4:38 PM, Philip McGrath wrote:
I'm not familiar with Ruby, so this is just some general information.

The Racket web server already supports concurrency with its built-in green threads, so handling one request won't block the concurrent handling of another. (Not all languages' built-in web servers do this; I don't know about Ruby in particular).

There isn't built-in support for parallelism, i.e. for taking advantage of multiple processor cores. This isn't built in, and I don't personally need this (my server only has two cores, and it runs a database and other processes in addition to Racket). However, it is probably possible to recreate the architecture you describe.

Essentially you would be running multiple instances of your application behind a load-balancer. If you want to use nginx as a load balancer, that's out of the equation; implementing a load-balancer in Racket would certainly be possible but probably more work. Extra-linguistically, you could just create a few systemd services or similar to run several totally separate Racket instances. Within Racket, you would want to build on "places." You can have N worker places running N instances of your application on N OS-level threads, plus a master place to control them. A mechanism for gracefully stopping the Racket web server is built in (see serve/launch/wait). Reloading isn't built in, but I've heard good things about the "reloadable" package (https://pkgs.racket-lang.org/package/reloadable), which should let you implement it if needed.

While I only run one instance of our application, I do use places to redirect HTTP to HTTPS.

One caveat is that all of this assumes that, if you are using continuations at all, you are using serializable continuations with `#lang web-server`. Making places work well with native continuations would probably be a lot of work, and it would probably be better to build that functionality into the Racket web server than to try to fake it as a client.

-Philip


Just a comment:

Multiple (identical) server instances running on the same machine can listen for connections on the same network port - an incoming client call will be connected to only one of them.  You only need to use different ports when services are not interchangeable [i.e. it matters which one you are talking to].

Process parallelism, in effect, can give you automatic connection load balancing on a single machine.  Using a separate load balancer in front technically is overkill for such situations, but it is a good design if you want to preserve the option to relocate your services to different machines.

George

Greg Hendershott

unread,
Nov 24, 2018, 12:03:27 AM11/24/18
to Brian Adkins, racket...@googlegroups.com
In my experience a Racket web server will just stubbornly continue to
work for months at a time, if you let it.

(Reminds me of the aviation joke. In the future, cockpits will have
just one human pilot and a dog. The dog is there to bite the human if
they try to turn off the autopilot.)

I have a site on one little t2.micro at AWS.

It is behind an AWS application load balancer -- but mainly just as an
easy way to do SSL termination.

I also added WAF to filter script kiddies who don't bother to supply a
valid Host header while they try to log into phpadmin or whatever. For
non-FANG sites honestly that will be a large proportion of your
traffic in terms of number of requests. It wasn't making the Racket
web server sweat, I just wanted to noise out of my logs and stats.

Philip McGrath

unread,
Nov 24, 2018, 12:10:01 AM11/24/18
to Greg Hendershott, Brian Adkins, racket...@googlegroups.com
I'm not sure if this was clear from what I said earlier, but I use the Racket web server without anything in front of it. In fact, Racket does SSL termination and serves as a proxy for non-Racket services. We have been completely satisfied with its performance and reliability.

-Philip


Brian Adkins

unread,
Nov 24, 2018, 11:57:53 AM11/24/18
to Racket Users
I'm glad to hear about the Racket web server's reliability! I'm still working through the high level architecture, but it seems like one Racket instance per core make sense to maximize cpu utilization, and then I may spin up a thread per request w/in each process to maximize memory utilization.

I like using nginx in front of the application for a couple reasons: 1) It handles non-application requests (e.g. simple files) very fast, and 2) I'm very familiar with configuring it for multiple distinct apps, SSL, etc.

So, in the absence of a Unicorn or Puma-like app server in Racket, I think a couple good options are:

1) Use monit to manage a set of Racket server processes (one per core), and have nginx load balance requests to them (or alternatively, as George mentioned, let the OS load balance by having the server processes all listen on the same port).

2) Write a simple master process (i.e. very scaled down Unicorn or Puma) in Racket that manages a Racket worker process per core.

I'm pretty sure that I'll eventually want option #2, but I may need to go with #1 initially in the interest of time.

Jesse Alama

unread,
Nov 24, 2018, 2:21:11 PM11/24/18
to Brian Adkins, Racket Users
Hi Brian,
Just to echo the experience of others here: I've also used the Racket
server straight up on the unfiltered Internet, and had no problems
except (as Greg mentioned) weird requests flowing in that the server
found dubious. Without crashing, I should add; these weird (if not
downright malformed) requests didn't kill the web server, they just
created a lot of junk in the logs. Putting Racket behind a server that's
more prepared to handle such junk (e.g., OpenBSD's httpd) restores log
sanity.

To get back to your real question, though: you want to know how to
deploy a new version of a running web app.

(1) There's the reloadable package

https://pkgs.racket-lang.org/package/reloadable

I myself haven't used it, but it's definitely relevant. That looks like
a real Racket-y solution, which is what I think you're looking for.

(2) I myself view these issues as DevOps-y things to be dealt with not
so much in Racket and more at the OS/service level. Generating an
httpd.conf file and then using OpenBSD's rcctl to switch out the old
config for the new one has been fine for me. This is perhaps a deviant
view; the reloadable package probably reflects a more catholic
understanding of the situation. But there may be cases where you change
your code in ways that are just too big, and I bet you'll find yourself
needing to do something like what I do and what you're currently doing
with Unicorn.

Jesse

Brian Adkins

unread,
Nov 24, 2018, 3:37:14 PM11/24/18
to Racket Users
I probably wasn't very clear about what's going on with Unicorn when I deploy a new version of the app. It's actually quite simple, and doesn't require loading new code on the fly. Here's what I do:

1) I update the app server code via:  git pull  (this doesn't affect the running processes due to Linux inode handling)
2) I send a signal to the Unicorn master process via:  kill -USR2 <pid>
3) Unicorn then instructs each of the worker processes to quit after finishing their current request, if any.
4) When each worker process quits, Unicorn automatically spins up a new one that happens to use the new code

Requests continue to be handled by existing worker processes, so there's no downtime.

I'm not too concerned about this aspect - I could easily instrument this manually by making the worker processes accept a signal/message/etc. that would instruct them to quit after the current request.

The monit solution seems fine, but it typically works via a pid file, so I'd simply have to ensure that the N Racket server processes write their respective pid's to N separate files. It's not a big deal, but having something like Unicorn handle that is handy. 

I could probably just have monit pass a unique argument to each of the N processes e.g. 1, 2, 3, ... , N and store the pid's in pid1, pid2, ... , pidN. They could also use the arg to form the port they listen on 4311, 4312, ... , 431N

I'm probably going to be dealing with only 2 or 4 cores typically, so the final solution doesn't need to be all that complicated.

I appreciate all the info folks have supplied, and I learned something new from George about multiple apps listening on the same port - I assume that involves SO_REUSE - I may have to research that. Having the OS handle the scheduling may be worth considering.

Philip McGrath

unread,
Nov 24, 2018, 7:39:33 PM11/24/18
to George Neuner, racket...@googlegroups.com
On Fri, Nov 23, 2018 at 5:44 PM George Neuner <gneu...@comcast.net> wrote:
Multiple (identical) server instances running on the same machine can listen for connections on the same network port - an incoming client call will be connected to only one of them.  You only need to use different ports when services are not interchangeable [i.e. it matters which one you are talking to].

Process parallelism, in effect, can give you automatic connection load balancing on a single machine.  Using a separate load balancer in front technically is overkill for such situations, but it is a good design if you want to preserve the option to relocate your services to different machines.

I didn't know this, and it sounds useful! Unfortunately, this functionality doesn't currently seem to be exposed at the Racket level. This example program:

#lang racket

(provide launch-places)

(module+ main
  (launch-places))

(define (launch-places [port 8011])
  (define place-log-pch
    (let-values ([{in-pch place-log-pch} (place-channel)])
      (thread (λ ()
                (let loop ()
                  (write-string (place-channel-get in-pch))
                  (loop))))
      place-log-pch))
  (define workers
    (for/list ([n (in-range 0 2)])
      (place/context p
        (define (place-log msg)
          (place-channel-put place-log-pch
                             (format "Place ~a: ~a\n" n msg)))
        (parameterize
            ([error-display-handler (λ (s v) (place-log s))])
          (place-log "started")
          (define listener
            (tcp-listen port 4 'reuse))
          (place-log "created listener")
          ;; don't let listener be gc-ed
          (tcp-accept listener)
          (place-log "ended")))))
  (apply sync (map place-dead-evt workers))
  (for-each place-kill workers))

prints the following output on both Mac OS and Ubuntu:
Place 0: started
Place 0: created listener
Place 1: started
Place 1: tcp-listen: listen failed
  port number: 8011
  system error: Address already in use; errno=48

It looks like the `reuse?` argument to `tcp-listen` corresponds to `SO_REUSEADDR` rather than `SO_REUSEPORT`. That's consistent with what `udp-bind!` explicitly says it does, and its the only thing I can see happening in the implementation (https://github.com/racket/racket/blob/master/racket/src/rktio/rktio_network.c)

It seems like this might be a useful feature in Racket, but I'm not sure of what would be needed to expose it in a good way. In particular, some quick Googling revealed that there are some portability considerations, which I haven't fully digested yet (https://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t).

-Philip

George Neuner

unread,
Nov 25, 2018, 3:51:36 AM11/25/18
to Philip McGrath, racket users
It's not working because the places in this code are OS threads in the SAME process.  You need to start separate processes:  using dynamic-place targeting localhost, or using system, process, subprocess, etc.

AIUI  'reuse?'  sets both  SO_REUSEADDR  and SO_REUSEPORT.  But a single process can't open the same port twice.

George

George Neuner

unread,
Nov 25, 2018, 4:07:28 AM11/25/18
to racket...@googlegroups.com


Paulo Matos recently announced a library called "Loci" that allows for
using mulitple processes with much the same API as places.
https://pkgs.racket-lang.org/package/loci


I haven't tried it, but it looks interesting.

George

Bogdan Popa

unread,
Nov 25, 2018, 4:11:43 AM11/25/18
to George Neuner, Philip McGrath, racket users
One way you might also solve this on UNIX systems is you could fork after listening on the socket. The file descriptor for the socket from the parent would then be shared with the children and so their accept calls would be "load balanced" by way of them all accepting connections from the same queue. This solves the problem without requiring SO_REUSEPORT (the disadvantage being that there's only a single kernel-level accept queue with this approach, whereas with SO_REUSEPORT each process would get its own queue). 

Is there a way to fork(2) in Racket?



George Neuner

unread,
Nov 25, 2018, 4:16:43 AM11/25/18
to Bogdan Popa, racket users

On 11/25/2018 4:11 AM, Bogdan Popa wrote:
One way you might also solve this on UNIX systems is you could fork after listening on the socket. The file descriptor for the socket from the parent would then be shared with the children and so their accept calls would be "load balanced" by way of them all accepting connections from the same queue. This solves the problem without requiring SO_REUSEPORT (the disadvantage being that there's only a single kernel-level accept queue with this approach, whereas with SO_REUSEPORT each process would get its own queue). 

Is there a way to fork(2) in Racket?

Unfortunately, no.  Racket is cross-platform, and Windows doesn't have the concept of fork().

George



Greg Hendershott

unread,
Nov 25, 2018, 11:26:11 AM11/25/18
to Brian Adkins, racket...@googlegroups.com
I just want to point out the possibility that your Racket web app
might not be CPU-bound. Some "generic" web sites are IO-bound. Blocked
on IO for the HTTP requests and responses. Blocked on IO talking to a
database server like Postgres.

In cases like that, you might not need more than one process. (Indeed
you might even get away with running on a t2.micro instance, which
throttles horribly after a short CPU burst, because you never come
close to that threshold even under your maximum traffic loads.)

It's a real possibility you might want to measure/see, first. Of
course it depends on how much work your Racket web app does (itself,
not farmed out to DB or other out-of-process servers), as well as on
the traffic loads you expect.

Having two or more servers might be convenient for non-load reasons.
For updates (to let the old "drain" as you described, or blue/green
deploys, etc.). Or for fail-over (although I'm not sure 2 procs on
same box is the way to go, if you even really need many 9s (many sites
really don't if we're being honest with ourselves)).

Matthew Flatt

unread,
Nov 25, 2018, 6:13:54 PM11/25/18
to Philip McGrath, George Neuner, racket...@googlegroups.com
At Sat, 24 Nov 2018 19:39:19 -0500, Philip McGrath wrote:
> On Fri, Nov 23, 2018 at 5:44 PM George Neuner <gneu...@comcast.net> wrote:
>
> > Multiple (identical) server instances running on the same machine can
> > listen for connections on the same network port - an incoming client call
> > will be connected to only one of them. You only need to use different
> > ports when services are not interchangeable [i.e. it matters which one you
> > are talking to].
> >
> > [...]
>
> I didn't know this, and it sounds useful! Unfortunately, this functionality
> doesn't currently seem to be exposed at the Racket level. [...]
>
> It looks like the `reuse?` argument to `tcp-listen` corresponds to
> `SO_REUSEADDR` rather than `SO_REUSEPORT`. [...]
>
> It seems like this might be a useful feature in Racket, but I'm not sure of
> what would be needed to expose it in a good way.

An alternative to exposing `SO_REUSEPORT` would be to allow TCP
listeners to be sent across place channels, so multiple places in same
Racket process could accept connections from the same listener. That
would only work for place-based parallelism, but it would be portable
and straightforward to implement --- and I'm happy to add it if anyone
would find that capability useful.

George Neuner

unread,
Nov 25, 2018, 7:05:03 PM11/25/18
to Matthew Flatt, racket users

On 11/25/2018 6:13 PM, Matthew Flatt wrote:
> An alternative to exposing `SO_REUSEPORT` would be to allow TCP
> listeners to be sent across place channels, so multiple places in same
> Racket process could accept connections from the same listener. That
> would only work for place-based parallelism, but it would be portable
> and straightforward to implement --- and I'm happy to add it if anyone
> would find that capability useful.

AFAIK, a single process can't open any listen port more than once - so
exposing  SO_REUSEPORT  wouldn't necessarily help.  However, I seem to
recall Jay McCarthy mentioning that TCP ports can be sent over place
channels.  Since listen gives you a new port for each connection, it
should be possible to have a centralized listener offloading processing
to a place.

But this still isn't the same as with multiple processes.  A listen port
has a fixed sized queue of waiting connections - it's adjustable within
boundaries, but can't be exceeded by a single process.  With multiple
processes, the queue is magnified by the number of processes - each
process can queue that number of connections.  It's an important
distinction if the connection load is very heavy.

George

Jérôme Martin

unread,
Nov 26, 2018, 4:42:15 AM11/26/18
to Racket Users
Just so you know, I started some months ago a Racket project that would help monitor different Racket web servers and load-balance them, using systemd-nspawn as a native container system, and an optional
 nginx server to load balance everything, serve static content and cache dynamic one.


It's in a very early stage right now but I'm actively working on it so that I can deploy and monitor my servers without having to rely on some big overkill container app du jour.

I'd be really glad to talk about some approaches you use and what seems the easiest to implement. My main objectives are minimalism, use as much native systems as possible, and as much Racket as possible.

Plus, containers in Bonny are called "pirates" (cause you're "shipping them", ahah).

Brian Adkins

unread,
Nov 26, 2018, 10:38:23 AM11/26/18
to Racket Users
The current Ruby/Rails app will max out at least one core at times now. I realize Racket should be faster, but I expect I'll still need more than a single core for the app as the volume will be going up significantly in January, and as you mentioned, there are some other benefits to a multi-process architecture.

Brian Adkins

unread,
Nov 26, 2018, 10:38:57 AM11/26/18
to Racket Users
Very interesting. I'll check out your project - thanks for mentioning it.

David Storrs

unread,
Nov 26, 2018, 11:41:08 AM11/26/18
to George Neuner, racket...@googlegroups.com
As a point of information, Perl's approach is to make things available and add a note saying "This doesn't work on Windows because Windows is weaksauce^H^H^H^H^H^H^H^H^H^H doesn't support it."  It's a balance between giving the user capabilities and protecting them from making mistakes.

George Neuner

unread,
Nov 26, 2018, 1:07:38 PM11/26/18
to David Storrs, racket users


On 11/26/2018 11:40 AM, David Storrs wrote:
On Sun, Nov 25, 2018 at 4:16 AM George Neuner <gneu...@comcast.net> wrote:

On 11/25/2018 4:11 AM, Bogdan Popa wrote:
Is there a way to fork(2) in Racket?
Unfortunately, no.  Racket is cross-platform, and Windows doesn't have the concept of fork().

As a point of information, Perl's approach is to make things available and add a note saying "This doesn't work on Windows because Windows is weaksauce doesn't support it."  It's a balance between giving the user capabilities and protecting them from making mistakes.

Perl should talk ... it's the bastard child of BrainF*ck and line noise.  It's a mystery to me how it became so popular.

Though I can appreciate the beauty of fork [after reading Bach's book], quite a lot of people thought fork was a stupid idea ... including the people, originally from DEC, who wrote Windows NT.  Windows has evolved a bit since then, but internally you still can see quite a bit of similarity to VMS [which, like Unix, was very reliable and inspired legions of fans].


The guts of Windows are every bit of good as Unix ... it's the fluff Microsoft layered on top that is the problem. 

I administered a mixed group of NT and 2K servers for over 10 years: running internet relay, DNS, file service, mail service, , source & document control, Sybase server, workgroup backup, and a pair of terminal servers hosting shared development tools.  In all that time, there were only 2 crashes, both due to failing hard drives.  The servers were rebooted once a year just for the heck of it, and a few times over their lives to install or upgrade software.  Other than that, they ran 24/7.

Though I'm no longer dealing with Windows servers, I still know people who are.  Not kidding, Windows is VERY reliable as long as you avoid running too many Microsoft applications on it.

During that same period I was developing industrial vision applications on Windows.  I had multiple versions of Microsoft and Intel tool chains and SIMD libraries, etc. on my desktop, and I could lock up Windows with 10 lines of code (still works, btw!!!).  But MSWord would crash on me at least once a week - it gave me far more grief than everything else put together.

I'm still using a Windows 7 desktop that runs for months at a time.  It gets rebooted only to install or upgrade software  [and I don't abide Microsoft's monthly update schedule - I consider OS updates if/when I need to do something else].  I still have Visual Studio, and Racket, and Python, and a few other things ... but I abandoned MSOffice for Libre long ago.

YMMV,
George

Neil Van Dyke

unread,
Nov 26, 2018, 1:50:25 PM11/26/18
to racket users
George Neuner wrote on 11/26/18 1:07 PM:
> Perl should talk ... [...] It's a mystery to me how it became so popular.

I bet George knows the story, but I'd like to tell it for the list,
since I think this is relevant background, to people interested in
practical programming languages research...

Perl, though I'd try not to use it today, actually has a proud history
of being a god-like power tool on Unix, starting circa 1990.  Four of
the reasons:

* a Perl programmer could wipe the floor, functionality-wise, with even
with the most skilled Unix shell person (the kind who could avoid
textual expansion/evaluation bugs in Bourne and Csh, and do things like
relational database operations using only AT&T / Bell Labs and Berkeley
shell tools);

* Perl was portable (there was a lot of variation among all the
engineering workstation Unix implementations; plus there were
VAXstations that you'd otherwise have to script in DCL, and DEC stuff
was all interesting and solid, but a very different philosophy from
Unix; Linux and the PC BSDs, and most MS-DOS and Windows stuff, were
still underpowered toys);

* the alternative to Perl was usually C or early C++ (though extension
languages like Tcl and Python were starting to kick around; but Tcl's
main use was relatively easy GUI development, and Python was just a bare
language without libraries when Perl already had lots); and

* Perl was especially well-suited to the needs of sysadmins, doing
everything from very sophisticated and complicated operations, to quick
little labor-saving scripts.

There was also a second burst to Perl popularity, when the Web started
to emerge.  Up until then, Perl was the lightsaber of the sysadmin, plus
a few software developers who knew about it, and liked to use it on the
side.  But one of the earliest ways to do server-side dynamic Web
content, in the early-mid 1990s, was with the CGI interface to the Web
server, and Perl had one of the first libraries for that, strong string
manipulation, and quick process startup.  (I did most of my CGI in Perl,
and only one in C, because it needed the GD library for drawing a
diagram, and Perl didn't yet have library support for that.  And a Perl
CGI script is how I have prior art on Facebook, years before, and mine
was scraping multiple university resources in real time. :)  Other
libraries and frameworks and Web servers and such came along, but, for a
while, Perl was almost synonymous with server-side Web stuff, because it
was there first, and you could get it to do what you needed.

I agree that Perl was always gratuitously crazy, with its linguistic
shortcuts and redundant forms.  (Today, a student could have a blast,
implementing a `#lang perl` for Perl 4 or later, and getting all the
syntax and semantics right.)  The cult-like pride behind "you can do
that in a single line of Perl", and perhaps being proud of line noise
appearance, might've fed that.  But it was easy to see why Perl became
popular despite this.

George Neuner

unread,
Nov 26, 2018, 3:48:10 PM11/26/18
to Neil Van Dyke, racket users

Very nice recap.  +1.


On 11/26/2018 1:50 PM, Neil Van Dyke wrote:
>
> * the alternative to Perl was usually C or early C++ (though extension
> languages like Tcl and Python were starting to kick around; but Tcl's
> main use was relatively easy GUI development, and Python was just a
> bare language without libraries when Perl already had lots); and

Minor addition:

At the beginning, Perl was used mainly for text processing and report
generation.  It was only later as it gained a significant number of
libraries that it developed into a more useful general purpose tool.

Early on, the (much) better alternative to Perl was SNOBOL4 - a pattern
matching language based on context free grammar that was far more
powerful than Perl's regular expressions, and already was well
established long before Perl existed.  SNOBOL4 was developed in the late
60's, and through the 70's and 80's, for many people it was the GOTO
tool for heavy duty text processing and report generation.

SNOBOL4 was a portable virtual machine implementation.  There were
versions available for most common platforms, including IBM PC, and
there even was a C macro implementation that could be embedded and used
anywhere a C compiler was available.

SNOBOL4 did not have built-in database access, nor did it really have
libraries [but its scripts were composable], so when Perl came along, it
had a minor technical advantage in its extensibility.  But typically
SNOBOL4 would be used in pipeline fashion, receiving and transforming
input, e.g., from a database query tool.  SNOBOL4's popularly spawned
several enhanced descendant languages - SPITBOL, Icon, Unicon, etc. -
that co-existed with early Perl, also featured library extensibility,
and SNOBOL4's more powerful text processing ability.


Unfortunately, the better tool doesn't always win.
George


Neil Van Dyke

unread,
Nov 26, 2018, 4:45:00 PM11/26/18
to racket users
I don't know how Snobol dropped out of awareness.  I almost never heard
of it in the MS-DOS, Windows, or Unix workstation circles, even before
Perl, though occasionally you'd see an interpreter on a BBS or
freeware/shareware list.

The most memorable mention I recall was a grad student in the '90s
remarking that (IIRC) Andy van Dam wanted to teach Snobol to
undergrads.  This being funny in a "he might be right, but it's lol so
random, and he wouldn't actually do that" kind of way.  (AvD did lead a
grad school class, around '95, to evaluate candidate languages for CS
intro use.  We looked at many languages, including Java when it was
called Oak, but I don't recall anyone championing Snobol.)

Could Snobol be another case study of a good technology that fell out of
use, such as due to accidents of products or user bases separate from
the innate merits of the technologies?  Lisp and Smalltalk and Prolog
and Betamax people could sympathize. :)  Are there any new lessons we
can learn about adoption, or any lessons of linguistics?  (Maybe one of
the best string-processing features of Snobol is a Racket procedure or
syntax extension waiting to happen. Or maybe understanding the twist of
fate of Snobol will enlighten
"https://www.neilvandyke.org/racket-money/" as to why we're not all rich
already. :)

George Neuner

unread,
Nov 26, 2018, 9:59:55 PM11/26/18
to Neil Van Dyke, racket users

On 11/26/2018 4:44 PM, Neil Van Dyke wrote:
> I don't know how Snobol dropped out of awareness.  I almost never
> heard of it in the MS-DOS, Windows, or Unix workstation circles, even
> before Perl, though occasionally you'd see an interpreter on a BBS or
> freeware/shareware list.

That's funny because it definitely was around.  I know for a fact it was
on SunOS and Ultrix, and there was a BSD version on VAX.  In the micro
world there were CP/M and Apple II versions.  MSDOS had at least 2
implementations: SNOBOL4 itself, and a descendant language called
"Icon"[*].  Icon later was ported to both Macintosh and Windows 3.


[*] an unfortunate name given the imminent rise of WYSIWYG user
interfaces.  Icon was not a graphical tool - it was a script language
and runtime.  The name "Icon" was a nod to SNOBOL's beginnings as a
symbolic computation language.


> The most memorable mention I recall was a grad student in the '90s
> remarking that (IIRC) Andy van Dam wanted to teach Snobol to
> undergrads.  This being funny in a "he might be right, but it's lol so
> random, and he wouldn't actually do that" kind of way. (AvD did lead a
> grad school class, around '95, to evaluate candidate languages for CS
> intro use.  We looked at many languages, including Java when it was
> called Oak, but I don't recall anyone championing Snobol.)

It's interesting even that someone would joke about that late. AFAIK,
SNOBOL4 was only taught from the late 60's to early 70's. Like other
things that work well, it was evangelized by its users.

I am not aware of any metrics on how many users it had, but given the
size of the computing community in 1980, even a few 10s of thousands
would have been considered phenomenal.  Unlike today where something is
a failure if it's not on a million smartphones within 6 months.  <frown>


> Could Snobol be another case study of a good technology that fell out
> of use, such as due to accidents of products or user bases separate
> from the innate merits of the technologies?  Lisp and Smalltalk and
> Prolog and Betamax people could sympathize. :)

I am pretty sure that was the situation.


> Are there any new lessons we can learn about adoption, or any lessons
> of linguistics?  (Maybe one of the best string-processing features of
> Snobol is a Racket procedure or syntax extension waiting to happen. Or
> maybe understanding the twist of fate of Snobol will enlighten
> "https://www.neilvandyke.org/racket-money/" as to why we're not all
> rich already. :)

Adoption?  Probably not.  Things become popular if they appear at the
right time, work well enough, and get some publicity.  Perl (predated
Linux but) was able to capitalize on the Linux craze, and crucially an
O'Reilly book about Perl appeared just  a few years after the initial
release.  When SNOBOL4 was first released, there were no micros or
personal workstations, operating systems were proprietary, and nobody
was writing books about computing.

Linguistics?  Maybe.  One of SNOBOL4's main uses was automated document
analysis and translation.  Human languages are contextual, and even
though vocabulary is restricted, it's likely that no CF tool will ever
be able to faithfully translate [regardless of how much time or power
you throw at it].  But SNOBOL4 made significant inroads because it's
complex recursive pattern language was able to do hard things like
identifying indirect objects, pronoun references, etc. - things that
made analyzing text possible.

As for implementing SNOBOL4 in Racket, I would think it should be
relatively easy.  After all, it has already been done in C, Java,
Python, JavaScript, etc.  I certainly could do SNOBOL4 as a standalone
implementation, but I have not yet grokked how to create (#lang ...)
languages integrated into Racket - and that's the way it should be done.

YMMV,
George

Matthias Felleisen

unread,
Nov 27, 2018, 9:23:24 AM11/27/18
to George Neuner, Neil Van Dyke, racket users


FWIW, when I started CS in 1980 at Arizona, Griswold had moved on to Icon and no longer seemed to care about Snobol. He definitely thought of Icon as an improvement over Snobol the way Racket improves on Scheme. — Matthias

Brian Adkins

unread,
Nov 28, 2018, 8:55:05 PM11/28/18
to Racket Users
I finally got a very simple, "hello world", Racket web app up and running, and I'm very encouraged with the performance. I just started a single Racket instance and proxy to it from nginx. I have it running on an AWS EC2 instance, and running the Apache Benchmark (ab) utility on my laptop as follows reported 300 requests per second (38 concurrent requests, 1000 requests total):

ab -c 38 -n 1000 http://hello-app

Granted, the "hello world" app is doing the bare minimum (no database connection, minimal processing, etc., but it is logging requests), but the fact that I'm getting 300 req/sec including proxying through nginx with one single-threaded Racket process (& zero time spent tuning) is great news! Of course, over a decade with Ruby may have lowered my performance bar somewhat ;)

I can't wait to get a more representative test running.

Philip McGrath

unread,
Nov 29, 2018, 6:06:19 PM11/29/18
to Matthew Flatt, George Neuner, racket...@googlegroups.com
On Sun, Nov 25, 2018 at 6:13 PM Matthew Flatt <mfl...@cs.utah.edu> wrote:
At Sat, 24 Nov 2018 19:39:19 -0500, Philip McGrath wrote:
> On Fri, Nov 23, 2018 at 5:44 PM George Neuner <gneu...@comcast.net> wrote:
>
> > Multiple (identical) server instances running on the same machine can
> > listen for connections on the same network port - an incoming client call
> > will be connected to only one of them.  You only need to use different
> > ports when services are not interchangeable [i.e. it matters which one you
> > are talking to].
> >
> > [...]

>
> I didn't know this, and it sounds useful! Unfortunately, this functionality
> doesn't currently seem to be exposed at the Racket level. [...]

>
> It looks like the `reuse?` argument to `tcp-listen` corresponds to
> `SO_REUSEADDR` rather than `SO_REUSEPORT`. [...]

>
> It seems like this might be a useful feature in Racket, but I'm not sure of
> what would be needed to expose it in a good way.

An alternative to exposing `SO_REUSEPORT` would be to allow TCP
listeners to be sent across place channels, so multiple places in same
Racket process could accept connections from the same listener. That
would only work for place-based parallelism, but it would be portable
and straightforward to implement --- and I'm happy to add it if anyone
would find that capability useful.
 
Allowing TCP listeners to be sent across place channels sounds much more promising than exposing the portability issues with `SO_REUSEPORT`—I, for one, am a big fan of the fact that Racket makes it easy to write portable code. It also seems like it would be a step toward adding built-in support for parallelism to the web server, which is something I'd be interested in working on eventually (i.e. definitely not this week).

While looking into this I read the discussion about adding `SO_REUSEPORT` to the Linux kernel, and one of the motivations discussed was performance, which did seem worth considering in advance. Specifically, apparently if you
accept on a single listener socket from multiple threads … the proportion of connections accepted per thread tends to be uneven under high connection load (assuming simple event loop: while (1) { accept(); process() }, wakeup does not promote fairness among the sockets.  We have seen the disproportion to be as high as 3:1 ratio between thread accepting most connections and the one accepting the fewest.  With so_reusport the distribution is uniform.

Am I right that this is essentially what Racket would be doing internally if multiple places were blocked on `tcp-accept` with the same TCP listener? (This is a bit low-level for me.) Even if so, I think being able to send TCP listeners across place channels would probably still be useful, especially since most of us will never see what Google would call "high connection load."

-Philip

Matthew Flatt

unread,
Nov 29, 2018, 6:49:08 PM11/29/18
to Philip McGrath, racket...@googlegroups.com
At Thu, 29 Nov 2018 18:06:05 -0500, Philip McGrath wrote:
> > accept on a single listener socket from multiple threads … the proportion
> > of connections accepted per thread tends to be uneven under high connection
> > load (assuming simple event loop: while (1) { accept(); process() }, wakeup
> > does not promote fairness among the sockets. We have seen the
> > disproportion to be as high as 3:1 ratio between thread accepting most
> > connections and the one accepting the fewest. With so_reusport the
> > distribution is uniform.
>
> Am I right that this is essentially what Racket would be doing internally
> if multiple places were blocked on `tcp-accept` with the same TCP listener?

Yes, that's right.

Reply all
Reply to author
Forward
0 new messages