Well I've been thinking about the re-routine as a model of cooperative multithreading,
then thinking about the flow-machine of protocols
NNTP
IMAP <-> NNTP
HTTP <-> IMAP <-> NNTP
Both IMAP and NNTP are session-oriented on the connection, while,
HTTP, in terms of session, has various approaches in terms of HTTP 1.1
and connections, and the session ID shared client/server.
The re-routine idea is this, that each kind of method, is memoizable,
and, it memoizes, by object identity as the key, for the method, all
its callers, how this is like so.
interface Reroutine1 {
Result1 rr1(String a1) {
Result2 r2 = reroutine2.rr2(a1);
Result3 r3 = reroutine3.rr3(r2);
return result(r2, r3);
}
}
The idea is that the executor, when it's submitted a reroutine,
when it runs the re-routine, in a thread, then it puts in a ThreadLocal,
the re-routine, so that when a re-routine it calls, returns null as it
starts an asynchronous computation for the input, then when
it completes, it submits to the executor the re-routine again.
Then rr1 runs through again, retrieving r2 which is memoized,
invokes rr3, which throws, after queuing to memoize and
resubmit rr1, when that calls back to resubmit r1, then rr1
routines, signaling the original invoker.
Then it seems each re-routine basically has an instance part
and a memoized part, and that it's to flush the memo
after it finishes, in terms of memoizing the inputs.
Result 1 rr(String a1) {
// if a1 is in the memo, return for it
// else queue for it and carry on
}
What is a re-routine?
It's a pattern for cooperative multithreading.
It's sort of a functional approach to functions and flow.
It has a declarative syntax in the language with usual flow-of-control.
So, it's cooperative multithreading so it yields?
No, it just quits, and expects to be called back.
So, if it quits, how does it complete?
The entry point to re-routine provides a callback.
Re-routines only return results to other re-routines,
It's the default callback. Otherwise they just callback.
So, it just quits?
If a re-routine gets called with a null, it throws.
If a re-routine gets a null, it just continues.
If a re-routine completes, it callbacks.
So, can a re-routine call any regular code?
Yeah, there are some issues, though.
So, it's got callbacks everywhere?
Well, it's just got callbacks implicitly everywhere.
So, how does it work?
Well, you build a re-routine with an input and a callback,
you call it, then when it completes, it calls the callback.
Then, re-routines call other re-routines with the argument,
and the callback's in a ThreadLocal, and the re-routine memoizes
all of its return values according to the object identity of the inputs,
then when a re-routine completes, it calls again with another ThreadLocal
indicating to delete the memos, following the exact same flow-of-control
only deleting the memos going along, until it results all the memos in
the re-routines for the interned or ref-counted input are deleted,
then the state of the re-routine is de-allocated.
So, it's sort of like a monad and all in pure and idempotent functions?
Yeah, it's sort of like a monad and all in pure and idempotent functions.
So, it's a model of cooperative multithreading, though with no yield, and callbacks implicitly everywhere?
Yeah, it's sort of figured that a called re-routine always has a callback in the ThreadLocal, because the runtime has pre-emptive multithreading anyways, that the thread runs through its re-routines in their normal declarative flow-of-control with exception handling, and whatever re-routines or other pure monadic idempotent functions it calls, throw when they get null inputs.
Also it sort of doesn't have primitive types, Strings must always be interned, all objects must have a distinct identity w.r.t. ==, and null is never an argument or return value.
So, what does it look like?
interface Reroutine1 {
Result1 rr1(String a1) {
Result2 r2 = reroutine2.rr2(a1);
Result3 r3 = reroutine3.rr3(r2);
return result(r2, r3);
}
}
So, I expect that to return "result(r2, r3)".
Well, that's synchronous, and maybe blocking, the idea is that it calls rr2, gets a1, and rr2 constructs with the callback of rr1 and it's own callback, and a1, and makes a memo for a1, and invokes whatever is its implementation, and returns null, then rr1 continues and invokes rr3 with r2, which is null, so that throws a NullPointerException, and rr1 quits.
So, ..., that's cooperative multithreading?
Well you see what happens is that rr2 invoked another re-routine or end routine, and at some point it will get called back, and that will happen over and over again until rr2 has an r2, then rr2 will memoize (a1, r2), and then it will callback rr1.
Then rr1 had quit, it runs again, this time it gets r2 from the (a1, r2) memo in the monad it's building, then it passes a non-null r2 to rr3, which proceeds in much the same way, while rr1 quits again until rr3 calls it back.
So, ..., it's non-blocking, because it just quits all the time, then happens to run through the same paces filling in?
That's the idea, that re-routines are responsible to build the monad and call-back.
So, can I just implement rr2 and rr3 as synchronous and blocking?
Sure, they're interfaces, their implementation is separate. If they don't know re-routine semantics then they're just synchronous and blocking. They'll get called every time though when the re-routine gets called back, and actually they need to know the semantics of returning an Object or value by identity, because, calling equals() to implement Memo usually would be too much, where the idea is to actually function only monadically, and that given same Object or value input, must return same Object or value output.
So, it's sort of an approach as a monadic pure idempotency?
Well, yeah, you can call it that.
So, what's the point of all this?
Well, the idea is that there are 10,000 connections, and any time one of them demultiplexes off the connection an input command message, then it builds one of these with the response input to the demultiplexer on its protocol on its connection, on the multiplexer to all the connections, with a callback to itself. Then the re-routine is launched and when it returns, it calls-back to the originator by its callback-number, then the output command response writes those back out.
The point is that there are only as many Theads as cores so the goal is that they never block,
and that the memos make for interning Objects by value, then the goal is mostly to receive command objects and handles to request bodies and result objects and handles to response bodies, then to call-back with those in whatever serial order is necessary, or not.
So, won't this run through each of these re-routines umpteen times?
Yeah, you figure that the runtime of the re-routine is on the order of n^2 the order of statements in the re-routine.
So, isn't that terrible?
Well, it doesn't block.
So, it sounds like a big mess.
Yeah, it could be. That's why to avoid blocking and callback semantics, is to make monadic idempotency semantics, so then the re-routines are just written in normal synchronous flow-of-control, and they're well-defined behavior is exactly according to flow-of-control including exception-handling.
There's that and there's basically it only needs one Thread, so, less Thread x stack size, for a deep enough thread call-stack. Then the idea is about one Thread per core, figuring for the thread to always be running and never be blocking.
So, it's just normal flow-of-control.
Well yeah, you expect to write the routine in normal flow-of-control, and to test it with synchronous and in-memory editions that just run through synchronously, and that if you don't much care if it blocks, then it's the same code and has no semantics about the asynchronous or callbacks actually in it. It just returns when it's done.
So what's the requirements of one of these again?
Well, the idea is, that, for a given instance of a re-routine, it's an Object, that implements an interface, and it has arguments, and it has a return value. The expectation is that the re-routine gets called with the same arguments, and must return the same return value. This way later calls to re-routines can match the same expectation, same/same.
Also, if it gets different arguments, by Object identity or primitive value, the re-routine must return a different return value, those being same/same.
The re-routine memoizes its arguments by its argument list, Object or primitive value, and a given argument list is same if the order and types and values of those are same, and it must return the same return value by type and value.
So, how is this cooperative multithreading unobtrusively in flow-of-control again?
Here for example the idea would be, rr2 quits and rr1 continues, rr3 quits and rr1 continues, then reaching rr4, rr4 throws and rr1 quits. When rr2's or rr3's memo-callback completes, then it calls-back rr1. as those come in, at some point rr4 will be fulfilled, and thus rr4 will quit and rr1 will quit. When rr4's callback completes, then it will call-back rr1, which will finally complete, and then call-back whatever called r1. Then rr1 runs itself through one more time to
delete or decrement all its memos.
interface Reroutine1 {
Result1 rr1(String a1) {
Result2 r2 = reroutine2.rr2(a1);
Result3 r3 = reroutine3.rr3(a1);
Result4 r4 = reroutine4.rr4(a1, r2, r3);
return Result1.r4(a1, r4);
}
}
The idea is that it doesn't block when it launchs rr2 and rr3, until such time as it just quits when it tries to invoke rr4 and gets a resulting NullPointerException, then eventually rr4 will complete and be memoized and call-back rr1, then rr1 will be called-back and then complete, then run itself through to delete or decrement the ref-count of all its memo-ized fragmented monad respectively.
Thusly it's cooperative multithreading by never blocking and always just launching callbacks.
There's this System.identityHashCode() method and then there's a notion of Object pools and interning Objects then as for about this way that it's about numeric identity instead of value identity, so that when making memo's that it's always "==" and for a HashMap with System.identityHashCode() instead of ever calling equals(), when calling equals() is more expensive than calling == and the same/same memo-ization is about Object numeric value or the primitive scalar value, those being same/same.
https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#identityHashCode-java.lang.Object-So, you figure to return Objects to these connections by their session and connection and mux/demux in these callbacks and then write those out?
Well, the idea is to make it so that according to the protocol, the back-end sort of knows what makes a handle to a datum of the sort, given the protocol and the protocol and the protocol, and the callback is just these handles, about what goes in the outer callbacks or outside the re-routine, those can be different/same. Then the single writer thread servicing the network I/O just wants to transfer those handles, or, as necessary through the compression and encryption codecs, then write those out, well making use of the java.nio for scatter/gather and vector I/O in the non-blocking and asynchronous I/O as much as possible.
So, that seems a lot of effort to just passing the handles, ....
Well, I don't want to write any code except normal flow-of-control.
So, this same/same bit seems onerous, as long as different/same has a ref-count and thus the memo-ized monad-fragment is maintained when all sorts of requests fetch the same thing.
Yeah, maybe you're right. There's much to be gained by re-using monadic pure idempotent functions yet only invoking them once. That gets into value equality besides numeric equality, though, with regards to going into re-routines and interning all Objects by value, so that inside and through it's all "==" and System.identityHashCode, the memos, then about the ref-counting in the memos.
So, I suppose you know HTTP, and about HTTP/2 and IMAP and NNTP here?
Yeah, it's a thing.
So, I think this needs a much cleaner and well-defined definition, to fully explore its meaning.
Yeah, I suppose. There's something to be said for reading it again.