charly

command module
v0.0.0-...-ab5db2a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: MIT Imports: 122 Imported by: 0

Documentation

Overview

plugin_connect_deadline.go — THE bound on the plugin readiness READ, plus the bounded teardown that lets a failed connect actually report.

charly#588 (RCA): `charly box build` (image-build step) and `charly check live` (plugin resolution) were each observed to block INDEFINITELY — the process parked (state S / futex_wait), its `__plugin serve` children alive and idle at 0.0% CPU, no step output, no error, and no summary.yml for 25+ minutes. The unbounded wait is the readiness READ that follows the handshake: describe() → conn.Meta.Describe(ctx, …) (plugin_grpc.go), a plain gRPC unary call issued on the loader's context, which is context.Background() at every call site — so a plugin child that completes the go-plugin handshake and then never answers leaves that call parked FOREVER.

The other legs are already bounded, which is why the bound belongs HERE and not around the whole connect: go-plugin bounds the spawn→handshake leg with StartTimeout (client.go: `timeout := time.After(c.config.StartTimeout)`, set from the project's resolved readiness per_attempt — plugin_transport.go's localPluginClientConfig), and the build BEFORE the connect is legitimately slow (a cold `go build` of a candy plus its module graph takes minutes), so it must not be bounded by a readiness deadline at all. Reproduced before the fix, and kept as a regression test (plugin_connect_deadline_test.go): LocalTransport.Connect against a child whose Describe never returns was still parked after 60s, while the identical connect under this bound returned in ~the bound with "plugin describe: rpc error: code = DeadlineExceeded".

WHY A FIXED CONSTANT AND NOT the resolved readiness per_attempt: resolving readiness from inside the connect path is a DEADLOCK, measured while building this fix. loadedReadiness() runs LoadUnified, which materialises the project through the plugin-loader candy, which calls loadBuiltinPluginUnits → builtinGateOnce.Do — and the builtin gate is ITSELF a caller of the connect seam, so a seam that resolved readiness would re-enter that non-reentrant sync.Once from inside its own callback and park forever (SIGQUIT dump: goroutine 1 in loadBuiltinPluginUnits → sync.Once.doSlow → loadBuiltinPluginUnits.func1 → loadedReadiness → sync.Once.doSlow → loadBuiltinPluginUnits). The bound therefore stays a plain constant, and it costs nothing: the per_attempt knob keeps governing the SPAWN leg (go-plugin's StartTimeout) while this constant governs only the post-handshake READ, which is a sub-second operation for a healthy plugin (its schema is a CUE string already in the child's memory) — the two never interact, so raising per_attempt for a heavy roster still moves what it always moved.

Code generated by charly/internal/pluginsgen (run by scripts/bootstrap-charly.sh). DO NOT EDIT.

One registerCompiledPlugin() call per plugin candy named in charly.yml `compiled_plugins:`. Regenerated by `scripts/bootstrap-charly.sh` before `go build`. Each compiled-in plugin module is a `require` pin in charly/go.mod resolved from the module proxy (no `use` directive in go.work — see the generator's go.work writer); a dev build (-dev-plugin) resolves a plugin through go.work.dev instead. Edit charly.yml's compiled_plugins and re-run the generator, never this file.

Code generated by charly/internal/pluginsgen (run by scripts/bootstrap-charly.sh). DO NOT EDIT.

pluginProviderRefs maps "<class>:<word>" -> the canonical candy ref of the plugin that serves it, DERIVED from every plugin repo's own `plugin:` block (its `providers:` + `source:`). This is the ONE word->provider fact: it lives in the plugin repos, never in charly. Regenerate with the rest of the wiring.

Source Files

Directories

Path Synopsis
internal
pluginsgen command
Command pluginsgen emits the compiled-in plugin wiring from a charly.yml's `compiled_plugins:` list: charly/plugins_generated.go (one registerCompiledPlugin call per plugin candy) AND the repo-root go.work (a `use` directive per candy, so `go build ./charly` resolves the candy imports in plugins_generated.go).
Command pluginsgen emits the compiled-in plugin wiring from a charly.yml's `compiled_plugins:` list: charly/plugins_generated.go (one registerCompiledPlugin call per plugin candy) AND the repo-root go.work (a `use` directive per candy, so `go build ./charly` resolves the candy imports in plugins_generated.go).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL