flan dev is one process, so the unix socket between the compiler and the program was a connect, a write and a read that never left the machine's own memory. A delivery is now a call into the agent's verb table. flan_agent.c's handlers write into a sink that is either an fd or a growing buffer, so serve() and the new flan_agent_request() share one verb table rather than two that would drift -- every answer in there is assembled from several pieces, so the seam had to be the writing. Dev.deliver, Dev.result and Dev.ask were three copies of the same socket dance and are now three lines over one Dev.request. Which path is taken is the linker's answer, not a flag: flan_agent_request is weak in dynload_stubs.c, null in every binary that links no flan_agent.o, and Agent.request is None there. So --two-process, flan reload and every test go on using the socket with nothing to configure. Three things the direct path must not quietly change. The ring's room check is separate from its store, which was safe only while the accept loop was the sole producer, so handle_line runs under a mutex. A delivery is still only published -- the install is one store per function on the game thread at a frame boundary, and doing it on the spot would be a frame running half in the old code and half in the new. And the OCaml runtime system is released across the call, because a delivery is a dlopen. Measured, and the number is the point: the transport was ~50us of a 23ms redefinition, so end to end did not move. Code generation is 19 of the 22 milliseconds. The merge's prize was never latency. The test is a deletion, because a reply cannot say which way it came: test_dev.ml unlinks the agent's socket file once the merged program has bound it, and every evaluation after that still installs.
24 lines
1.3 KiB
OCaml
24 lines
1.3 KiB
OCaml
(** The agent, as the compiler reaches it when both are in one process.
|
|
|
|
[flan dev] builds one binary that is the compiled program and holds this
|
|
compiler; the agent's listener thread and this one are threads in it. So a
|
|
request that used to be a connect, a write and a read on a unix socket
|
|
looping back into this address space is a call into
|
|
[vendor/agent/flan_agent.c] instead.
|
|
|
|
[None] means there is no agent in this process — the [flan] binary that
|
|
builds the merged program, [flan reload], the two-process daemon, every
|
|
test. The answer comes from the linker rather than from a flag: the symbol
|
|
is weak in [dynload_stubs.c] and is null in a binary that links no
|
|
[flan_agent.o]. [Dev] falls back to the socket on [None], which is what
|
|
keeps [--two-process] working unchanged.
|
|
|
|
What does *not* change is where a delivery lands. The agent still only
|
|
publishes to its ring; the game thread picks the job up when it next
|
|
reaches a frame boundary. The rule that keeps the collector away from the
|
|
frame thread — the game thread never calls into OCaml, requests are left
|
|
somewhere and picked up — is the same rule with the same mechanism. This is
|
|
the compiler thread calling C, never the other way. *)
|
|
|
|
external request : string -> string option = "flan_agent_direct"
|