Skip to content

Commit 8c18778

Browse files
committed
rhino.compute: /activechildren reports ready children; no longer spawns
- /activechildren + /active-children report ComputeChildren.CurrentChildCount (children READY TO SERVE: port open) instead of the raw OS process count. Identical in steady state; differs only while a child is loading. - Still always 200 with the integer body (0 is a valid answer, not a failure). - Removed the ?initialize param: no longer spawns. Launching is POST /launch-children (fill to SpawnCount) / POST /launch-child. - Removed the separate /childrenready endpoint (added in 1776326).
1 parent 1158b2b commit 8c18778

3 files changed

Lines changed: 27 additions & 44 deletions

File tree

CHANGELOG.HOPS.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

7+
## [0.17.1] - 2026-08-24
8+
9+
### Added
10+
11+
- Added a configurable child-process startup timeout on rhino.compute: a new `--child-startup-timeout` CLI flag and a matching `RHINO_COMPUTE_CHILD_STARTUP_TIMEOUT` environment variable (clamped to 5-3600 seconds), so the value can be changed on an already-deployed server without editing `web.config`. A child compute.geometry process does not open its port until Rhino, Grasshopper and the compute plug-ins have all finished loading; on a freshly created cloud instance that first load is dominated by on-demand reads of the Rhino + Grasshopper file set from a snapshot-backed volume and can take well over a minute. (The previous fixed 60-second cap is now raised - see Changed.)
12+
- Added a `RHINO_COMPUTE_CHILDCOUNT` environment variable, equivalent to the existing `--childcount` flag, so the child pool size can be set on an already-deployed server without editing `web.config`. As with the flag, the value is clamped to the `MaxChildren = 64` ceiling.
13+
- Added a `RHINO_COMPUTE_IDLESPAN` environment variable, equivalent to the existing `--idlespan` flag (clamped to 60-86400 seconds), so the child idle-shutdown period can be set on an already-deployed server without editing `web.config`.
14+
15+
### Changed
16+
17+
- Raised the default child-process startup timeout on rhino.compute from 60 to 300 seconds. The previous value dated from 2021 and was chosen for child processes launched locally by Hops on a developer workstation; on a fresh cloud instance the first child's cold load routinely exceeds 60 seconds, so the first request to every newly provisioned server would fail until a retry warmed the OS file cache. 300 seconds is roughly 2.5x the measured worst-case cold load, with headroom for smaller/burstable instance types and user-installed Grasshopper plug-ins. Override with `--child-startup-timeout` / `RHINO_COMPUTE_CHILD_STARTUP_TIMEOUT`.
18+
- On a cold-start bootstrap, concurrent first requests that arrive while the first child compute.geometry is still loading now wait for that single in-flight spawn instead of each starting another child. Previously a client that timed out and retried during the (slow) first load could trigger several parallel Rhino + Grasshopper loads that contended for the same first-touch disk reads and slowed each other down. The pool still fills to `--childcount` afterward via the normal background top-up.
19+
- The `/activechildren` and `/active-children` endpoints now report the number of child compute.geometry processes that are **ready to serve** (their port is open and they are in the pool) rather than the raw count of child OS processes, which previously included children still loading. The endpoints also no longer spawn children: the `?initialize` query parameter (added in 0.17.0) has been removed, and each is now a pure, side-effect-free report that always returns HTTP 200 with the integer count (a count of `0` is a valid answer, not a failure). To launch children, use `POST /launch-children` (fill the pool to the configured `--childcount`) or `POST /launch-child` (add one). This supersedes the `?initialize` behavior described under 0.17.0.
20+
721
## [0.17.0] - 2026-06-05
822

923
### Added

src/rhino.compute/ReverseProxy.cs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -125,27 +125,25 @@ await res.WriteAsJsonAsync(new
125125
app.MapPost("/{*uri}", (HttpRequest req, HttpResponse res) => ProxyRequest(req, res, HttpMethod.Post));
126126
}
127127

