Hi Philip,
Sorry for the delay in responding ... busy day.
On 10/14/2018 7:09 AM, Philip McGrath wrote:
On Sun, Oct 14, 2018 at 5:30 AM George Neuner <gneun...@comcast.net
<mailto:gneun...@comcast.net>> wrote:
You are absolutely correct that in typical usage globals are not
closed over ... normally they are considered mutable shared
"communication" state.
However, I was speaking more generally: to be persisted long
duration, and be made portable across separate runtime instances,
the continuation must close over AND deep copy ANY VALUE the
current call chain references. You don't (necessarily) want to
restart the continuation a week later, on a different machine,
with different values than when it started.
I am definitely not an expert on continuations or PL generally, so I
could be very wrong. My understanding, though, has been that
continuations not closing over non-local variables is a property of
continuations in general: not only in a web context, and not only of
the serializable continuations from `#lang web-server`. (I used those
in my example just because it is easy to see what values they do in
fact close over.)
In the most basic form, the continuation is just "what to do next" -
e.g., a pointer to the next code to be executed. That is [more or less]
all that is required for a local (within a function) escape continuation
because [barring stack corruption] all of the EC's stack context is
guaranteed to exist if/when the continuation is invoked, and that state
is needed anyway because the program will continue on regardless of how
it leaves the scope of the EC.
For a non-local EC, you need to return to the stack frame that was
current when the continuation was created [the rest of the stack can be
discarded]. In any case, an EC can very compact - just a couple of
pointers.
[Aside: a Lisp-style exception requires that the entire call stack be
kept until you know where execution will continue. You need to return
to the EC's call frame (to find the exception handler), but Lisp allows
exceptions to introspect/modify the environment and then to return
control to the point of the exception (where the intrinsic EC was
invoked). Scheme doesn't have this capability.]
General continuations - such as can implement coroutines - need to
capture much more state. The entire stack state of the current function
call chain at the point where the continuation is created must be
preserved in order to reenter the continuation.
[this is where stateful and stateless part company - more below]
Winding continuations are an extension of general continuations to the
requirements of dynamic-wind. In addition to whatever is required for a
general continuation, a winding continuation has preamble code that must
be executed whenever a continuation enters the winding scope, and
postamble code to be executed whenever (and how ever) execution leaves
the winding scope.
The long winded point here is that continuations have no canon
implementation - if you want more functionality from them, you need to
capture/preserve more state.
The web-server is multi-threaded, and the stack state of each thread is
unique - so for each thread in which a continuation is created in the
stateful server, the thread's call stack must be preserved.
The stateless server is a different case. CPS code doesn't use a call
stack ... or rather its "stack" only ever contains one useful frame -
the current one. So there's only one call frame per thread that needs
to be preserved rather than a (maybe large) stack of them. This is why
stateless server continuations - in general - use much less memory than
stateful server continuations.
[The above is a bit simplistic because Scheme supports nested functions
with non-local variable access. [Scheme also supports 1st class
closures based on nested functions, but that is not what this is
about.] To support non-local access without a stack, CPS code needs to
construct shared environments on the fly as the program executes to
communicate values from producers to consumers, and these environments
(where applicable) also must be part of saved continuation state.
Environment construction is similar to, but not the same as closure
conversion: as with closures the code must be compiled to expect the
variable(s) in the external environment block rather than in the call
frame - but unlike closures, a new environment must be constructed each
time a given producer needs to communicate with consumers.
The alternative to these "environments" is to pass everything by
argument - but doing that in CPS quickly leads to functions with huge
argument lists, and to functions having many "pass-thru" arguments that
they don't use.
A related procedure done with stack-based implementations is sometimes
referred to as "stack flattening". When a function high in the call
chain must communicate with functions lower down in the chain, a shared
environment is allocated high on the stack and a pointer to it is passed
as an argument down through the chain allowing any lower function to
access it. The stack case is somewhat easier to analyze than the CPS
case because typically there are just a small number of functions
involved. The CPS case is more complicated: although any particular
environment will have a limited lifetime, often multiple different
(logical) environments to coexist simultaneously in a call chain of
(potentially) unlimited length in which the number of functions involved
can be up to every function in the program. It makes for an interesting
compilation and runtime structuring problem.]
The documentation for `#lang web-server` does emphatically warn that
the store is *not* serialized. If you rely on the store you will
be taking huge risks. You will be assuming that the serialized
continuation is invoked on the same server before the server is
restarted or the memory is garbage collected.
It is reasonable to assume a continuation will be invoked on the same
program instance. But assuming referenced (heap) objects will not be
garbage collected in the meantime IMO is not reasonable. That alone is a
reason NOT to use serialized continuations - better to use in-memory
continuations that preserve their complete (program-wide) state.
As far as I can see, serialized continuations from `#lang web-server`,
for better or for worse, have the same issues in these respects as the
kinds of manually RESTful web applications you might write in various
languages with names beginning in P, where you would marshal an ad-hoc
representation of the continuation into a query parameter, path
segment, or hidden form field. In that case, also, multiple server
instances with different server-side state could create chaos. If you
are relying on that kind of server-side state, though, your web
application isn't really RESTful at all. The web-server language
doesn't prevent you from creating such problems, but it shows you a
principled way to avoid them: write functional programs.
REST actually only constrains the client/server communication protocol
to being stateless ... it doesn't constrain the service itself to being
stateless.
The problem with serialized continuations is that they don't save enough
state to restart the computation if the required state has been purged
(GC'd), or was not present to begin with in a different (identical code)
server instance.
George
--
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+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.