128-
// GET /activechildren and GET /active-children — return the count of active compute.geometry
129-
// child processes. ?initialize=true (default) spawns up to SpawnCount children if none are
130-
// running, then returns the count. ?initialize=false just reports the count without spawning,
131-
// useful for passive monitoring that shouldn't trigger billing.
128+
// GET /activechildren and GET /active-children — report the number of compute.geometry
129+
// children READY TO SERVE (port open, in the pool) as a plain integer body. Always 200:
130+
// this is a query that always succeeds and "0" is a valid answer (no children ready yet),
131+
// so a 503 would wrongly imply the endpoint itself failed. Callers treat > 0 as ready.
132+
// Pure report, NO side effects: it never spawns a child, so polling it can never start
133+
// the metered software charge. To launch children use POST /launch-children (fill to
134+
// SpawnCount) or POST /launch-child (add one).
135+
//
136+
// NOTE: the count is the READY pool (ComputeChildren.CurrentChildCount), not the raw OS
137+
// process count — a child still loading Rhino is not counted until its port is open.
132138
static async Task ActiveChildrenEndpoint(HttpContext context)
133139
{
134-
bool initialize = true;
135-
if (context.Request.Query.TryGetValue("initialize", out var initValue)
136-
&& bool.TryParse(initValue, out var parsed))
137-
{
138-
initialize = parsed;
139-
}
140-
if (initialize)
141-
InitializeChildren();
142-
await context.Response.WriteAsync($"{ComputeChildren.ActiveComputeCount}");
140+
await context.Response.WriteAsync($"{ComputeChildren.CurrentChildCount}");
143141
}
144142

145143
// POST /shutdown-children — gracefully shut down children. No params = all; ?port=N
146144
// = just that one. Does not respawn. After shutdown-all, the next /grasshopper request
147-
// triggers an auto-spawn back to SpawnCount; to confirm count without re-spawning, poll
148-
// /activechildren?initialize=false.
145+
// triggers an auto-spawn back to SpawnCount; to confirm the ready count, poll
146+
// /activechildren (it never spawns).
149147
static async Task ShutdownChildrenEndpoint(HttpRequest req, HttpResponse res)
150148
{
151149
if (!TryParsePortFilter(req, out int? portFilter, out string parseError))

src/rhino.compute/Startup.cs

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ public void Configure(IApplicationBuilder app)
7070
{
7171
builder.MapHealthChecks("/healthcheck");
7272
MapValidateEndpoint(builder);
73-
MapChildrenReadyEndpoint(builder);
7473
ReverseProxyModule.MapEndpoints(builder);
7574
});
7675
}
@@ -111,33 +110,5 @@ static void MapValidateEndpoint(Microsoft.AspNetCore.Routing.IEndpointRouteBuild
111110
});
112111
}
113112

114-
// Children-ready probe. Returns 200 once at least one compute.geometry child has
115-
// finished loading and can solve geometry; 503 while the pool is still empty or warming.
116-
// Unlike the catch-all proxy it does NOT call GetComputeServerBaseUrl, so polling
117-
// /childrenready never spawns a child (and never starts the metered software charge).
118-
//
119-
// Distinct from /activechildren, deliberately: /activechildren counts child PROCESSES
120-
// (including ones still loading, whose port is not open yet) and its default can spawn.
121-
// /childrenready reports only children READY TO SERVE, as a plain 200/503 so a health
122-
// probe can key on the status code. Pair with /healthcheck: healthcheck = the front end
123-
// is up; childrenready = geometry can be solved now. (Behind ApiKeyMiddleware when a key
124-
// is configured, same as /healthcheck.)
125-
static void MapChildrenReadyEndpoint(Microsoft.AspNetCore.Routing.IEndpointRouteBuilder builder)
126-
{
127-
builder.MapGet("/childrenready", async ctx =>
128-
{
129-
int ready = ComputeChildren.CurrentChildCount;
130-
if (ready > 0)
131-
{
132-
ctx.Response.StatusCode = 200;
133-
await ctx.Response.WriteAsync($"Ready ({ready} child process(es) ready to serve)");
134-
}
135-
else
136-
{
137-
ctx.Response.StatusCode = 503;
138-
await ctx.Response.WriteAsync("Not ready (no compute.geometry child ready to serve yet)");
139-
}
140-
});
141-
}
142113
}
143114
}

0 commit comments

Comments
 (0)